base/transaction.js

import { xdr, hash } from './index'

import { encodeCheck } from './strkey'
import { Operation } from './operation'
import { Network } from './network'
import { Memo } from './memo'
import map from 'lodash/map'
import each from 'lodash/each'
import isString from 'lodash/isString'

export class Transaction {
  /**
   * A new Transaction object is created from a transaction envelope or via {@link TransactionBuilder}.
   * Once a Transaction has been created from an envelope, its attributes and operations
   * should not be changed. You should only add signers (using {@link Transaction#sign}) to a Transaction object before
   * submitting to the network or forwarding on to additional signers.
   * @constructor
   * @param {string|xdr.TransactionEnvelope} envelope - The transaction envelope object or base64 encoded string.
   */
  constructor (envelope) {
    if (typeof envelope === 'string') {
      let buffer = Buffer.from(envelope, 'base64')
      envelope = xdr.TransactionEnvelope.fromXDR(buffer)
    }
    // since this transaction is immutable, save the tx
    this.tx = envelope.tx()
    this.source = encodeCheck(
      'accountId',
      envelope.tx().sourceAccount().ed25519()
    )
    this.fee = this.tx.fee()
    this.memo = this.tx.memo()
    this.ext = new xdr.TransactionExt(xdr.LedgerVersion.emptyVersion())
    let operations = this.tx.operations() || []
    this.operations = map(operations, op => {
      return Operation.operationToObject(op)
    })

    let signatures = envelope.signatures() || []
    this.signatures = map(signatures, s => s)

    let additionalSignatures = envelope.additionalSignatures() || []
    this.additionalSignatures = map(additionalSignatures, s => s)
  }

  /**
   * Signs the transaction with the given {@link Keypair}.
   * @param {...Keypair} keypairs Keypairs of signers
   * @returns {void}
   */
  sign (...keypairs) {
    let txHash = this.hash()
    each(keypairs, kp => {
      let sig = kp.signDecorated(txHash)
      this.signatures.push(sig)
    })
  }

  /**
   * Adds any other additional signature to the transaction.
   * @param {...addSignatures} a list of string or Memo objects each representing a serialized version of a signature
   * in any format (as a string or a Memo object)
   * @returns {Transaction}
   */
  addAditionalSign (...addSignatures) {
    each(addSignatures, sg => {
      let sig = sg
      if (isString(sig)) {
        sig = Memo.additionalSignature(sig)
      } else if (!(sig instanceof Memo)) {
        throw new Error('additional signature should be either a string or a Memo')
      }
      this.additionalSignatures.push(sig)
    })
    return this
  }

  /**
   * Returns a hash for this transaction, suitable for signing.
   * @returns {Buffer}
   */
  hash () {
    return hash(this.signatureBase())
  }

  /**
   * Returns the "signature base" of this transaction, which is the value
   * that, when hashed, should be signed to create a signature that
   * validators on the Stellar Network will accept.
   *
   * It is composed of a 4 prefix bytes followed by the xdr-encoded form
   * of this transaction.
   * @returns {Buffer}
   */
  signatureBase () {
    return Buffer.concat([
      Network.current().networkId(),
      xdr.EnvelopeType.envelopeTypeTx().toXDR(),
      this.tx.toXDR()
    ])
  }

  /**
   * To envelope returns a xdr.TransactionEnvelope which can be submitted to the network.
   * @returns {xdr.TransactionEnvelope}
   */
  toEnvelope () {
    let tx = this.tx
    let signatures = this.signatures
    let additionalSignatures = this.additionalSignatures
    let ext = new xdr.TransactionEnvelopeExt(xdr.LedgerVersion.emptyVersion())
    let envelope = new xdr.TransactionEnvelope({
      tx,
      signatures,
      additionalSignatures,
      ext
    })

    return envelope
  }
}