Source: primitives/coin.js

/*!
 * coin.js - coin object for bcoin
 * Copyright (c) 2014-2015, Fedor Indutny (MIT License)
 * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
 * https://github.com/bcoin-org/bcoin
 */

'use strict';

const assert = require('bsert');
const bio = require('bufio');
const util = require('../utils/util');
const Amount = require('../btc/amount');
const Output = require('./output');
const Network = require('../protocol/network');
const consensus = require('../protocol/consensus');
const Outpoint = require('./outpoint');
const {inspectSymbol} = require('../utils');

/**
 * Coin
 * Represents an unspent output.
 * @alias module:primitives.Coin
 * @extends Output
 * @property {Number} version
 * @property {Number} height
 * @property {Amount} value
 * @property {Script} script
 * @property {Boolean} coinbase
 * @property {Hash} hash
 * @property {Number} index
 */

class Coin extends Output {
  /**
   * Create a coin.
   * @constructor
   * @param {Object} options
   */

  constructor(options) {
    super();

    this.version = 1;
    this.height = -1;
    this.coinbase = false;
    this.hash = consensus.ZERO_HASH;
    this.index = 0;

    if (options)
      this.fromOptions(options);
  }

  /**
   * Inject options into coin.
   * @private
   * @param {Object} options
   */

  fromOptions(options) {
    assert(options, 'Coin data is required.');

    if (options.version != null) {
      assert((options.version >>> 0) === options.version,
        'Version must be a uint32.');
      this.version = options.version;
    }

    if (options.height != null) {
      if (options.height !== -1) {
        assert((options.height >>> 0) === options.height,
          'Height must be a uint32.');
        this.height = options.height;
      } else {
        this.height = -1;
      }
    }

    if (options.value != null) {
      assert(Number.isSafeInteger(options.value) && options.value >= 0,
        'Value must be a uint64.');
      this.value = options.value;
    }

    if (options.script)
      this.script.fromOptions(options.script);

    if (options.coinbase != null) {
      assert(typeof options.coinbase === 'boolean',
        'Coinbase must be a boolean.');
      this.coinbase = options.coinbase;
    }

    if (options.hash != null) {
      assert(Buffer.isBuffer(options.hash));
      this.hash = options.hash;
    }

    if (options.index != null) {
      assert((options.index >>> 0) === options.index,
        'Index must be a uint32.');
      this.index = options.index;
    }

    return this;
  }

  /**
   * Instantiate Coin from options object.
   * @private
   * @param {Object} options
   */

  static fromOptions(options) {
    return new this().fromOptions(options);
  }

  /**
   * Clone the coin.
   * @private
   * @returns {Coin}
   */

  clone() {
    assert(false, 'Coins are not cloneable.');
  }

  /**
   * Calculate number of confirmations since coin was created.
   * @param {Number?} height - Current chain height. Network
   * height is used if not passed in.
   * @return {Number}
   */

  getDepth(height) {
    assert(typeof height === 'number', 'Must pass a height.');

    if (this.height === -1)
      return 0;

    if (height === -1)
      return 0;

    if (height < this.height)
      return 0;

    return height - this.height + 1;
  }

  /**
   * Serialize coin to a key
   * suitable for a hash table.
   * @returns {String}
   */

  toKey() {
    return Outpoint.toKey(this.hash, this.index);
  }

  /**
   * Inject properties from hash table key.
   * @private
   * @param {String} key
   * @returns {Coin}
   */

  fromKey(key) {
    const {hash, index} = Outpoint.fromKey(key);
    this.hash = hash;
    this.index = index;
    return this;
  }

  /**
   * Instantiate coin from hash table key.
   * @param {String} key
   * @returns {Coin}
   */

  static fromKey(key) {
    return new this().fromKey(key);
  }

  /**
   * Get little-endian hash.
   * @returns {Hash}
   */

  rhash() {
    return util.revHex(this.hash);
  }

  /**
   * Get little-endian hash.
   * @returns {Hash}
   */

  txid() {
    return this.rhash();
  }

  /**
   * Convert the coin to a more user-friendly object.
   * @returns {Object}
   */

 [inspectSymbol]() {
    return {
      type: this.getType(),
      version: this.version,
      height: this.height,
      value: Amount.btc(this.value),
      script: this.script,
      coinbase: this.coinbase,
      hash: this.hash ? util.revHex(this.hash) : null,
      index: this.index,
      address: this.getAddress()
    };
  }

  /**
   * Convert the coin to an object suitable
   * for JSON serialization.
   * @returns {Object}
   */

  toJSON() {
    return this.getJSON();
  }

  /**
   * Convert the coin to an object suitable
   * for JSON serialization. Note that the hash
   * will be reversed to abide by bitcoind's legacy
   * of little-endian uint256s.
   * @param {Network} network
   * @param {Boolean} minimal
   * @returns {Object}
   */

  getJSON(network, minimal) {
    let addr = this.getAddress();

    network = Network.get(network);

    if (addr)
      addr = addr.toString(network);

    return {
      version: this.version,
      height: this.height,
      value: this.value,
      script: this.script.toJSON(),
      address: addr,
      coinbase: this.coinbase,
      hash: !minimal ? this.rhash() : undefined,
      index: !minimal ? this.index : undefined
    };
  }

  /**
   * Inject JSON properties into coin.
   * @private
   * @param {Object} json
   */

  fromJSON(json) {
    assert(json, 'Coin data required.');
    assert((json.version >>> 0) === json.version, 'Version must be a uint32.');
    assert(json.height === -1 || (json.height >>> 0) === json.height,
      'Height must be a uint32.');
    assert(Number.isSafeInteger(json.value) && json.value >= 0,
      'Value must be a uint64.');
    assert(typeof json.coinbase === 'boolean', 'Coinbase must be a boolean.');

    this.version = json.version;
    this.height = json.height;
    this.value = json.value;
    this.script.fromJSON(json.script);
    this.coinbase = json.coinbase;

    if (json.hash != null) {
      assert(typeof json.hash === 'string', 'Hash must be a string.');
      assert(json.hash.length === 64, 'Hash must be a string.');
      assert((json.index >>> 0) === json.index, 'Index must be a uint32.');
      this.hash = util.fromRev(json.hash);
      this.index = json.index;
    }

    return this;
  }

  /**
   * Instantiate an Coin from a jsonified coin object.
   * @param {Object} json - The jsonified coin object.
   * @returns {Coin}
   */

  static fromJSON(json) {
    return new this().fromJSON(json);
  }

  /**
   * Calculate size of coin.
   * @returns {Number}
   */

  getSize() {
    return 17 + this.script.getVarSize();
  }

  /**
   * Write the coin to a buffer writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    let height = this.height;

    if (height === -1)
      height = 0x7fffffff;

    bw.writeU32(this.version);
    bw.writeU32(height);
    bw.writeI64(this.value);
    bw.writeVarBytes(this.script.toRaw());
    bw.writeU8(this.coinbase ? 1 : 0);

    return bw;
  }

  /**
   * Serialize the coin.
   * @returns {Buffer|String}
   */

  toRaw() {
    const size = this.getSize();
    return this.toWriter(bio.write(size)).render();
  }

  /**
   * Inject properties from serialized buffer writer.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.version = br.readU32();
    this.height = br.readU32();
    this.value = br.readI64();
    this.script.fromRaw(br.readVarBytes());
    this.coinbase = br.readU8() === 1;

    if (this.height === 0x7fffffff)
      this.height = -1;

    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    return this.fromReader(bio.read(data));
  }

  /**
   * Instantiate a coin from a buffer reader.
   * @param {BufferReader} br
   * @returns {Coin}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate a coin from a serialized Buffer.
   * @param {Buffer} data
   * @param {String?} enc - Encoding, can be `'hex'` or null.
   * @returns {Coin}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }

  /**
   * Inject properties from TX.
   * @param {TX} tx
   * @param {Number} index
   */

  fromTX(tx, index, height) {
    assert(typeof index === 'number');
    assert(typeof height === 'number');
    assert(index >= 0 && index < tx.outputs.length);
    this.version = tx.version;
    this.height = height;
    this.value = tx.outputs[index].value;
    this.script = tx.outputs[index].script;
    this.coinbase = tx.isCoinbase();
    this.hash = tx.hash();
    this.index = index;
    return this;
  }

  /**
   * Instantiate a coin from a TX
   * @param {TX} tx
   * @param {Number} index - Output index.
   * @returns {Coin}
   */

  static fromTX(tx, index, height) {
    return new this().fromTX(tx, index, height);
  }

  /**
   * Test an object to see if it is a Coin.
   * @param {Object} obj
   * @returns {Boolean}
   */

  static isCoin(obj) {
    return obj instanceof Coin;
  }
}

/*
 * Expose
 */

module.exports = Coin;