all files / src/ transaction.js

97.62% Statements 82/84
92.86% Branches 26/28
88.24% Functions 15/17
95.35% Lines 41/43
3 statements, 4 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 129 130 131 132 133 134 135                      1× 1×                     1× 20× 2× 2×     20× 20× 20× 20× 20×   20× 20× 1×           20× 20× 26×     20× 20×       2×                       1× 2× 1× 1× 1×                   2×       2× 1×     1× 1× 1× 1×               3×                         3× 1×     2×                       4× 4× 4×   4×      
import {xdr, hash} from "./index";
 
import {StrKey} 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';
import crypto from "crypto";
 
let MIN_LEDGER   = 0;
let MAX_LEDGER   = 0xFFFFFFFF; // max uint32
 
/**
 * 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.
 */
export class Transaction {
  constructor(envelope) {
    if (typeof envelope === "string") {
      let buffer = new Buffer(envelope, "base64");
      envelope = xdr.TransactionEnvelope.fromXDR(buffer);
    }
    // since this transaction is immutable, save the tx
    this.tx       = envelope.tx();
    this.source   = StrKey.encodeEd25519PublicKey(envelope.tx().sourceAccount().ed25519());
    this.fee      = this.tx.fee();
    this._memo    = this.tx.memo();
    this.sequence = this.tx.seqNum().toString();
 
    let timeBounds = this.tx.timeBounds();
    if (timeBounds) {
      this.timeBounds = {
        minTime: timeBounds.minTime().toString(),
        maxTime: timeBounds.maxTime().toString()
      };
    }
 
    let operations  = this.tx.operations() || [];
    this.operations = map(operations, op => {
      return Operation.fromXDRObject(op);
    });
 
    let signatures = envelope.signatures() || [];
    this.signatures = map(signatures, s => s);
  }
 
  get memo() {
    return Memo.fromXDRObject(this._memo);
  }
 
  set memo(value) {
    throw new Error("Transaction is immutable");
  }
 
  /**
   * Signs the transaction with the given {@link Keypair}.
   * @param {...Keypair} keypairs Keypairs of signers
   * @returns {void}
   */
  sign(...keypairs) {
    let txHash = this.hash();
    let newSigs = each(keypairs, kp => {
      let sig = kp.signDecorated(txHash);
      this.signatures.push(sig);
    });
  }
 
  /**
   * Add `hashX` signer preimage as signature.
   * @param {Buffer|String} preimage Preimage of hash used as signer
   * @returns {void}
   */
  signHashX(preimage) {
    Iif (isString(preimage)) {
      preimage = Buffer.from(preimage, "hex");
    }
 
    if (preimage.length > 64) {
      throw new Error('preimage cannnot be longer than 64 bytes');
    }
 
    let signature = preimage;
    let hash = crypto.createHash('sha256').update(preimage).digest();
    let hint = hash.slice(hash.length - 4);
    this.signatures.push(new xdr.DecoratedSignature({hint, signature}));
  }
 
  /**
   * 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 DigitalBits Network will accept.
   *
   * It is composed of a 4 prefix bytes followed by the xdr-encoded form
   * of this transaction.
   * @returns {Buffer}
   */
  signatureBase() {
    if (Network.current() === null) {
      throw new Error("No network selected. Use `Network.use`, `Network.usePublicNetwork` or `Network.useTestNetwork` helper methods to select network.");
    }
 
    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 envelope = new xdr.TransactionEnvelope({tx, signatures});
 
    return envelope;
  }
}