all files / src/ transaction_builder.js

98.61% Statements 71/72
92.86% Branches 26/28
100% Functions 10/10
96.55% Lines 28/29
2 statements, 5 branches Ignored     
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128                          1× 1× 1×                                                                                         1× 18×     18× 18× 18× 18× 18×     18×                 24× 24×                 7× 7×                 18×   18×               18× 1× 1× 1×     18× 18×   18× 18×   18×   18×      
import {default as xdr} from "./generated/digitalbits-xdr_generated";
import {UnsignedHyper} from "js-xdr";
import {hash} from "./hashing";
import {Keypair} from "./keypair";
import {Account} from "./account";
import {Operation} from "./operation";
import {Transaction} from "./transaction";
import {Memo} from "./memo";
import BigNumber from 'bignumber.js';
import clone from "lodash/clone";
import map from "lodash/map";
import isUndefined from "lodash/isUndefined";
 
let BASE_FEE     = 100; // Stroops
let MIN_LEDGER   = 0;
let MAX_LEDGER   = 0xFFFFFFFF; // max uint32
 
/**
 * <p>Transaction builder helps constructs a new `{@link Transaction}` using the given {@link Account}
 * as the transaction's "source account". The transaction will use the current sequence
 * number of the given account as its sequence number and increment the given account's
 * sequence number by one. The given source account must include a private key for signing
 * the transaction or an error will be thrown.</p>
 *
 * <p>Operations can be added to the transaction via their corresponding builder methods, and
 * each returns the TransactionBuilder object so they can be chained together. After adding
 * the desired operations, call the `build()` method on the `TransactionBuilder` to return a fully
 * constructed `{@link Transaction}` that can be signed. The returned transaction will contain the
 * sequence number of the source account and include the signature from the source account.</p>
 *
 * <p>The following code example creates a new transaction with {@link Operation.createAccount} and
 * {@link Operation.payment} operations.
 * The Transaction's source account first funds `destinationA`, then sends
 * a payment to `destinationB`. The built transaction is then signed by `sourceKeypair`.</p>
 *
 * ```
 * var transaction = new TransactionBuilder(source)
 *  .addOperation(Operation.createAccount({
        destination: destinationA,
        startingBalance: "20"
    }) // <- funds and creates destinationA
    .addOperation(Operation.payment({
        destination: destinationB,
        amount: "100"
        asset: Asset.native()
    }) // <- sends 100 XLM to destinationB
 *   .build();
 *
 * transaction.sign(sourceKeypair);
 * ```
 * @constructor
 * @param {Account} sourceAccount - The source account for this transaction.
 * @param {object} [opts]
 * @param {number} [opts.fee] - The max fee willing to pay per operation in this transaction (**in stroops**).
 * @param {object} [opts.timebounds] - The timebounds for the validity of this transaction.
 * @param {number|string} [opts.timebounds.minTime] - 64 bit unix timestamp
 * @param {number|string} [opts.timebounds.maxTime] - 64 bit unix timestamp
 * @param {Memo} [opts.memo] - The memo for the transaction
 */
export class TransactionBuilder {
  constructor(sourceAccount, opts={}) {
    Iif (!sourceAccount) {
      throw new Error("must specify source account for the transaction");
    }
    this.source     = sourceAccount;
    this.operations = [];
    this.baseFee    = (isUndefined(opts.fee) ? BASE_FEE : opts.fee);
    this.timebounds = clone(opts.timebounds);
    this.memo       = opts.memo || Memo.none();
 
    // the signed base64 form of the transaction to be sent to Frontier
    this.blob = null;
  }
 
  /**
   * Adds an operation to the transaction.
   * @param {xdr.Operation} operation The xdr operation object, use {@link Operation} static methods.
   * @returns {TransactionBuilder}
   */
  addOperation(operation) {
    this.operations.push(operation);
    return this;
  }
 
  /**
   * Adds a memo to the transaction.
   * @param {Memo} memo {@link Memo} object
   * @returns {TransactionBuilder}
   */
  addMemo(memo) {
    this.memo = memo;
    return this;
  }
 
  /**
   * This will build the transaction.
   * It will also increment the source account's sequence number by 1.
   * @returns {Transaction} This method will return the built {@link Transaction}.
   */
  build() {
    let sequenceNumber = new BigNumber(this.source.sequenceNumber()).add(1);
 
    var attrs = {
      sourceAccount: Keypair.fromPublicKey(this.source.accountId()).xdrAccountId(),
      fee:           this.baseFee * this.operations.length,
      seqNum:        xdr.SequenceNumber.fromString(sequenceNumber.toString()),
      memo:          this.memo ? this.memo.toXDRObject() : null,
      ext:           new xdr.TransactionExt(0)
    };
 
    if (this.timebounds) {
      this.timebounds.minTime = UnsignedHyper.fromString(this.timebounds.minTime.toString());
      this.timebounds.maxTime = UnsignedHyper.fromString(this.timebounds.maxTime.toString());
      attrs.timeBounds = new xdr.TimeBounds(this.timebounds);
    }
 
    let xtx = new xdr.Transaction(attrs);
    xtx.operations(this.operations);
 
    let xenv = new xdr.TransactionEnvelope({tx:xtx});
    let tx = new Transaction(xenv);
 
    this.source.incrementSequenceNumber();
    
    return tx;
  }
}