base/transaction_builder.js

import { default as xdr } from './generated/xdr_generated'
import { UnsignedHyper } from 'js-xdr'
import { Keypair } from './keypair'
import { Transaction } from './transaction'
import { Memo } from './memo'
import BigNumber from 'bignumber.js'
import clone from 'lodash/clone'

let TX_EXPIRATION_PERIOD = 60 * 60 * 24 * 7 - 60 * 60
let BASE_FEE = 0 // Stroops

export class TransactionBuilder {
  /**
   * <p>Transaction builder helps constructs a new `{@link Transaction}` using the given account id</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
   * 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 {string} 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 {string} [opts.timeBounds.minTime] - 64 bit unix timestamp
   * @param {string} [opts.timeBounds.maxTime] - 64 bit unix timestamp
   * @param {Memo} [opts.memo] - The memo for the transaction
   */
  constructor (sourceAccount, opts = {}) {
    if (!sourceAccount) {
      throw new Error('must specify source account for the transaction')
    }
    this.source = sourceAccount
    this.operations = []
    this.signers = []

    this.baseFee = opts.fee || BASE_FEE
    this.timeBounds = clone(opts.timeBounds)
    this.salt = opts.salt
    this.memo = opts.memo || Memo.none()

    // the signed base64 form of the transaction to be sent to Horizon
    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 {xdr.Memo} memo The xdr memo object, use {@link Memo} static methods.
   * @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 () {
    if (!this.salt) {
      this.salt = BigNumber.random(0)
    }

    let attrs = {
      sourceAccount: Keypair
        .fromAccountId(this.source)
        .xdrAccountId(),
      salt: xdr.Salt.fromString(this.salt.toString()),
      fee: this.baseFee * this.operations.length,
      memo: this.memo,
      ext: new xdr.TransactionExt(xdr.LedgerVersion.emptyVersion())
    }

    if (!this.timeBounds) {
      this.timeBounds = {
        minTime: 0,
        maxTime: Math.round(Date.now() / 1000) + TX_EXPIRATION_PERIOD
      }
    }

    attrs.timeBounds = new xdr.TimeBounds({
      minTime: UnsignedHyper.fromString(this.timeBounds.minTime.toString()),
      maxTime: UnsignedHyper.fromString(this.timeBounds.maxTime.toString()),
      ext: new xdr.TimeBoundsExt(xdr.LedgerVersion.emptyVersion())
    })

    let xtx = new xdr.Transaction(attrs)

    xtx.operations(this.operations)
    let xenv = new xdr.TransactionEnvelope({ tx: xtx })

    let tx = new Transaction(xenv)
    tx.sign(...this.signers)

    return tx
  }
}