From 2b630ad99cb4fa672579c629cc1932f5c814a1b5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 13 Aug 2016 00:10:22 -0700 Subject: [PATCH] http: api refactor. --- bin/bcoin | 15 +- bin/cli | 735 ++++++++++++++++++++---------------- lib/bcoin/config.js | 22 +- lib/bcoin/http/client.js | 367 ++---------------- lib/bcoin/http/rpc.js | 4 +- lib/bcoin/http/rpcclient.js | 95 +++++ lib/bcoin/http/server.js | 133 +++---- lib/bcoin/http/wallet.js | 51 ++- lib/bcoin/wallet.js | 54 +++ lib/bcoin/walletdb.js | 6 +- test/http-test.js | 22 +- 11 files changed, 718 insertions(+), 786 deletions(-) create mode 100644 lib/bcoin/http/rpcclient.js diff --git a/bin/bcoin b/bin/bcoin index bf6e28f5..811370f0 100755 --- a/bin/bcoin +++ b/bin/bcoin @@ -2,7 +2,6 @@ rl=0 daemon=0 -cli=0 cmd='node' if ! type perl > /dev/null 2>& 1; then @@ -25,7 +24,14 @@ dir=$(dirname "$file") if test x"$1" = x'cli'; then shift - cli=1 + exec "${dir}/cli" "$@" + exit 1 +fi + +if test x"$1" = x'rpc'; then + shift + exec "${dir}/cli" rpc "$@" + exit 1 fi for arg in "$@"; do @@ -39,11 +45,6 @@ for arg in "$@"; do esac done -if test $cli -eq 1; then - exec "${dir}/cli" "$@" - exit 1 -fi - if test $daemon -eq 1; then if ! type setsid > /dev/null 2>& 1; then echo 'BCoin requires setsid to start as a daemon.' >& 2 diff --git a/bin/cli b/bin/cli index 66209d62..47101da5 100755 --- a/bin/cli +++ b/bin/cli @@ -2,487 +2,564 @@ 'use strict'; -var argv = parseArg(process.argv); - -var bcoin = require('../').set(argv.network); -var network = bcoin.network.get(); -var utils = bcoin.utils; +var config = require('../lib/bcoin/config'); +var utils = require('../lib/bcoin/utils'); +var Client = require('../lib/bcoin/http/client'); +var Wallet = require('../lib/bcoin/http/wallet'); var assert = utils.assert; -var Client = bcoin.http.client; -var client = new Client( - argv.url - || process.env.BCOIN_URL - || 'http://localhost:' + network.rpcPort -); - -client.on('error', function(err) { - ; -}); - -function getID() { - if (argv.id) - return argv.id; - if (process.env.BCOIN_WALLET_ID) - return process.env.BCOIN_WALLET_ID; - return argv.args.shift(); +function CLI() { + this.config = config({ + config: true, + arg: true, + env: true, + network: 'main' + }).data; + this.argv = this.config.args; + this.client = null; + this.wallet = null; } -function createWallet(callback) { - var options = { id: getID() }; +CLI.prototype.log = function log(json) { + if (typeof json === 'string') + return console.log.apply(console, arguments); + console.log(JSON.stringify(json, null, 2)); +}; - if (argv.type) - options.type = argv.type; +CLI.prototype.createWallet = function createWallet(callback) { + var options = { id: this.config.id }; - if (argv.master) - options.master = argv.master; + if (this.config.type) + options.type = this.config.type; - if (argv.keys) - options.keys = argv.keys.split(/[,:]/); + if (this.config.master) + options.master = this.config.master; - if (argv.lookahead) - options.lookahead = argv.lookahead >>> 0; + if (this.config.keys) + options.keys = this.config.keys.split(/[,:]/); - if (argv.m) - options.m = argv.m >>> 0; + if (this.config.m) + options.m = this.config.m >>> 0; - if (argv.n) - options.n = argv.n >>> 0; + if (this.config.n) + options.n = this.config.n >>> 0; - if (argv.witness != null) - options.witness = !!argv.witness; + if (this.config.witness != null) + options.witness = !!this.config.witness; - if (argv.passphrase) - options.passphrase = argv.passphrase; + if (this.config.passphrase) + options.passphrase = this.config.passphrase; - client.createWallet(options, function(err, wallet) { + this.client.createWallet(options, function(err, wallet) { if (err) return callback(err); - utils.log(wallet); + self.log(wallet); callback(); }); -} +}; -function addKey(callback) { - var id = getID(); - var keys = argv.keys || argv.key; +CLI.prototype.addKey = function addKey(callback) { + var self = this; + var keys = this.config.keys || this.config.key; if (keys) keys = keys.split(','); else - keys = argv.args; - client.addKey(id, argv.account, keys, function(err, wallet) { + keys = this.argv; + this.wallet.addKey(this.config.account, keys, function(err, wallet) { if (err) return callback(err); - utils.log('added'); + self.log('added'); callback(); }); -} +}; -function createAccount(callback) { - var id = getID(); - var account = argv.args[0] || argv.account; - client.createWalletAccount(id, account, function(err, account) { +CLI.prototype.createAccount = function createAccount(callback) { + var self = this; + var account = this.argv[0] || this.config.account; + this.wallet.createAccount(account, function(err, account) { if (err) return callback(err); - utils.log(account); + self.log(account); callback(); }); -} +}; -function getAccounts(callback) { - var id = getID(); - client.getWalletAccounts(id, function(err, accounts) { +CLI.prototype.getAccount = function getAccount(callback) { + var self = this; + var account = this.argv[0] || this.config.account; + this.wallet.getAccount(account, function(err, account) { if (err) return callback(err); - utils.log(accounts); + self.log(account); callback(); }); -} +}; -function removeKey(callback) { - var id = getID(); - var keys = argv.keys || argv.key; +CLI.prototype.getAccounts = function getAccounts(callback) { + var self = this; + this.wallet.getAccounts(function(err, accounts) { + if (err) + return callback(err); + self.log(accounts); + callback(); + }); +}; + +CLI.prototype.removeKey = function removeKey(callback) { + var self = this; + var keys = this.config.keys || this.config.key; if (keys) keys = keys.split(','); else - keys = argv.args; - client.removeKey(id, argv.account, keys, function(err) { + keys = this.argv; + this.wallet.removeKey(this.config.account, keys, function(err) { if (err) return callback(err); - utils.log('removed'); + self.log('removed'); callback(); }); -} +}; -function getWallet(callback) { - var id = getID(); - var passphrase = argv.args[0]; - client.getWallet(id, argv.account, passphrase, function(err, wallet) { +CLI.prototype.getWallet = function getWallet(callback) { + var self = this; + var passphrase = this.argv[0]; + this.wallet.getInfo(this.config.account, passphrase, function(err, wallet) { if (err) return callback(err); - utils.log(wallet); - wallet.destroy(); + self.log(wallet); callback(); }); -} +}; -function getTX(callback) { - var hash = argv.args[0]; - if (bcoin.address.validate(hash)) { - return client.getTXByAddress(hash, function(err, txs) { +CLI.prototype.getTX = function getTX(callback) { + var self = this; + var hash = this.argv[0]; + if (utils.isBase58(hash)) { + return this.client.getTXByAddress(hash, function(err, txs) { if (err) return callback(err); - utils.log(txs); + self.log(txs); callback(); }); } - hash = utils.revHex(hash); - client.getTX(hash, function(err, tx) { + this.client.getTX(hash, function(err, tx) { if (err) return callback(err); if (!tx) { - utils.log('TX not found.'); + self.log('TX not found.'); return callback(); } - utils.log(tx); + self.log(tx); callback(); }); -} +}; -function getBlock(callback) { - var hash = argv.args[0]; +CLI.prototype.getBlock = function getBlock(callback) { + var self = this; + var hash = this.argv[0]; if (hash.length !== 64) hash = +hash; - else - hash = utils.revHex(hash); - client.getBlock(hash, function(err, block) { + this.client.getBlock(hash, function(err, block) { if (err) return callback(err); if (!block) { - utils.log('Block not found.'); + self.log('Block not found.'); return callback(); } - utils.log(block); - utils.log('Coinbase Data:'); - utils.log(block.txs[0].inputs[0].script.getCoinbaseFlags()); + self.log(block); callback(); }); -} +}; -function getCoin(callback) { - var hash = argv.args[0]; - var index = argv.args[1]; - if (bcoin.address.validate(hash)) { - return client.getCoinsByAddress(hash, function(err, coins) { +CLI.prototype.getCoin = function getCoin(callback) { + var self = this; + var hash = this.argv[0]; + var index = this.argv[1]; + if (utils.isBase58(hash)) { + return this.client.getCoinsByAddress(hash, function(err, coins) { if (err) return callback(err); - utils.log(coins); + self.log(coins); callback(); }); } - hash = utils.revHex(hash); - client.getCoin(hash, index, function(err, coin) { + this.client.getCoin(hash, index, function(err, coin) { if (err) return callback(err); if (!coin) { - utils.log('Coin not found.'); + self.log('Coin not found.'); return callback(); } - utils.log(coin); + self.log(coin); callback(); }); -} +}; -function getWalletHistory(callback) { - var id = getID(); - client.getWalletHistory(id, argv.account, function(err, txs) { +CLI.prototype.getWalletHistory = function getWalletHistory(callback) { + var self = this; + this.wallet.getHistory(this.config.account, function(err, txs) { if (err) return callback(err); - utils.log(txs); + self.log(txs); callback(); }); -} +}; -function listenWallet(callback) { - var id = getID(); - client.join(id); - client.on('tx', function(tx, map) { - utils.log('TX:'); - utils.log(tx); - utils.log(map); +CLI.prototype.listenWallet = function listenWallet(callback) { + var self = this; + this.wallet.on('tx', function(details) { + self.log('TX:'); + self.log(details); }); - client.on('updated', function(tx, map) { - utils.log('TX updated:'); - utils.log(tx); - utils.log(map); + this.wallet.on('confirmed', function(details) { + self.log('TX confirmed:'); + self.log(details); }); - client.on('confirmed', function(tx, map) { - utils.log('TX updated:'); - utils.log(tx); - utils.log(map); + this.wallet.on('unconfirmed', function(details) { + self.log('TX unconfirmed:'); + self.log(details); }); - client.on('address', function(receive, change) { - utils.log('New addresses allocated:'); - utils.log(receive); - utils.log(change); + this.wallet.on('conflict', function(details) { + self.log('TX conflict:'); + self.log(details); }); - client.on('balance', function(tx, map) { - utils.log('Balance:'); - utils.log(tx); - utils.log(map); + this.wallet.on('address', function(receive) { + self.log('New addresses allocated:'); + self.log(receive); }); -} + this.wallet.on('balance', function(balance) { + self.log('Balance:'); + self.log(balance); + }); +}; -function getBalance(callback) { - var id = getID(); - client.getWalletBalance(id, argv.account, function(err, balance) { +CLI.prototype.getBalance = function getBalance(callback) { + var self = this; + this.wallet.getBalance(this.config.account, function(err, balance) { if (err) return callback(err); - utils.log('Confirmed: %s', utils.btc(balance.confirmed)); - utils.log('Unconfirmed: %s', utils.btc(balance.unconfirmed)); - utils.log('Total: %s', utils.btc(balance.total)); + self.log(balance); callback(); }); -} +}; -function getMempool(callback) { - client.getMempool(function(err, txs) { +CLI.prototype.getMempool = function getMempool(callback) { + var self = this; + this.client.getMempool(function(err, txs) { if (err) return callback(err); - utils.log(txs); + self.log(txs); callback(); }); -} +}; -function sendTX(callback) { - var id = getID(); - var options = { account: argv.account, passphrase: argv.passphrase }; +CLI.prototype.sendTX = function sendTX(callback) { + var self = this; var output = {}; - if (argv.script) { - output.script = new bcoin.script(new Buffer(argv.script, 'hex')); - output.value = utils.satoshi(argv.value || argv.args[0]); + if (this.config.script) { + output.script = this.config.script; + output.value = utils.satoshi(this.config.value || this.argv[0]); } else { - output.address = argv.address || argv.args[0]; - output.value = utils.satoshi(argv.value || argv.args[1]); + output.address = this.config.address || this.argv[0]; + output.value = utils.satoshi(this.config.value || this.argv[1]); } - client.walletSend(id, {outputs:[output]}, function(err, tx) { + var options = { + account: this.config.account, + passphrase: this.config.passphrase, + outputs: [output] + }; + this.wallet.send(options, function(err, tx) { if (err) return callback(err); - utils.log(tx); - utils.log(tx.toRaw('hex')); + self.log(tx); callback(); }); -} +}; -function createTX(callback) { - var id = getID(); - var options = { account: argv.account, passphrase: argv.passphrase }; +CLI.prototype.createTX = function createTX(callback) { + var self = this; + var options = { account: this.config.account, passphrase: this.config.passphrase }; var output = {}; - if (argv.script) { - output.script = new bcoin.script(new Buffer(argv.script, 'hex')); - output.value = utils.satoshi(argv.value || argv.args[0]); + if (this.config.script) { + output.script = this.config.script; + output.value = utils.satoshi(this.config.value || this.argv[0]); } else { - output.address = argv.address || argv.args[0]; - output.value = utils.satoshi(argv.value || argv.args[1]); + output.address = this.config.address || this.argv[0]; + output.value = utils.satoshi(this.config.value || this.argv[1]); } - client.walletCreate(id, options, [output], function(err, tx) { + options.outputs = [output]; + this.wallet.createTX(options, function(err, tx) { if (err) return callback(err); - utils.log(tx); - utils.log(tx.toRaw('hex')); + self.log(tx); callback(); }); -} +}; -function signTX(callback) { - var id = getID(); - var options = { passphrase: argv.passphrase }; - var tx = bcoin.tx.fromRaw(options.tx || argv.args[0], 'hex'); - client.walletSign(id, tx, options, function(err, tx) { +CLI.prototype.signTX = function signTX(callback) { + var self = this; + var options = { passphrase: this.config.passphrase }; + var tx = options.tx || this.argv[0]; + this.wallet.sign(tx, options, function(err, tx) { if (err) return callback(err); - utils.log(tx); - utils.log(tx.toRaw('hex')); + self.log(tx); callback(); }); -} +}; -function zap(callback) { - var id = getID(); - var age = (argv.age >>> 0) || 72 * 60 * 60; - client.walletZap(id, argv.account, age, function(err) { +CLI.prototype.zap = function zap(callback) { + var self = this; + var age = (this.config.age >>> 0) || 72 * 60 * 60; + this.wallet.zap(this.config.account, age, function(err) { if (err) return callback(err); - utils.log('Zapped!'); + self.log('Zapped!'); callback(); }); -} +}; -function broadcast(callback) { - var tx = bcoin.tx.fromRaw(argv.args[0] || argv.tx, 'hex'); - client.broadcast(tx, function(err, tx) { +CLI.prototype.broadcast = function broadcast(callback) { + var self = this; + var tx = this.argv[0] || this.config.tx; + this.client.broadcast(tx, function(err, tx) { if (err) return callback(err); - utils.log('Broadcasted:'); - utils.log(tx); + self.log('Broadcasted:'); + self.log(tx); callback(); }); -} +}; -function view(callback) { - var tx = bcoin.tx.fromRaw(argv.args[0] || argv.tx, 'hex'); - client.walletFill(tx, function(err, tx) { +CLI.prototype.viewTX = function viewTX(callback) { + var self = this; + var tx = this.argv[0] || this.config.tx; + this.wallet.fill(tx, function(err, tx) { if (err) return callback(err); - utils.log(tx); + self.log(tx); callback(); }); -} +}; + +CLI.prototype.getDetails = function getDetails(callback) { + var self = this; + var hash = this.argv[0]; + this.wallet.getTX(hash, function(err, tx) { + if (err) + return callback(err); + self.log(tx); + callback(); + }); +}; + +CLI.prototype.rpc = function rpc(callback) { + var self = this; + var method = this.argv.shift(); + var params = []; + var i, arg, param; + + for (i = 0; i < this.argv.length; i++) { + arg = this.argv[i]; + try { + param = JSON.parse(arg); + } catch (e) { + param = arg; + } + params.push(param); + } + + this.client.rpc.call(method, params, function(err, result) { + if (err) + return callback(err); + self.log(result); + callback(); + }); +}; + +CLI.prototype.handleWallet = function handleWallet(callback) { + var self = this; + + var options = { + id: this.config.id, + token: this.config.token + }; + + this.wallet = new Wallet({ + uri: this.config.url || this.config.uri, + apiKey: this.config.apiKey + }); + + this.wallet.open(options, function(err) { + if (err) + return callback(err); + + switch (self.argv.shift()) { + case 'listen': + return self.listenWallet(callback); + case 'get': + return self.getWallet(callback); + case 'addkey': + return self.addKey(callback); + case 'rmkey': + return self.removeKey(callback); + case 'balance': + return self.getBalance(callback); + case 'history': + return self.getWalletHistory(callback); + case 'account': + if (self.argv[0] === 'create') { + self.argv.shift(); + return self.createAccount(callback); + } + return self.getAccount(callback); + case 'accounts': + return self.getAccounts(callback); + case 'sign': + return self.signTX(callback); + case 'mktx': + return self.createTX(callback); + case 'send': + return self.sendTX(callback); + case 'zap': + return self.zap(callback); + case 'tx': + return self.getDetails(callback); + case 'view': + return self.viewTX(callback); + default: + self.log('Unrecognized command.'); + self.log('Commands:'); + self.log(' $ wallet [id] --keys [hdkeys]' + + ' --type [pubkeyhash/multisig] -m [m-value]' + + ' -n [n-value] --witness: View or create wallet by ID.'); + self.log(' $ listen [id]: Listen for wallet events.'); + self.log(' $ getwallet [id]: View wallet by ID.'); + self.log(' $ addkey [id] --keys [hdkeys]: Add keys to wallet.'); + self.log(' $ rmkey [id] --keys [hdkeys]: Remove keys from wallet.'); + self.log(' $ balance [id]: Get wallet balance.'); + self.log(' $ history [id]: View wallet TX history.'); + self.log(' $ accounts [id]: List account names.'); + self.log(' $ account [id] [acct]: Get account details.'); + self.log(' $ send [id] [address] [value] --script [code]: Send transaction.'); + self.log(' $ create [id] [address] [value] --script [code]: Create transaction.'); + self.log(' $ sign [id] [tx-hex]: Sign transaction.'); + self.log(' $ zap [id] --age [age]: Zap pending wallet TXs.'); + self.log(' $ broadcast [tx-hex]: Broadcast transaction.'); + self.log(' $ view [tx-hex]: View transaction.'); + self.log(' $ mempool: Get mempool snapshot.'); + self.log(' $ tx [hash/address]: View transactions.'); + self.log(' $ coin [hash+index/address]: View coins.'); + self.log(' $ block [hash/height]: View block.'); + self.log('Other Options:'); + self.log(' --passphrase [passphrase]: For signing and account creation.'); + self.log(' --account [acctname]: Account name.'); + return callback(); + } + }); +}; + +CLI.prototype.handleNode = function handleNode(callback) { + var self = this; + + this.client = new Client({ + uri: this.config.url || this.config.uri, + apiKey: this.config.apiKey + }); + + this.client.getInfo(function(err, info) { + if (err) + return callback(err); + + switch (self.argv.shift()) { + case 'mkwallet': + return self.createWallet(callback); + case 'broadcast': + return self.broadcast(callback); + case 'mempool': + return self.getMempool(callback); + case 'tx': + return self.getTX(callback); + case 'coin': + return self.getCoin(callback); + case 'block': + return self.getBlock(callback); + case 'rpc': + return self.rpc(callback); + default: + self.log('Unrecognized command.'); + self.log('Commands:'); + self.log(' $ wallet [id] --keys [hdkeys]' + + ' --type [pubkeyhash/multisig] -m [m-value]' + + ' -n [n-value] --witness: View or create wallet by ID.'); + self.log(' $ listen [id]: Listen for wallet events.'); + self.log(' $ getwallet [id]: View wallet by ID.'); + self.log(' $ addkey [id] --keys [hdkeys]: Add keys to wallet.'); + self.log(' $ rmkey [id] --keys [hdkeys]: Remove keys from wallet.'); + self.log(' $ balance [id]: Get wallet balance.'); + self.log(' $ history [id]: View wallet TX history.'); + self.log(' $ accounts [id]: List account names.'); + self.log(' $ account [id] [acct]: Get account details.'); + self.log(' $ send [id] [address] [value] --script [code]: Send transaction.'); + self.log(' $ create [id] [address] [value] --script [code]: Create transaction.'); + self.log(' $ sign [id] [tx-hex]: Sign transaction.'); + self.log(' $ zap [id] --age [age]: Zap pending wallet TXs.'); + self.log(' $ broadcast [tx-hex]: Broadcast transaction.'); + self.log(' $ view [tx-hex]: View transaction.'); + self.log(' $ mempool: Get mempool snapshot.'); + self.log(' $ tx [hash/address]: View transactions.'); + self.log(' $ coin [hash+index/address]: View coins.'); + self.log(' $ block [hash/height]: View block.'); + self.log('Other Options:'); + self.log(' --passphrase [passphrase]: For signing and account creation.'); + self.log(' --account [acctname]: Account name.'); + return callback(); + } + }); +}; + +CLI.prototype.open = function open(callback) { + switch (this.argv[0]) { + case 'w': + case 'wallet': + this.argv.shift(); + if (this.argv[0] === 'create') { + this.argv[0] = 'mkwallet'; + return this.handleNode(callback); + } + return this.handleWallet(callback); + default: + return this.handleNode(callback); + } +}; + +CLI.prototype.destroy = function destroy(callback) { + if (this.wallet) + this.wallet.destroy(); + if (this.client) + this.client.destroy(); + callback(); +}; function main(callback) { - switch (argv.args.shift()) { - case 'wallet': - return createWallet(callback); - case 'listen': - return listenWallet(callback); - case 'getwallet': - return getWallet(callback); - case 'addkey': - return addKey(callback); - case 'rmkey': - return removeKey(callback); - case 'balance': - return getBalance(callback); - case 'history': - return getWalletHistory(callback); - case 'account': - return createAccount(callback); - case 'accounts': - return getAccounts(callback); - case 'sign': - return signTX(callback); - case 'create': - return createTX(callback); - case 'send': - return sendTX(callback); - case 'zap': - return zap(callback); - case 'broadcast': - return broadcast(callback); - case 'view': - return view(callback); - case 'mempool': - return getMempool(callback); - case 'tx': - return getTX(callback); - case 'coin': - return getCoin(callback); - case 'block': - return getBlock(callback); - default: - utils.log('Unrecognized command.'); - utils.log('Commands:'); - utils.log(' $ wallet [id] --keys [hdkeys]' - + ' --type [pubkeyhash/multisig] -m [m-value]' - + ' -n [n-value] --witness: View or create wallet by ID.'); - utils.log(' $ listen [id]: Listen for wallet events.'); - utils.log(' $ getwallet [id]: View wallet by ID.'); - utils.log(' $ addkey [id] --keys [hdkeys]: Add keys to wallet.'); - utils.log(' $ rmkey [id] --keys [hdkeys]: Remove keys from wallet.'); - utils.log(' $ balance [id]: Get wallet balance.'); - utils.log(' $ history [id]: View wallet TX history.'); - utils.log(' $ accounts [id]: List account names.'); - utils.log(' $ account [id] [acct]: Get account details.'); - utils.log(' $ send [id] [address] [value] --script [code]: Send transaction.'); - utils.log(' $ create [id] [address] [value] --script [code]: Create transaction.'); - utils.log(' $ sign [id] [tx-hex]: Sign transaction.'); - utils.log(' $ zap [id] --age [age]: Zap pending wallet TXs.'); - utils.log(' $ broadcast [tx-hex]: Broadcast transaction.'); - utils.log(' $ view [tx-hex]: View transaction.'); - utils.log(' $ mempool: Get mempool snapshot.'); - utils.log(' $ tx [hash/address]: View transactions.'); - utils.log(' $ coin [hash+index/address]: View coins.'); - utils.log(' $ block [hash/height]: View block.'); - utils.log('Other Options:'); - utils.log(' --passphrase [passphrase]: For signing and account creation.'); - utils.log(' --account [acctname]: Account name.'); - return callback(); - } + var cli = new CLI(); + cli.open(function(err) { + if (err) + return callback(err); + cli.destroy(callback); + }); } -function parseArg(argv) { - var args = []; - var options = {}; - var arg, negate; - - argv = argv.slice(); - - function getarg() { - var arg = argv.shift(); - - if (arg.indexOf('--') === 0) { - // e.g. --opt - arg = arg.split('='); - if (arg.length > 1) { - // e.g. --opt=val - argv.unshift(arg.slice(1).join('=')); - } - arg = arg[0]; - } else if (arg[0] === '-') { - if (arg.length > 2) { - // e.g. -abc - argv = arg.substring(1).split('').map(function(ch) { - return '-' + ch; - }).concat(argv); - arg = argv.shift(); - } else { - // e.g. -a - } - } else { - // e.g. foo - } - - return arg; - } - - while (argv.length) { - arg = getarg(); - if (arg.indexOf('--') === 0) { - negate = arg.indexOf('--no-') === 0; - opt = arg.replace(/^--(no-)?/, ''); - options[opt] = !argv[0] || argv[0][0] === '-' - ? (negate ? false : true) - : argv.shift(); - } else { - args.push(arg); - } - } - - options.args = args.slice(2); - - return options; -} - -client.getInfo(function(err, info) { +main(function(err) { if (err) { console.error(err.stack + ''); - //return process.exit(1); + return process.exit(1); } - - if (!argv.args[0]) - utils.log(info); - - main(function(err) { - if (err) { - console.error(err.stack + ''); - return process.exit(1); - } - client.destroy(); - }); + return process.exit(0); }); diff --git a/lib/bcoin/config.js b/lib/bcoin/config.js index eb2f3bb7..540d82bd 100644 --- a/lib/bcoin/config.js +++ b/lib/bcoin/config.js @@ -172,6 +172,8 @@ config.parseData = function parseData(data) { options.walletAuth = bool(data.walletauth); options.noAuth = bool(data.noauth); + options.data = data; + return options; }; @@ -245,13 +247,13 @@ config.parseText = function parseText(text) { */ config.parseArg = function parseArg(argv) { - var data = {}; - var i, arg, key, value, alias; + var data = { args: [] }; + var i, arg, key, value, alias, equals; if (!argv) argv = process.argv; - argv = argv.slice(); + argv = argv.slice(2); while (argv.length) { arg = argv.shift(); @@ -264,8 +266,10 @@ config.parseArg = function parseArg(argv) { if (arg.length > 1) { // e.g. --opt=val value = arg.slice(1).join('=').trim(); + equals = true; } else { value = 'true'; + equals = false; } key = key.replace(/\-/g, ''); @@ -292,19 +296,23 @@ config.parseArg = function parseArg(argv) { if (alias) key = alias; data[key] = 'true'; + equals = false; } continue; } // e.g. foo - if (key) { - value = arg.trim(); + value = arg.trim(); - if (value.length === 0) - continue; + if (value.length === 0) + continue; + if (key && !equals) { data[key] = value; + key = null; + } else { + data.args.push(value); } } diff --git a/lib/bcoin/http/client.js b/lib/bcoin/http/client.js index 911ee3a3..a670f527 100644 --- a/lib/bcoin/http/client.js +++ b/lib/bcoin/http/client.js @@ -7,8 +7,9 @@ 'use strict'; -var bcoin = require('../env'); +var Network = require('../network'); var AsyncObject = require('../async'); +var RPCClient = require('./rpcclient'); var utils = require('../utils'); var assert = utils.assert; var request = require('./request'); @@ -34,12 +35,13 @@ function HTTPClient(options) { AsyncObject.call(this); this.options = options; - this.network = bcoin.network.get(options.network); + this.network = Network.get(options.network); this.uri = options.uri || 'http://localhost:' + this.network.rpcPort; this.socket = null; this.apiKey = options.apiKey; this.auth = options.auth; + this.rpc = new RPCClient(options); if (this.apiKey) { if (typeof this.apiKey === 'string') { @@ -110,9 +112,6 @@ HTTPClient.prototype._open = function _open(callback) { }); this.socket.on('wallet address', function(receive) { - receive = receive.map(function(address) { - return bcoin.keyring.fromJSON(address); - }); self.emit('address', receive); }); @@ -126,7 +125,8 @@ HTTPClient.prototype._open = function _open(callback) { }); this.socket.on('connect', function() { - self.socket.emit('auth', self.apiKey.toString('hex'), function(err) { + var apiKey = self.apiKey ? self.apiKey.toString('hex') : null; + self.socket.emit('auth', apiKey, function(err) { if (err) return callback(new Error(err.error)); callback(); @@ -350,23 +350,7 @@ HTTPClient.prototype.getWalletHistory = function getWalletHistory(id, account, c options = { account: account }; - return this._get('/wallet/' + id + '/tx/history', options, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(null, []); - - try { - body = body.map(function(data) { - return bcoin.tx.fromJSON(data); - }); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._get('/wallet/' + id + '/tx/history', options, callback); }; /** @@ -385,23 +369,7 @@ HTTPClient.prototype.getWalletCoins = function getWalletCoins(id, account, callb options = { account: account }; - return this._get('/wallet/' + id + '/coin', options, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(null, []); - - try { - body = body.map(function(data) { - return bcoin.coin.fromJSON(data); - }); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._get('/wallet/' + id + '/coin', options, callback); }; /** @@ -420,23 +388,7 @@ HTTPClient.prototype.getWalletUnconfirmed = function getUnconfirmed(id, account, options = { account: account }; - return this._get('/wallet/' + id + '/tx/unconfirmed', options, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(null, []); - - try { - body = body.map(function(data) { - return bcoin.tx.fromJSON(data); - }); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._get('/wallet/' + id + '/tx/unconfirmed', options, callback); }; /** @@ -455,20 +407,7 @@ HTTPClient.prototype.getWalletBalance = function getBalance(id, account, callbac options = { account: account }; - return this._get('/wallet/' + id + '/balance', options, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(new Error('Not found.')); - - return callback(null, { - id: body.id, - confirmed: utils.satoshi(body.confirmed), - unconfirmed: utils.satoshi(body.unconfirmed), - total: utils.satoshi(body.total) - }); - }); + return this._get('/wallet/' + id + '/balance', options, callback); }; /** @@ -488,23 +427,7 @@ HTTPClient.prototype.getWalletLast = function getWalletLast(id, account, limit, options = { account: account, limit: limit }; - return this._get('/wallet/' + id + '/tx/last', options, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(null, []); - - try { - body = body.map(function(data) { - return bcoin.tx.fromJSON(data); - }); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._get('/wallet/' + id + '/tx/last', options, callback); }; /** @@ -533,23 +456,7 @@ HTTPClient.prototype.getWalletRange = function getWalletRange(id, account, optio reverse: options.reverse }; - return this._get('/wallet/' + id + '/tx/range', options, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(null, []); - - try { - body = body.map(function(data) { - return bcoin.tx.fromJSON(data); - }); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._get('/wallet/' + id + '/tx/range', options, callback); }; /** @@ -571,23 +478,7 @@ HTTPClient.prototype.getWalletTX = function getTX(id, account, hash, callback) { options = { account: account }; - hash = utils.revHex(hash); - - return this._get('/wallet/' + id + '/tx/' + hash, options, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(); - - try { - body = bcoin.tx.fromJSON(body); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._get('/wallet/' + id + '/tx/' + hash, options, callback); }; /** @@ -611,24 +502,9 @@ HTTPClient.prototype.getWalletCoin = function getCoin(id, account, hash, index, options = { account: account }; - hash = utils.revHex(hash); path = '/wallet/' + id + '/coin/' + hash + '/' + index; - return this._get(path, options, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(); - - try { - body = bcoin.coin.fromJSON(body); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._get(path, options, callback); }; /** @@ -641,23 +517,7 @@ HTTPClient.prototype.getWalletCoin = function getCoin(id, account, hash, index, HTTPClient.prototype.getCoinsByAddress = function getCoinsByAddress(address, callback) { var body = { addresses: address }; - return this._post('/coin/address', body, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(null, []); - - try { - body = body.map(function(data) { - return bcoin.coin.fromJSON(data); - }); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._post('/coin/address', body, callback); }; /** @@ -669,23 +529,7 @@ HTTPClient.prototype.getCoinsByAddress = function getCoinsByAddress(address, cal */ HTTPClient.prototype.getCoin = function getCoin(hash, index, callback) { - hash = utils.revHex(hash); - - return this._get('/coin/' + hash + '/' + index, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(); - - try { - body = bcoin.coin.fromJSON(body); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._get('/coin/' + hash + '/' + index, callback); }; /** @@ -698,23 +542,7 @@ HTTPClient.prototype.getCoin = function getCoin(hash, index, callback) { HTTPClient.prototype.getTXByAddress = function getTXByAddress(address, callback) { var body = { addresses: address }; - return this._post('/tx/address', body, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(null, []); - - try { - body = body.map(function(data) { - return bcoin.tx.fromJSON(data); - }); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._post('/tx/address', body, callback); }; /** @@ -724,23 +552,7 @@ HTTPClient.prototype.getTXByAddress = function getTXByAddress(address, callback) */ HTTPClient.prototype.getTX = function getTX(hash, callback) { - hash = utils.revHex(hash); - - return this._get('/tx/' + hash, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(); - - try { - body = bcoin.tx.fromJSON(body); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._get('/tx/' + hash, callback); }; /** @@ -750,24 +562,7 @@ HTTPClient.prototype.getTX = function getTX(hash, callback) { */ HTTPClient.prototype.getBlock = function getBlock(hash, callback) { - if (typeof hash !== 'number') - hash = utils.revHex(hash); - - return this._get('/block/' + hash, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(); - - try { - body = bcoin.block.fromJSON(body); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._get('/block/' + hash, callback); }; /** @@ -781,11 +576,7 @@ HTTPClient.prototype.broadcast = function broadcast(tx, callback) { callback = utils.ensure(callback); - return this._post('/broadcast', body, function(err) { - if (err) - return callback(err); - return callback(); - }); + return this._post('/broadcast', body, callback); }; /** @@ -816,18 +607,7 @@ HTTPClient.prototype.walletSend = function walletSend(id, options, callback) { callback = utils.ensure(callback); - return this._post('/wallet/' + id + '/send', options, function(err, body) { - if (err) - return callback(err); - - try { - body = bcoin.tx.fromJSON(body); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._post('/wallet/' + id + '/send', options, callback); }; /** @@ -890,18 +670,7 @@ HTTPClient.prototype.walletCreate = function walletCreate(id, options, callback) callback = utils.ensure(callback); - return this._post('/wallet/' + id + '/create', options, function(err, body) { - if (err) - return callback(err); - - try { - body = bcoin.tx.fromJSON(body); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._post('/wallet/' + id + '/create', options, callback); }; /** @@ -926,18 +695,7 @@ HTTPClient.prototype.walletSign = function walletCreate(id, tx, options, callbac callback = utils.ensure(callback); - return this._post('/wallet/' + id + '/sign', body, function(err, body) { - if (err) - return callback(err); - - try { - body = bcoin.tx.fromJSON(body); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._post('/wallet/' + id + '/sign', body, callback); }; /** @@ -951,18 +709,7 @@ HTTPClient.prototype.walletFill = function walletFill(tx, callback) { callback = utils.ensure(callback); - return this._post('/wallet/_/fill', body, function(err, body) { - if (err) - return callback(err); - - try { - body = bcoin.tx.fromJSON(body); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._post('/wallet/_/fill', body, callback); }; /** @@ -990,11 +737,7 @@ HTTPClient.prototype.walletZap = function walletZap(id, account, age, callback) callback = utils.ensure(callback); - return this._post('/wallet/' + id + '/zap', body, function(err) { - if (err) - return callback(err); - return callback(); - }); + return this._post('/wallet/' + id + '/zap', body, callback); }; /** @@ -1020,11 +763,7 @@ HTTPClient.prototype.addKey = function addKey(id, account, key, callback) { callback = utils.ensure(callback); - return this._put('/wallet/' + id + '/key', options, function(err) { - if (err) - return callback(err); - return callback(); - }); + return this._put('/wallet/' + id + '/key', options, callback); }; /** @@ -1050,11 +789,7 @@ HTTPClient.prototype.removeKey = function removeKey(id, account, key, callback) callback = utils.ensure(callback); - return this._del('/wallet/' + id + '/key', options, function(err) { - if (err) - return callback(err); - return callback(); - }); + return this._del('/wallet/' + id + '/key', options, callback); }; /** @@ -1065,15 +800,7 @@ HTTPClient.prototype.removeKey = function removeKey(id, account, key, callback) HTTPClient.prototype.getWalletAccounts = function getWalletAccounts(id, callback) { var path = '/wallet/' + id + '/account'; - return this._get(path, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(null, []); - - return callback(null, body); - }); + return this._get(path, callback); }; /** @@ -1099,15 +826,7 @@ HTTPClient.prototype.createWalletAccount = function createWalletAccount(id, opti path = '/wallet/' + id + '/account'; - return this._post(path, options, function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(null, []); - - return callback(null, body); - }); + return this._post(path, options, callback); }; @@ -1117,23 +836,7 @@ HTTPClient.prototype.createWalletAccount = function createWalletAccount(id, opti */ HTTPClient.prototype.getMempool = function getMempool(callback) { - return this._get('/mempool', function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(null, []); - - try { - body = body.map(function(data) { - return bcoin.tx.fromJSON(data); - }); - } catch (e) { - return callback(e); - } - - return callback(null, body); - }); + return this._get('/mempool', callback); }; /** @@ -1142,15 +845,7 @@ HTTPClient.prototype.getMempool = function getMempool(callback) { */ HTTPClient.prototype.getInfo = function getInfo(callback) { - return this._get('/', function(err, body) { - if (err) - return callback(err); - - if (!body) - return callback(new Error('Info not available.')); - - return callback(null, body); - }); + return this._get('/', callback); }; /* diff --git a/lib/bcoin/http/rpc.js b/lib/bcoin/http/rpc.js index 1fd31ee4..4599f6d1 100644 --- a/lib/bcoin/http/rpc.js +++ b/lib/bcoin/http/rpc.js @@ -2909,7 +2909,7 @@ RPC.prototype._toWalletTX = function _toWalletTX(tx, callback) { var self = this; var i, det, receive, member, sent, received, json; - this.wallet.tx.toDetails(tx, function(err, details) { + this.wallet.toDetails(tx, function(err, details) { if (err) return callback(err); @@ -3240,7 +3240,7 @@ RPC.prototype._toListTX = function _toListTX(tx, callback) { var i, receive, member, det, sent, received, index; var sendMember, recMember, sendIndex, recIndex, json; - this.wallet.tx.toDetails(tx, function(err, details) { + this.wallet.toDetails(tx, function(err, details) { if (err) return callback(err); diff --git a/lib/bcoin/http/rpcclient.js b/lib/bcoin/http/rpcclient.js new file mode 100644 index 00000000..fac4b22b --- /dev/null +++ b/lib/bcoin/http/rpcclient.js @@ -0,0 +1,95 @@ +/*! + * rpcclient.js - json rpc client for bcoin + * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +var Network = require('../network'); +var utils = require('../utils'); +var assert = utils.assert; +var request = require('./request'); + +/** + * BCoin RPC client. + * @exports RPCClient + * @constructor + * @param {String} uri + * @param {Object?} options + */ + +function RPCClient(options) { + if (!(this instanceof RPCClient)) + return new RPCClient(options); + + if (!options) + options = {}; + + if (typeof options === 'string') + options = { uri: options }; + + this.options = options; + this.network = Network.get(options.network); + + this.uri = options.uri || 'http://localhost:' + this.network.rpcPort; + this.apiKey = options.apiKey; + this.id = 0; + + if (this.apiKey) { + if (typeof this.apiKey === 'string') { + assert(utils.isHex(this.apiKey), 'API key must be a hex string.'); + this.apiKey = new Buffer(this.apiKey, 'hex'); + } + assert(Buffer.isBuffer(this.apiKey)); + assert(this.apiKey.length === 32, 'API key must be 32 bytes.'); + } +} + +/** + * Make an http request to endpoint. + * @private + * @param {String} method + * @param {String} endpoint - Path. + * @param {Object} json - Body or query depending on method. + * @param {Function} callback - Returns [Error, Object?]. + */ + +RPCClient.prototype.call = function call(method, params, callback) { + var self = this; + + request({ + method: 'POST', + uri: this.uri, + json: { + method: method, + params: params, + id: this.id++ + }, + auth: { + username: 'bitcoinrpc', + password: this.apiKey ? this.apiKey.toString('hex') : '' + }, + expect: 'json' + }, function(err, res, body) { + if (err) + return callback(err); + + if (!body) + return callback(); + + if (res.statusCode !== 200) { + if (body.error) + return callback(new Error(body.error.message)); + return callback(new Error('Status code: ' + res.statusCode)); + } + + return callback(null, body.result); + }); +}; + +/* + * Expose + */ + +module.exports = RPCClient; diff --git a/lib/bcoin/http/server.js b/lib/bcoin/http/server.js index 239a6312..361d9854 100644 --- a/lib/bcoin/http/server.js +++ b/lib/bcoin/http/server.js @@ -278,13 +278,22 @@ HTTPServer.prototype._init = function _init() { if (req.path.length < 2 || req.path[0] !== 'wallet') return next(); - if (!self.options.walletAuth) - return next(); + if (!self.options.walletAuth) { + return self.walletdb.get(req.options.id, function(err, wallet) { + if (err) + return next(err); - self.walletdb.auth(req.options.id, req.options.token, function(err) { + if (!wallet) + return send(404); + + req.wallet = wallet; + + return next(); + }); + } + + self.walletdb.auth(req.options.id, req.options.token, function(err, wallet) { if (err) { - if (err.message === 'Wallet not found.') - return next(); self.logger.info('Auth failure for %s: %s.', req.options.id, err.message); res.setHeader('WWW-Authenticate', 'Basic realm="wallet"'); @@ -292,6 +301,10 @@ HTTPServer.prototype._init = function _init() { return; } + if (!wallet) + return send(404); + + req.wallet = wallet; self.logger.info('Successful auth for %s.', req.options.id); next(); }); @@ -525,27 +538,16 @@ HTTPServer.prototype._init = function _init() { // Get wallet this.get('/wallet/:id', function(req, res, next, send) { - self.walletdb.getInfo(req.options.id, function(err, wallet) { - if (err) - return next(err); - - if (!wallet) - return send(404); - - send(200, wallet.toJSON()); - }); + send(200, req.wallet.toJSON()); }); - // Create/get wallet + // Create wallet this.post('/wallet/:id?', function(req, res, next, send) { var json; - self.walletdb.ensure(req.options, function(err, wallet) { + self.walletdb.create(req.options, function(err, wallet) { if (err) return next(err); - if (!wallet) - return send(404); - json = wallet.toJSON(); wallet.destroy(); @@ -555,7 +557,7 @@ HTTPServer.prototype._init = function _init() { // List accounts this.get('/wallet/:id/account', function(req, res, next, send) { - self.walletdb.getAccounts(req.options.id, function(err, accounts) { + req.wallet.getAccounts(function(err, accounts) { if (err) return next(err); @@ -568,10 +570,7 @@ HTTPServer.prototype._init = function _init() { // Create/get account this.post('/wallet/:id/account/:account?', function(req, res, next, send) { - var id = req.options.id; - var options = req.options; - options.name = options.account || options.name; - self.walletdb.ensureAccount(id, options, function(err, account) { + req.wallet.createAccount(req.options, function(err, account) { if (err) return next(err); @@ -584,11 +583,10 @@ HTTPServer.prototype._init = function _init() { // Change passphrase this.post('/wallet/:id/passphrase', function(req, res, next, send) { - var id = req.options.id; var options = req.options; var old = options.old; var new_ = options.passphrase; - self.walletdb.setPassphrase(id, old, new_, function(err) { + req.wallet.setPassphrase(old, new_, function(err) { if (err) return next(err); @@ -598,9 +596,8 @@ HTTPServer.prototype._init = function _init() { // Generate new token this.post('/wallet/:id/retoken', function(req, res, next, send) { - var id = req.options.id; var options = req.options; - self.walletdb.retoken(id, options.passphrase, function(err, token) { + req.wallet.retoken(options.passphrase, function(err, token) { if (err) return next(err); @@ -610,10 +607,9 @@ HTTPServer.prototype._init = function _init() { // Send TX this.post('/wallet/:id/send', function(req, res, next, send) { - var id = req.options.id; var options = req.options; - self.walletdb.send(id, options, function(err, tx) { + req.wallet.send(options, function(err, tx) { if (err) return next(err); @@ -623,14 +619,13 @@ HTTPServer.prototype._init = function _init() { // Create TX this.post('/wallet/:id/create', function(req, res, next, send) { - var id = req.options.id; var options = req.options; - self.walletdb.createTX(id, options, function(err, tx) { + req.wallet.createTX(options, function(err, tx) { if (err) return next(err); - self.walletdb.sign(id, tx, options, function(err) { + req.wallet.sign(tx, options, function(err) { if (err) return next(err); @@ -641,11 +636,10 @@ HTTPServer.prototype._init = function _init() { // Sign TX this.post('/wallet/:id/sign', function(req, res, next, send) { - var id = req.options.id; var options = req.options; var tx = req.options.tx; - self.walletdb.sign(id, tx, options, function(err) { + req.wallet.sign(tx, options, function(err) { if (err) return next(err); @@ -655,10 +649,9 @@ HTTPServer.prototype._init = function _init() { // Fill TX this.post('/wallet/:id/fill', function(req, res, next, send) { - var id = req.options.id; var tx = req.options.tx; - self.walletdb.fillHistory(id, tx, function(err) { + req.wallet.fillHistory(tx, function(err) { if (err) return next(err); @@ -668,11 +661,10 @@ HTTPServer.prototype._init = function _init() { // Zap Wallet TXs this.post('/wallet/:id/zap', function(req, res, next, send) { - var id = req.options.id; var account = req.options.account; var age = req.options.age; - self.walletdb.zap(id, account, age, function(err) { + req.wallet.zap(account, age, function(err) { if (err) return next(err); @@ -682,10 +674,9 @@ HTTPServer.prototype._init = function _init() { // Add key this.put('/wallet/:id/key', function(req, res, next, send) { - var id = req.options.id; var account = req.options.account; var key = req.options.key; - self.walletdb.addKey(id, account, key, function(err) { + req.wallet.addKey(account, key, function(err) { if (err) return next(err); @@ -695,10 +686,9 @@ HTTPServer.prototype._init = function _init() { // Remove key this.del('/wallet/:id/key', function(req, res, next, send) { - var id = req.options.id; var account = req.options.account; var key = req.options.key; - self.walletdb.removeKey(id, account, key, function(err) { + req.wallet.removeKey(account, key, function(err) { if (err) return next(err); @@ -708,9 +698,8 @@ HTTPServer.prototype._init = function _init() { // Create address this.post('/wallet/:id/address', function(req, res, next, send) { - var id = req.options.id; var account = req.options.account; - self.walletdb.createAddress(id, account, false, function(err, address) { + req.wallet.createAddress(account, false, function(err, address) { if (err) return next(err); @@ -720,28 +709,22 @@ HTTPServer.prototype._init = function _init() { // Wallet Balance this.get('/wallet/:id/balance', function(req, res, next, send) { - var id = req.options.id; var account = req.options.account; - self.walletdb.getBalance(id, account, function(err, balance) { + req.wallet.getBalance(account, function(err, balance) { if (err) return next(err); if (!balance) return send(404); - send(200, { - confirmed: utils.btc(balance.confirmed), - unconfirmed: utils.btc(balance.unconfirmed), - total: utils.btc(balance.total) - }); + send(200, balance.toJSON()); }); }); // Wallet UTXOs this.get('/wallet/:id/coin', function(req, res, next, send) { - var id = req.options.id; var account = req.options.account; - self.walletdb.getCoins(id, account, function(err, coins) { + req.wallet.getCoins(account, function(err, coins) { if (err) return next(err); @@ -756,10 +739,9 @@ HTTPServer.prototype._init = function _init() { // Wallet Coin this.get('/wallet/:id/coin/:hash/:index', function(req, res, next, send) { - var id = req.options.id; var hash = req.options.hash; var index = req.options.index; - self.walletdb.getCoin(id, hash, index, function(err, coin) { + req.wallet.getCoin(hash, index, function(err, coin) { if (err) return next(err); @@ -772,18 +754,15 @@ HTTPServer.prototype._init = function _init() { // Wallet TXs this.get('/wallet/:id/tx/history', function(req, res, next, send) { - var id = req.options.id; var account = req.options.account; - self.walletdb.getHistory(id, account, function(err, txs) { + req.wallet.getHistory(account, function(err, txs) { if (err) return next(err); if (!txs.length) return send(404); - utils.forEachSerial(txs, function(tx, next) { - self.walletdb.fillHistory(id, tx, next); - }, function(err) { + req.wallet.toDetails(txs, function(err, txs) { if (err) return next(err); @@ -796,18 +775,15 @@ HTTPServer.prototype._init = function _init() { // Wallet Pending TXs this.get('/wallet/:id/tx/unconfirmed', function(req, res, next, send) { - var id = req.options.id; var account = req.options.account; - self.walletdb.getUnconfirmed(id, account, function(err, txs) { + req.wallet.getUnconfirmed(account, function(err, txs) { if (err) return next(err); if (!txs.length) return send(404); - utils.forEachSerial(txs, function(tx, next) { - self.walletdb.fillHistory(id, tx, next); - }, function(err) { + req.wallet.toDetails(txs, function(err, txs) { if (err) return next(err); @@ -820,19 +796,16 @@ HTTPServer.prototype._init = function _init() { // Wallet TXs within time range this.get('/wallet/:id/tx/range', function(req, res, next, send) { - var id = req.options.id; var account = req.options.account; var options = req.options; - self.walletdb.getRange(id, account, options, function(err, txs) { + req.walletdb.getRange(account, options, function(err, txs) { if (err) return next(err); if (!txs.length) return send(404); - utils.forEachSerial(txs, function(tx, next) { - self.walletdb.fillHistory(id, tx, next); - }, function(err) { + req.wallet.toDetails(txs, function(err, txs) { if (err) return next(err); @@ -845,19 +818,16 @@ HTTPServer.prototype._init = function _init() { // Wallet TXs within time range this.get('/wallet/:id/tx/last', function(req, res, next, send) { - var id = req.options.id; var account = req.options.account; var limit = req.options.limit; - self.walletdb.getRange(id, account, limit, function(err, txs) { + req.wallet.getRange(account, limit, function(err, txs) { if (err) return next(err); if (!txs.length) return send(404); - utils.forEachSerial(txs, function(tx, next) { - self.walletdb.fillHistory(id, tx, next); - }, function(err) { + req.wallet.toDetails(txs, function(err, txs) { if (err) return next(err); @@ -870,16 +840,15 @@ HTTPServer.prototype._init = function _init() { // Wallet TX this.get('/wallet/:id/tx/:hash', function(req, res, next, send) { - var id = req.options.id; var hash = req.options.hash; - self.walletdb.getTX(id, hash, function(err, tx) { + req.wallet.getTX(hash, function(err, tx) { if (err) return next(err); if (!tx) return send(404); - self.walletdb.fillHistory(id, tx, function(err) { + req.wallet.toDetails(tx, function(err, tx) { if (err) return next(err); send(200, tx.toJSON()); @@ -954,11 +923,15 @@ HTTPServer.prototype._initIO = function _initIO() { return callback(); } - self.walletdb.auth(id, token, function(err) { + self.walletdb.auth(id, token, function(err, wallet) { if (err) { self.logger.info('Wallet auth failure for %s: %s.', id, err.message); return callback({ error: 'Bad token.' }); } + + if (!wallet) + return callback({ error: 'Wallet does not exist.' }); + self.logger.info('Successful wallet auth for %s.', id); socket.join(id); return callback(); diff --git a/lib/bcoin/http/wallet.js b/lib/bcoin/http/wallet.js index edabebdd..6ecfefef 100644 --- a/lib/bcoin/http/wallet.js +++ b/lib/bcoin/http/wallet.js @@ -7,7 +7,7 @@ 'use strict'; -var bcoin = require('../env'); +var Network = require('../network'); var EventEmitter = require('events').EventEmitter; var utils = require('../utils'); @@ -33,7 +33,7 @@ function HTTPWallet(options) { options = { uri: options }; this.options = options; - this.network = bcoin.network.get(options.network); + this.network = Network.get(options.network); this.client = new http.client(options); this.uri = options.uri; @@ -83,7 +83,7 @@ HTTPWallet.prototype._init = function _init() { }; /** - * Open the client and ensure a wallet. + * Open the client and get a wallet. * @alias HTTPWallet#open * @param {Function} callback */ @@ -94,6 +94,38 @@ HTTPWallet.prototype.open = function open(options, callback) { if (Buffer.isBuffer(options.token)) options.token = options.token.toString('hex'); + this.id = options.id; + + if (options.token) { + this.client.auth = { username: 'x', password: options.token }; + this.token = new Buffer(options.token, 'hex'); + } + + this.client.open(function(err) { + if (err) + return callback(err); + + self.client.getWallet(self.id, function(err, wallet) { + if (err) + return callback(err); + self.client.joinWallet(self.id, wallet.token, function(err) { + if (err) + return callback(new Error(err.error)); + callback(null, wallet); + }); + }); + }); +}; + +/** + * Open the client and create a wallet. + * @alias HTTPWallet#open + * @param {Function} callback + */ + +HTTPWallet.prototype.create = function create(options, callback) { + var self = this; + this.client.open(function(err) { if (err) return callback(err); @@ -101,14 +133,11 @@ HTTPWallet.prototype.open = function open(options, callback) { self.client.createWallet(options, function(err, wallet) { if (err) return callback(err); - self.id = wallet.id; - self.client.auth = { username: 'x', password: wallet.token }; - self.token = new Buffer(wallet.token, 'hex'); - self.client.joinWallet(self.id, wallet.token, function(err) { - if (err) - return callback(new Error(err.error)); - callback(null, wallet); - }); + + self.open({ + id: wallet.id, + token: wallet.token + }, callback); }); }); }; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 5e35e0b6..0ca763d5 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -511,6 +511,30 @@ Wallet.prototype.createAccount = function createAccount(options, callback, force }); }; +/** + * Ensure an account. Requires passphrase if master key is encrypted. + * @param {Object} options - See {@link Account} options. + * @param {Function} callback - Returns [Error, {@link Account}]. + */ + +Wallet.prototype.ensureAccount = function ensureAccount(options, callback) { + var self = this; + var account = options.account; + + if (typeof options.name === 'string') + account = options.name; + + this.hasAccount(account, function(err, exists) { + if (err) + return callback(err); + + if (exists) + return self.getAccount(account, callback); + + self.createAccount(options, callback); + }); +}; + /** * List account names and indexes from the db. * @param {Function} callback - Returns [Error, Array]. @@ -1349,6 +1373,36 @@ Wallet.prototype.fillCoins = function fillCoins(tx, callback) { return this.tx.fillCoins(tx, callback); }; +/** + * Fill transaction with historical coins (accesses db). + * @param {TX} tx + * @param {Function} callback - Returns [Error, {@link TX}]. + */ + +Wallet.prototype.fillHistory = function fillHistory(tx, callback) { + return this.tx.fillHistory(tx, callback); +}; + +/** + * Fill transaction with historical coins (accesses db). + * @param {TX} tx + * @param {Function} callback - Returns [Error, {@link TX}]. + */ + +Wallet.prototype.toDetails = function toDetails(tx, callback) { + return this.tx.toDetails(tx, callback); +}; + +/** + * Fill transaction with historical coins (accesses db). + * @param {TX} tx + * @param {Function} callback - Returns [Error, {@link TX}]. + */ + +Wallet.prototype.getDetails = function getDetails(tx, callback) { + return this.tx.getDetails(tx, callback); +}; + /** * Get a coin from the wallet (accesses db). * @param {Hash} hash diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index 04ec92d2..9ec1e6b6 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -438,12 +438,12 @@ WalletDB.prototype.save = function save(wallet) { */ WalletDB.prototype.auth = function auth(id, token, callback) { - this._get(id, function(err, wallet) { + this.get(id, function(err, wallet) { if (err) return callback(err); if (!wallet) - return callback(new Error('Wallet not found.')); + return callback(); if (typeof token === 'string') { if (!utils.isHex(token)) @@ -455,7 +455,7 @@ WalletDB.prototype.auth = function auth(id, token, callback) { if (!utils.ccmp(token, wallet.token)) return callback(new Error('Authentication error.')); - return callback(); + return callback(null, wallet); }); }; diff --git a/test/http-test.js b/test/http-test.js index e8e58466..fdc25f49 100644 --- a/test/http-test.js +++ b/test/http-test.js @@ -53,7 +53,7 @@ describe('HTTP', function() { }); it('should create wallet', function(cb) { - wallet.open({ id: 'test' }, function(err, wallet) { + wallet.create({ id: 'test' }, function(err, wallet) { assert.ifError(err); assert.equal(wallet.id, 'test'); cb(); @@ -113,9 +113,9 @@ describe('HTTP', function() { assert.equal(receive.type, 'pubkeyhash'); assert.equal(receive.change, 0); assert(balance); - assert.equal(balance.confirmed, 0); - assert.equal(balance.unconfirmed, 201840); - assert.equal(balance.total, 201840); + assert.equal(utils.satoshi(balance.confirmed), 0); + assert.equal(utils.satoshi(balance.unconfirmed), 201840); + assert.equal(utils.satoshi(balance.total), 201840); assert(details); assert.equal(details.hash, t1.rhash); cb(); @@ -126,9 +126,9 @@ describe('HTTP', function() { it('should get balance', function(cb) { wallet.getBalance(function(err, balance) { assert.ifError(err); - assert.equal(balance.confirmed, 0); - assert.equal(balance.unconfirmed, 201840); - assert.equal(balance.total, 201840); + assert.equal(utils.satoshi(balance.confirmed), 0); + assert.equal(utils.satoshi(balance.unconfirmed), 201840); + assert.equal(utils.satoshi(balance.total), 201840); cb(); }); }); @@ -147,8 +147,8 @@ describe('HTTP', function() { assert(tx); assert.equal(tx.inputs.length, 1); assert.equal(tx.outputs.length, 2); - assert.equal(tx.getOutputValue(), 48190); - hash = tx.hash('hex'); + assert.equal(utils.satoshi(tx.outputs[0].value) + utils.satoshi(tx.outputs[1].value), 48190); + hash = tx.hash; cb(); }); }); @@ -157,7 +157,7 @@ describe('HTTP', function() { wallet.getTX(hash, function(err, tx) { assert.ifError(err); assert(tx); - assert.equal(tx.hash('hex'), hash); + assert.equal(tx.hash, hash); cb(); }); }); @@ -175,7 +175,7 @@ describe('HTTP', function() { it('should get balance', function(cb) { wallet.getBalance(function(err, balance) { assert.ifError(err); - assert.equal(balance.total, 199570); + assert.equal(utils.satoshi(balance.total), 199570); cb(); }); });