Source: client/wallet.js

/*!
 * wallet.js - http wallet client for bcoin
 * Copyright (c) 2017, Christopher Jeffrey (MIT License).
 * https://github.com/bcoin-org/bcoin
 */

'use strict';

const assert = require('bsert');
const EventEmitter = require('events');
const {Client} = require('bcurl');

/**
 * Wallet Client
 * @extends {bcurl.Client}
 */

class WalletClient extends Client {
  /**
   * Create a wallet client.
   * @param {Object?} options
   */

  constructor(options) {
    super(options);
    this.wallets = new Map();
  }

  /**
   * Open the client.
   * @private
   * @returns {Promise}
   */

  init() {
    this.bind('tx', (id, details) => {
      this.dispatch(id, 'tx', details);
    });

    this.bind('confirmed', (id, details) => {
      this.dispatch(id, 'confirmed', details);
    });

    this.bind('unconfirmed', (id, details) => {
      this.dispatch(id, 'unconfirmed', details);
    });

    this.bind('conflict', (id, details) => {
      this.dispatch(id, 'conflict', details);
    });

    this.bind('updated', (id, details) => {
      this.dispatch(id, 'updated', details);
    });

    this.bind('address', (id, receive) => {
      this.dispatch(id, 'address', receive);
    });

    this.bind('balance', (id, balance) => {
      this.dispatch(id, 'balance', balance);
    });
  }

  /**
   * Dispatch event.
   * @param {Number} id
   * @param {String} event
   * @private
   */

  dispatch(id, event, ...args) {
    const wallet = this.wallets.get(id);

    if (wallet)
      wallet.emit(event, ...args);
  }

  /**
   * Open the client.
   * @returns {Promise}
   */

  async open() {
    await super.open();
    this.init();
  }

  /**
   * Close the client.
   * @returns {Promise}
   */

  async close() {
    await super.close();
    this.wallets = new Map();
  }

  /**
   * Auth with server.
   * @returns {Promise}
   */

  async auth() {
    await this.call('auth', this.password);
  }

  /**
   * Make an RPC call.
   * @returns {Promise}
   */

  execute(name, params) {
    return super.execute('/', name, params);
  }

  /**
   * Create a wallet object.
   * @param {Number} id
   * @param {String} token
   */

  wallet(id, token) {
    return new Wallet(this, id, token);
  }

  /**
   * Join a wallet.
   * @param {String} token
   */

  all(token) {
    return this.call('join', '*', token);
  }

  /**
   * Leave a wallet.
   */

  none() {
    return this.call('leave', '*');
  }

  /**
   * Join a wallet.
   * @param {Number} id
   * @param {String} token
   */

  join(id, token) {
    return this.call('join', id, token);
  }

  /**
   * Leave a wallet.
   * @param {Number} id
   */

  leave(id) {
    return this.call('leave', id);
  }

  /**
   * Rescan the chain.
   * @param {Number} height
   * @returns {Promise}
   */

  rescan(height) {
    return this.post('/rescan', { height });
  }

  /**
   * Resend pending transactions.
   * @returns {Promise}
   */

  resend() {
    return this.post('/resend');
  }

  /**
   * Backup the walletdb.
   * @param {String} path
   * @returns {Promise}
   */

  backup(path) {
    return this.post('/backup', { path });
  }

  /**
   * Get list of all wallet IDs.
   * @returns {Promise}
   */

  getWallets() {
    return this.get('/wallet');
  }

  /**
   * Create a wallet.
   * @param {Number} id
   * @param {Object} options
   * @returns {Promise}
   */

  createWallet(id, options) {
    return this.put(`/wallet/${id}`, options);
  }

  /**
   * Get wallet transaction history.
   * @param {Number} id
   * @param {String} account
   * @returns {Promise}
   */

  getHistory(id, account) {
    return this.get(`/wallet/${id}/tx/history`, { account });
  }

  /**
   * Get wallet coins.
   * @param {Number} id
   * @param {String} account
   * @returns {Promise}
   */

  getCoins(id, account) {
    return this.get(`/wallet/${id}/coin`, { account });
  }

  /**
   * Get all unconfirmed transactions.
   * @param {Number} id
   * @param {String} account
   * @returns {Promise}
   */

  getPending(id, account) {
    return this.get(`/wallet/${id}/tx/unconfirmed`, { account });
  }

  /**
   * Calculate wallet balance.
   * @param {Number} id
   * @param {String} account
   * @returns {Promise}
   */

  getBalance(id, account) {
    return this.get(`/wallet/${id}/balance`, { account });
  }

  /**
   * Get last N wallet transactions.
   * @param {Number} id
   * @param {String} account
   * @param {Number} limit - Max number of transactions.
   * @returns {Promise}
   */

  getLast(id, account, limit) {
    return this.get(`/wallet/${id}/tx/last`, { account, limit });
  }

  /**
   * Get wallet transactions by timestamp range.
   * @param {Number} id
   * @param {String} account
   * @param {Object} options
   * @param {Number} options.start - Start time.
   * @param {Number} options.end - End time.
   * @param {Number?} options.limit - Max number of records.
   * @param {Boolean?} options.reverse - Reverse order.
   * @returns {Promise}
   */

  getRange(id, account, options) {
    return this.get(`/wallet/${id}/tx/range`, {
      account: account,
      start: options.start,
      end: options.end,
      limit: options.limit,
      reverse: options.reverse
    });
  }

  /**
   * Get transaction (only possible if the transaction
   * is available in the wallet history).
   * @param {Number} id
   * @param {Hash} hash
   * @returns {Promise}
   */

  getTX(id, hash) {
    return this.get(`/wallet/${id}/tx/${hash}`);
  }

  /**
   * Get wallet blocks.
   * @param {Number} id
   * @returns {Promise}
   */

  getBlocks(id) {
    return this.get(`/wallet/${id}/block`);
  }

  /**
   * Get wallet block.
   * @param {Number} id
   * @param {Number} height
   * @returns {Promise}
   */

  getBlock(id, height) {
    return this.get(`/wallet/${id}/block/${height}`);
  }

  /**
   * Get unspent coin (only possible if the transaction
   * is available in the wallet history).
   * @param {Number} id
   * @param {Hash} hash
   * @param {Number} index
   * @returns {Promise}
   */

  getCoin(id, hash, index) {
    return this.get(`/wallet/${id}/coin/${hash}/${index}`);
  }

  /**
   * @param {Number} id
   * @param {String} account
   * @param {Number} age - Age delta.
   * @returns {Promise}
   */

  zap(id, account, age) {
    return this.post(`/wallet/${id}/zap`, { account, age });
  }

  /**
   * @param {Number} id
   * @param {Hash} hash
   * @returns {Promise}
   */

  abandon(id, hash) {
    return this.del(`/wallet/${id}/tx/${hash}`);
  }

  /**
   * Create a transaction, fill.
   * @param {Number} id
   * @param {Object} options
   * @returns {Promise}
   */

  createTX(id, options) {
    return this.post(`/wallet/${id}/create`, options);
  }

  /**
   * Create a transaction, fill, sign, and broadcast.
   * @param {Number} id
   * @param {Object} options
   * @param {String} options.address
   * @param {Amount} options.value
   * @returns {Promise}
   */

  send(id, options) {
    return this.post(`/wallet/${id}/send`, options);
  }

  /**
   * Sign a transaction.
   * @param {Number} id
   * @param {Object} options
   * @returns {Promise}
   */

  sign(id, options) {
    return this.post(`/wallet/${id}/sign`, options);
  }

  /**
   * Get the raw wallet JSON.
   * @param {Number} id
   * @returns {Promise}
   */

  getInfo(id) {
    return this.get(`/wallet/${id}`);
  }

  /**
   * Get wallet accounts.
   * @param {Number} id
   * @returns {Promise} - Returns Array.
   */

  getAccounts(id) {
    return this.get(`/wallet/${id}/account`);
  }

  /**
   * Get wallet master key.
   * @param {Number} id
   * @returns {Promise}
   */

  getMaster(id) {
    return this.get(`/wallet/${id}/master`);
  }

  /**
   * Get wallet account.
   * @param {Number} id
   * @param {String} account
   * @returns {Promise}
   */

  getAccount(id, account) {
    return this.get(`/wallet/${id}/account/${account}`);
  }

  /**
   * Create account.
   * @param {Number} id
   * @param {String} name
   * @param {Object} options
   * @returns {Promise}
   */

  createAccount(id, name, options) {
    return this.put(`/wallet/${id}/account/${name}`, options);
  }

  /**
   * Create address.
   * @param {Number} id
   * @param {Object} options
   * @returns {Promise}
   */

  createAddress(id, account) {
    return this.post(`/wallet/${id}/address`, { account });
  }

  /**
   * Create change address.
   * @param {Number} id
   * @param {Object} options
   * @returns {Promise}
   */

  createChange(id, account) {
    return this.post(`/wallet/${id}/change`, { account });
  }

  /**
   * Create nested address.
   * @param {Number} id
   * @param {String} account
   * @returns {Promise}
   */

  createNested(id, account) {
    return this.post(`/wallet/${id}/nested`, { account });
  }

  /**
   * Change or set master key`s passphrase.
   * @param {Number} id
   * @param {String|Buffer} passphrase
   * @param {(String|Buffer)?} old
   * @returns {Promise}
   */

  setPassphrase(id, passphrase, old) {
    return this.post(`/wallet/${id}/passphrase`, { passphrase, old });
  }

  /**
   * Generate a new token.
   * @param {Number} id
   * @param {(String|Buffer)?} passphrase
   * @returns {Promise}
   */

  retoken(id, passphrase) {
    return this.post(`/wallet/${id}/retoken`, {
      passphrase
    });
  }

  /**
   * Import private key.
   * @param {Number} id
   * @param {String} account
   * @param {String} key
   * @returns {Promise}
   */

  importPrivate(id, account, privateKey, passphrase) {
    return this.post(`/wallet/${id}/import`, {
      account,
      privateKey,
      passphrase
    });
  }

  /**
   * Import public key.
   * @param {Number} id
   * @param {Number|String} account
   * @param {String} publicKey
   * @returns {Promise}
   */

  importPublic(id, account, publicKey) {
    return this.post(`/wallet/${id}/import`, {
      account,
      publicKey
    });
  }

  /**
   * Import address.
   * @param {Number} id
   * @param {String} account
   * @param {String} address
   * @returns {Promise}
   */

  importAddress(id, account, address) {
    return this.post(`/wallet/${id}/import`, { account, address });
  }

  /**
   * Lock a coin.
   * @param {String} hash
   * @param {Number} index
   * @returns {Promise}
   */

  lockCoin(id, hash, index) {
    return this.put(`/wallet/${id}/locked/${hash}/${index}`);
  }

  /**
   * Unlock a coin.
   * @param {Number} id
   * @param {String} hash
   * @param {Number} index
   * @returns {Promise}
   */

  unlockCoin(id, hash, index) {
    return this.del(`/wallet/${id}/locked/${hash}/${index}`);
  }

  /**
   * Get locked coins.
   * @param {Number} id
   * @returns {Promise}
   */

  getLocked(id) {
    return this.get(`/wallet/${id}/locked`);
  }

  /**
   * Lock wallet.
   * @param {Number} id
   * @returns {Promise}
   */

  lock(id) {
    return this.post(`/wallet/${id}/lock`);
  }

  /**
   * Unlock wallet.
   * @param {Number} id
   * @param {String} passphrase
   * @param {Number} timeout
   * @returns {Promise}
   */

  unlock(id, passphrase, timeout) {
    return this.post(`/wallet/${id}/unlock`, { passphrase, timeout });
  }

  /**
   * Get wallet key.
   * @param {Number} id
   * @param {String} address
   * @returns {Promise}
   */

  getKey(id, address) {
    return this.get(`/wallet/${id}/key/${address}`);
  }

  /**
   * Get wallet key WIF dump.
   * @param {Number} id
   * @param {String} address
   * @param {String?} passphrase
   * @returns {Promise}
   */

  getWIF(id, address, passphrase) {
    return this.get(`/wallet/${id}/wif/${address}`, { passphrase });
  }

  /**
   * Add a public account key to the wallet for multisig.
   * @param {Number} id
   * @param {String} account
   * @param {String} key - Account (bip44) key (base58).
   * @returns {Promise}
   */

  addSharedKey(id, account, accountKey) {
    return this.put(`/wallet/${id}/shared-key`, { account, accountKey });
  }

  /**
   * Remove a public account key to the wallet for multisig.
   * @param {Number} id
   * @param {String} account
   * @param {String} accountKey - Account (bip44) key (base58).
   * @returns {Promise}
   */

  removeSharedKey(id, account, accountKey) {
    return this.del(`/wallet/${id}/shared-key`, { account, accountKey });
  }

  /**
   * Resend wallet transactions.
   * @param {Number} id
   * @returns {Promise}
   */

  resendWallet(id) {
    return this.post(`/wallet/${id}/resend`);
  }
}

/**
 * Wallet Instance
 * @extends {EventEmitter}
 */

class Wallet extends EventEmitter {
  /**
   * Create a wallet client.
   * @param {Object?} options
   */

  constructor(parent, id, token) {
    super();
    this.parent = parent;
    this.client = parent.clone();
    this.client.token = token;
    this.id = id;
    this.token = token;
  }

  /**
   * Open wallet.
   * @returns {Promise}
   */

  async open() {
    await this.parent.join(this.id, this.token);
    this.parent.wallets.set(this.id, this);
  }

  /**
   * Close wallet.
   * @returns {Promise}
   */

  async close() {
    await this.parent.leave(this.id);
    this.parent.wallets.delete(this.id);
  }

  /**
   * Get wallet transaction history.
   * @param {String} account
   * @returns {Promise}
   */

  getHistory(account) {
    return this.client.getHistory(this.id, account);
  }

  /**
   * Get wallet coins.
   * @param {String} account
   * @returns {Promise}
   */

  getCoins(account) {
    return this.client.getCoins(this.id, account);
  }

  /**
   * Get all unconfirmed transactions.
   * @param {String} account
   * @returns {Promise}
   */

  getPending(account) {
    return this.client.getPending(this.id, account);
  }

  /**
   * Calculate wallet balance.
   * @param {String} account
   * @returns {Promise}
   */

  getBalance(account) {
    return this.client.getBalance(this.id, account);
  }

  /**
   * Get last N wallet transactions.
   * @param {String} account
   * @param {Number} limit - Max number of transactions.
   * @returns {Promise}
   */

  getLast(account, limit) {
    return this.client.getLast(this.id, account, limit);
  }

  /**
   * Get wallet transactions by timestamp range.
   * @param {String} account
   * @param {Object} options
   * @param {Number} options.start - Start time.
   * @param {Number} options.end - End time.
   * @param {Number?} options.limit - Max number of records.
   * @param {Boolean?} options.reverse - Reverse order.
   * @returns {Promise}
   */

  getRange(account, options) {
    return this.client.getRange(this.id, account, options);
  }

  /**
   * Get transaction (only possible if the transaction
   * is available in the wallet history).
   * @param {Hash} hash
   * @returns {Promise}
   */

  getTX(hash) {
    return this.client.getTX(this.id, hash);
  }

  /**
   * Get wallet blocks.
   * @param {Number} height
   * @returns {Promise}
   */

  getBlocks() {
    return this.client.getBlocks(this.id);
  }

  /**
   * Get wallet block.
   * @param {Number} height
   * @returns {Promise}
   */

  getBlock(height) {
    return this.client.getBlock(this.id, height);
  }

  /**
   * Get unspent coin (only possible if the transaction
   * is available in the wallet history).
   * @param {Hash} hash
   * @param {Number} index
   * @returns {Promise}
   */

  getCoin(hash, index) {
    return this.client.getCoin(this.id, hash, index);
  }

  /**
   * @param {String} account
   * @param {Number} age - Age delta.
   * @returns {Promise}
   */

  zap(account, age) {
    return this.client.zap(this.id, account, age);
  }

  /**
   * Used to remove a pending transaction from the wallet.
   * That is likely the case if it has a policy or low fee
   * that prevents it from proper network propagation.
   * @param {Hash} hash
   * @returns {Promise}
   */

  abandon(hash) {
    return this.client.abandon(this.id, hash);
  }

  /**
   * Create a transaction, fill.
   * @param {Object} options
   * @returns {Promise}
   */

  createTX(options) {
    return this.client.createTX(this.id, options);
  }

  /**
   * Create a transaction, fill, sign, and broadcast.
   * @param {Object} options
   * @param {String} options.address
   * @param {Amount} options.value
   * @returns {Promise}
   */

  send(options) {
    return this.client.send(this.id, options);
  }

  /**
   * Sign a transaction.
   * @param {Object} options
   * @returns {Promise}
   */

  sign(options) {
    return this.client.sign(this.id, options);
  }

  /**
   * Get the raw wallet JSON.
   * @returns {Promise}
   */

  getInfo() {
    return this.client.getInfo(this.id);
  }

  /**
   * Get wallet accounts.
   * @returns {Promise} - Returns Array.
   */

  getAccounts() {
    return this.client.getAccounts(this.id);
  }

  /**
   * Get wallet master key.
   * @returns {Promise}
   */

  getMaster() {
    return this.client.getMaster(this.id);
  }

  /**
   * Get wallet account.
   * @param {String} account
   * @returns {Promise}
   */

  getAccount(account) {
    return this.client.getAccount(this.id, account);
  }

  /**
   * Create account.
   * @param {String} name
   * @param {Object} options
   * @returns {Promise}
   */

  createAccount(name, options) {
    return this.client.createAccount(this.id, name, options);
  }

  /**
   * Create address.
   * @param {String} account
   * @returns {Promise}
   */

  createAddress(account) {
    return this.client.createAddress(this.id, account);
  }

  /**
   * Create change address.
   * @param {String} account
   * @returns {Promise}
   */

  createChange(account) {
    return this.client.createChange(this.id, account);
  }

  /**
   * Create nested address.
   * @param {String} account
   * @returns {Promise}
   */

  createNested(account) {
    return this.client.createNested(this.id, account);
  }

  /**
   * Change or set master key`s passphrase.
   * @param {String|Buffer} passphrase
   * @param {(String|Buffer)?} old
   * @returns {Promise}
   */

  setPassphrase(passphrase, old) {
    return this.client.setPassphrase(this.id, passphrase, old);
  }

  /**
   * Generate a new token.
   * @param {(String|Buffer)?} passphrase
   * @returns {Promise}
   */

  async retoken(passphrase) {
    const result = await this.client.retoken(this.id, passphrase);

    assert(result);
    assert(typeof result.token === 'string');

    this.token = result.token;

    return result;
  }

  /**
   * Import private key.
   * @param {Number|String} account
   * @param {String} privateKey
   * @param {String} passphrase
   * @returns {Promise}
   */

  importPrivate(account, privateKey, passphrase) {
    return this.client.importPrivate(this.id, account, privateKey, passphrase);
  }

  /**
   * Import public key.
   * @param {Number|String} account
   * @param {String} publicKey
   * @returns {Promise}
   */

  importPublic(account, publicKey) {
    return this.client.importPublic(this.id, account, publicKey);
  }

  /**
   * Import address.
   * @param {Number|String} account
   * @param {String} address
   * @returns {Promise}
   */

  importAddress(account, address) {
    return this.client.importAddress(this.id, account, address);
  }

  /**
   * Lock a coin.
   * @param {String} hash
   * @param {Number} index
   * @returns {Promise}
   */

  lockCoin(hash, index) {
    return this.client.lockCoin(this.id, hash, index);
  }

  /**
   * Unlock a coin.
   * @param {String} hash
   * @param {Number} index
   * @returns {Promise}
   */

  unlockCoin(hash, index) {
    return this.client.unlockCoin(this.id, hash, index);
  }

  /**
   * Get locked coins.
   * @returns {Promise}
   */

  getLocked() {
    return this.client.getLocked(this.id);
  }

  /**
   * Lock wallet.
   * @returns {Promise}
   */

  lock() {
    return this.client.lock(this.id);
  }

  /**
   * Unlock wallet.
   * @param {String} passphrase
   * @param {Number} timeout
   * @returns {Promise}
   */

  unlock(passphrase, timeout) {
    return this.client.unlock(this.id, passphrase, timeout);
  }

  /**
   * Get wallet key.
   * @param {String} address
   * @returns {Promise}
   */

  getKey(address) {
    return this.client.getKey(this.id, address);
  }

  /**
   * Get wallet key WIF dump.
   * @param {String} address
   * @param {String?} passphrase
   * @returns {Promise}
   */

  getWIF(address, passphrase) {
    return this.client.getWIF(this.id, address, passphrase);
  }

  /**
   * Add a public account key to the wallet for multisig.
   * @param {String} account
   * @param {String} accountKey - Account (bip44) key (base58).
   * @returns {Promise}
   */

  addSharedKey(account, accountKey) {
    return this.client.addSharedKey(this.id, account, accountKey);
  }

  /**
   * Remove a public account key to the wallet for multisig.
   * @param {String} account
   * @param {String} accountKey - Account (bip44) key (base58).
   * @returns {Promise}
   */

  removeSharedKey(account, accountKey) {
    return this.client.removeSharedKey(this.id, account, accountKey);
  }

  /**
   * Resend wallet transactions.
   * @returns {Promise}
   */

  resend() {
    return this.client.resendWallet(this.id);
  }
}

/*
 * Expose
 */

module.exports = WalletClient;