UNPKG

@ellcrys/spell

Version:

The official JavaScript library for Ellcrys

274 lines (248 loc) 6.14 kB
/** * @module TransactionBuilder */ import Decimal from "decimal.js"; import moment = require("moment"); import { PrivateKey } from ".."; import { Transaction, TxResult } from "../../.."; import errors from "../errors"; import { Address } from "../key"; import RPCClient from "../rpcclient"; import TxUtility from "./tx_util"; const b58 = require("bs58check"); export const NumDecimals = 18; export const TxPayloadVersion = Buffer.from([95]); /** * Transaction builder provides * transaction creation and execution * capabilities. * * @class TxBuilder */ class TxBuilder { public balance: TxBalanceBuilder; constructor(client: RPCClient) { this.balance = new TxBalanceBuilder(client); } } export default TxBuilder; /** * TxBalanceBuilder provides the ability to * build and execute a balance transaction * * @class TxBalanceBuilder */ export class TxBalanceBuilder extends TxUtility { /** * The transaction data * * @protected * @type {Transaction} * @memberof TxBalanceBuilder */ protected data: Transaction; /** * The RPC client * * @protected * @type {RPCClient} * @memberof TxBalanceBuilder */ protected client: undefined | RPCClient; /** * Creates an instance of TxBalanceBuilder. * * @param {RPCClient} [client] The RPC client * @memberof TxBalanceBuilder */ constructor(client?: RPCClient) { super(); this.client = client; this.data = { type: 0x1, }; } /** * Set the sender address * * @param {string|Address} address The address * @returns {TxBalanceBuilder} * @memberof TxBalanceBuilder */ public from(address: string | Address): TxBalanceBuilder { if (address instanceof Address) { this.data.from = address.toString(); } else { this.data.from = address; } return this; } /** * Set the recipient address * * @param {string|Address} address The address * @returns {TxBalanceBuilder} * @memberof TxBalanceBuilder */ public to(address: string | Address): TxBalanceBuilder { if (address instanceof Address) { this.data.to = address.toString(); } else { this.data.to = address; } return this; } /** * The next nonce of the sending account * * @param {number} n The next nonce of the sender * @returns {TxBalanceBuilder} * @memberof TxBalanceBuilder */ public nonce(num: number): TxBalanceBuilder { this.data.nonce = num; return this; } /** * Set the amount to send from the * sender to the recipient * * @param {string} value The amount to send * @returns {TxBalanceBuilder} * @memberof TxBalanceBuilder */ public value(value: string | Decimal): TxBalanceBuilder { if (value instanceof Decimal) { this.data.value = value.toFixed(NumDecimals); } else { this.data.value = value; } return this; } /** * Set the fee to be paid for this * transaction * * @param {string} value The amount to pay as fee * @returns {TxBalanceBuilder} * @memberof TxBalanceBuilder */ public fee(fee: string | Decimal): TxBalanceBuilder { if (fee instanceof Decimal) { this.data.fee = fee.toFixed(NumDecimals); } else { this.data.fee = fee; } return this; } /** * Reset the transaction builder * * @memberof TxBalanceBuilder */ public reset() { this.data = {}; } /** * Returns the transaction data without sending * it to the network. It will finalize the transaction * if the sender's private key is provided. * * @param {PrivateKey} [sk] The senders private key * @memberof TxBalanceBuilder */ public async payload(sk?: PrivateKey) { // Set timestamp this.data.timestamp = moment().unix(); // If the private is provided, // we can attempt to finalize // the builder if (sk) { await this.finalize(sk); } return this.data; } /** * Send the transaction to the network * * @param {PrivateKey} sk The sender's private key * @returns {Promise<TxResult>} * @memberof TxBalanceBuilder */ public async send(sk: PrivateKey): Promise<TxResult> { return new Promise(async (resolve, reject) => { this.data.timestamp = moment().unix(); await this.finalize(sk); if (!this.client) { return reject(errors.ClientNotInitialized); } this.client .call("ell_send", this.data) .then((hash) => { return resolve(hash); }) .catch((err) => { return reject(err); }); }); } /** * Returns a base58 serialized version of the * transaction. * * @param {PrivateKey} sk The sender's private key * @returns {string} * @memberof TxBalanceBuilder */ public async packed(sk: PrivateKey): Promise<string> { this.data.timestamp = moment().unix(); await this.finalize(sk); const txBytes = Buffer.from(JSON.stringify(this.data)); return Promise.resolve(b58.encode(Buffer.concat([TxPayloadVersion, txBytes]))); } /** * Performs final operations such computing and * setting the transaction hash and signature as * well as setting the sender public key and time. * * @protected * @param {PrivateKey} sk The sender's private key * @returns {Promise<string>} Returns the transaction hash * @memberof TxBalanceBuilder */ protected finalize(sk?: PrivateKey): Promise<string> { return new Promise(async (resolve, reject) => { if (!sk) { return reject(errors.RequirePrivateKey); } this.data.senderPubKey = sk.publicKey().toBase58(); // We need to determine the senders current nonce // if it has not been manually set. if (!this.data.nonce) { if (!this.client) { return reject(errors.ClientNotInitialized); } try { const nonce = await this.client.call( "state_suggestNonce", this.data.from, ); this.data.nonce = nonce; } catch (e) { if (e.statusCode === 400) { e.data = JSON.parse(e.data); if (e.data.error.code === 30001) { return reject(errors.UnknownSenderAccount); } } return reject(e); } } // Compute and set hash this.data.hash = this.hash(this.data, ""); // Compute and set signature this.data.sig = this.sign(this.data, sk, ""); return resolve(this.data.hash); }); } }