diff --git a/bin/bcoin-cli b/bin/bcoin-cli index fd1b0958..2bf3ece2 100755 --- a/bin/bcoin-cli +++ b/bin/bcoin-cli @@ -25,6 +25,9 @@ function getID() { function createWallet(callback) { var options = { id: getID() }; + if (argv.type) + options.type = argv.type; + if (argv.master) options.master = argv.master; @@ -61,6 +64,36 @@ function createWallet(callback) { }); } +function addKey(callback) { + var id = getID(); + var keys = argv.keys || argv.key; + if (keys) + keys = keys.split(','); + else + keys = argv.args; + client.addKey(id, keys, function(err, wallet) { + if (err) + return callback(err); + utils.print('added'); + callback(); + }); +} + +function removeKey(callback) { + var id = getID(); + var keys = argv.keys || argv.key; + if (keys) + keys = keys.split(','); + else + keys = argv.args; + client.removeKey(id, keys, function(err) { + if (err) + return callback(err); + utils.print('removed'); + callback(); + }); +} + function getWallet(callback) { var options = { id: getID(), passphrase: argv.args[0] }; client.getWallet(options.id, options.passphrase, function(err, wallet) { @@ -188,6 +221,10 @@ function main(callback) { return createWallet(callback); case 'getwallet': return getWallet(callback); + case 'addkey': + return addKey(callback); + case 'rmkey': + return removeKey(callback); case 'balance': return getBalance(callback); case 'history': @@ -204,8 +241,25 @@ function main(callback) { return getCoin(callback); case 'block': return getBlock(callback); + default: + utils.print('Unrecognized command.'); + utils.print('Commands:'); + utils.print(' $ wallet [id] --keys [hdkeys]' + + ' --type [pubkeyhash/multisig] -m [m-value] + + ' -n [n-value] --witness: View or create wallet by ID.'); + utils.print(' $ getwallet [id]: View wallet by ID.'); + utils.print(' $ addkey [id] --keys [hdkeys]: Add keys to wallet.'); + utils.print(' $ rmkey [id] --keys [hdkeys]: Remove keys from wallet.'); + utils.print(' $ balance [id]: Get wallet balance.'); + utils.print(' $ history [id]: View wallet TX history.'); + utils.print(' $ send [id] [address] [value] --script [code]: Send transaction.'); + utils.print(' $ zap [id] --now [now] --age [age]: Zap pending wallet TXs.'); + utils.print(' $ mempool: Get mempool snapshot.'); + utils.print(' $ tx [hash/address]: View transactions.'); + utils.print(' $ coin [hash+index/address]: View coins.'); + utils.print(' $ block [hash/height]: View block.'); + return callback(); } - return callback(new Error('Bad command.')); } function parseArg(argv) { diff --git a/lib/bcoin/http/client.js b/lib/bcoin/http/client.js index 40390c78..adb71710 100644 --- a/lib/bcoin/http/client.js +++ b/lib/bcoin/http/client.js @@ -591,6 +591,40 @@ Client.prototype.zapWallet = function zapWallet(id, now, age, callback) { }); }; +Client.prototype.addKey = function addKey(id, keys, callback) { + if (!Array.isArray(keys)) + keys = [keys]; + + keys = keys.map(function(key) { + return key || key.xpubkey; + }); + + callback = utils.ensure(callback); + + return this._put('/wallet/' + id + '/key', keys, function(err) { + if (err) + return callback(err); + return callback(); + }); +}; + +Client.prototype.removeKey = function removeKey(id, keys, callback) { + if (!Array.isArray(keys)) + keys = [keys]; + + keys = keys.map(function(key) { + return key || key.xpubkey; + }); + + callback = utils.ensure(callback); + + return this._del('/wallet/' + id + '/key', keys, function(err) { + if (err) + return callback(err); + return callback(); + }); +}; + Client.prototype.getMempool = function getMempool(callback) { return this._get('/mempool', function(err, body) { if (err) diff --git a/lib/bcoin/http/server.js b/lib/bcoin/http/server.js index eb385d7f..38ea3493 100644 --- a/lib/bcoin/http/server.js +++ b/lib/bcoin/http/server.js @@ -121,6 +121,16 @@ NodeServer.prototype._init = function _init() { if (params.age) options.age = params.age >>> 0; + if (params.key) + params.keys = params.key; + + if (params.keys) { + if (typeof params.keys === 'string') + options.keys = params.keys.split(','); + else + options.keys = params.keys; + } + if (params.passphrase) options.passphrase = params.passphrase; @@ -332,6 +342,29 @@ NodeServer.prototype._init = function _init() { }); }); + // Add key + this.put('/wallet/:id/key', function(req, res, next, send) { + self.walletdb.addKey(req.options.id, req.options.keys, function(err) { + if (err) + return next(err); + + send(200, { success: true }); + }); + }); + + // Remove key + this.del('/wallet/:id/key', function(req, res, next, send) { + self.walletdb.removeKey(req.options.id, req.options.keys, function(err) { + if (err) + return next(err); + + if (!json) + return send(404); + + send(200, { success: true }); + }); + }); + // Wallet Balance this.get('/wallet/:id/balance', function(req, res, next, send) { self.walletdb.getBalance(req.options.id, function(err, balance) { diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 4a5243df..6cf17d59 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -127,10 +127,14 @@ Wallet.prototype._init = function _init() { this.provider.setID(this.id); - this.provider.on('error', function(err) { + this.on('error', function(err) { utils.debug('Wallet Error: %s', err.message); }); + this.provider.on('error', function(err) { + self.emit('error', err); + }); + this.provider.on('tx', function(tx) { self.emit('tx', tx); }); @@ -1019,16 +1023,22 @@ Wallet.prototype.inspect = function inspect() { network: network.type, m: this.m, n: this.n, - keyAddress: this.keyAddress, - scriptAddress: this.scriptAddress, - programAddress: this.programAddress, + keyAddress: this._initialized + ? this.keyAddress + : null, + scriptAddress: this._initialized + ? this.scriptAddress + : null, + programAddress: this._initialized + ? this.programAddress + : null, witness: this.witness, derivation: this.derivation, copayBIP45: this.copayBIP45, accountIndex: this.accountIndex, receiveDepth: this.receiveDepth, changeDepth: this.changeDepth, - master: this.master.toJSON(this.options.passphrase), + master: this.master.toJSON(), accountKey: this.accountKey.xpubkey, addressMap: this.addressMap, keys: this.keys.map(function(key) { @@ -1092,7 +1102,7 @@ Wallet.fromJSON = function fromJSON(json, passphrase) { // For updating the address table quickly // without decrypting the master key. -Wallet._syncDepth = function sync(json, options) { +Wallet._syncDepth = function _syncDepth(json, options) { var master, wallet; var res = false; @@ -1156,6 +1166,41 @@ Wallet.setDepth = function setDepth(json, receiveDepth, changeDepth) { }); }; +Wallet._addKey = function _addKey(json, keys, remove) { + var master, wallet; + + assert.equal(json.v, 3); + assert.equal(json.name, 'wallet'); + + if (!Array.isArray(keys)) + keys = [keys]; + + if (json.network) + assert.equal(json.network, network.type); + + master = json.master; + json.master = json.accountKey; + wallet = new Wallet(json); + keys.forEach(function(key) { + if (remove) + wallet.removeKey(key); + else + wallet.addKey(key); + }); + wallet = wallet.toJSON(); + wallet.master = master; + + return wallet; +}; + +Wallet.addKey = function addKey(json, keys) { + return Wallet._addKey(json, keys, false); +}; + +Wallet.removeKey = function removeKey(json, keys) { + return Wallet._addKey(json, keys, true); +}; + Wallet.isWallet = function isWallet(obj) { return obj && typeof obj.receiveDepth === 'number' diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index e0b5076e..d6146679 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -240,6 +240,44 @@ WalletDB.prototype.setDepth = function setDepth(id, receive, change, callback) { }); }; +WalletDB.prototype.addKey = function addKey(id, key, callback) { + var self = this; + + callback = utils.ensure(callback); + + this.getJSON(id, function(err, json) { + if (err) + return callback(err); + + try { + json = bcoin.wallet.addKey(json, key); + } catch (e) { + return callback(e); + } + + self.saveJSON(id, json, callback); + }); +}; + +WalletDB.prototype.removeKey = function removeKey(id, key, callback) { + var self = this; + + callback = utils.ensure(callback); + + this.getJSON(id, function(err, json) { + if (err) + return callback(err); + + try { + json = bcoin.wallet.removeKey(json, key); + } catch (e) { + return callback(e); + } + + self.saveJSON(id, json, callback); + }); +}; + WalletDB.prototype.getJSON = function getJSON(id, callback) { if (typeof id === 'object') id = id.id;