#!/usr/bin/env node 'use strict'; var Config = require('../lib/node/config'); var util = require('../lib/utils/util'); var Client = require('../lib/http/client'); var Wallet = require('../lib/http/wallet'); function CLI() { this.config = new Config('bcoin'); this.config.load({ argv: true, env: true }); this.config.open('bcoin.conf'); this.argv = this.config.argv; this.client = null; this.wallet = null; } CLI.prototype.log = function log(json) { if (typeof json === 'string') return console.log.apply(console, arguments); console.log(JSON.stringify(json, null, 2)); }; CLI.prototype.getInfo = async function getInfo() { var info = await this.client.getInfo(); this.log(info); }; CLI.prototype.getWallets = async function getWallets() { var wallets = await this.client.getWallets(); this.log(wallets); }; CLI.prototype.createWallet = async function createWallet() { var options = { id: this.config.str(0) }; var wallet; options.type = this.config.str('type'); options.master = this.config.str('master'); options.mnemonic = this.config.str('mnemonic'); options.m = this.config.num('m'); options.n = this.config.num('n'); options.witness = this.config.bool('witness'); options.passphrase = this.config.str('passphrase'); if (this.config.has('watch')) { options.watchOnly = true; options.accountKey = this.config.str('watch'); } wallet = await this.client.createWallet(options); this.log(wallet); }; CLI.prototype.getMaster = async function getMaster() { var master = await this.wallet.getMaster(); this.log(master); }; CLI.prototype.getKey = async function getKey() { var address = this.config.str(0); var key = await this.wallet.getKey(address); this.log(key); }; CLI.prototype.getWIF = async function getWIF() { var address = this.config.str(0); var key = await this.wallet.getWIF(address, this.config.str('passphrase')); this.log(key.privateKey); }; CLI.prototype.addSharedKey = async function addSharedKey() { var key = this.config.str(0); await this.wallet.addSharedKey(this.config.str('account'), key); this.log('Added key.'); }; CLI.prototype.removeSharedKey = async function removeSharedKey() { var key = this.config.str(0); await this.wallet.removeSharedKey(this.config.str('account'), key); this.log('Removed key.'); }; CLI.prototype.getSharedKeys = async function getSharedKeys() { var acct = this.config.str([0, 'account']); var account = await this.wallet.getAccount(acct); this.log(account.keys); }; CLI.prototype.getAccount = async function getAccount() { var acct = this.config.str([0, 'account']); var account = await this.wallet.getAccount(acct); this.log(account); }; CLI.prototype.createAccount = async function createAccount() { var name = this.config.str(0); var options = {}; var account; options.type = this.config.str('type'); options.m = this.config.num('m'); options.n = this.config.num('n'); options.witness = this.config.bool('witness'); options.accountKey = this.config.str('watch'); account = await this.wallet.createAccount(name, options); this.log(account); }; CLI.prototype.createAddress = async function createAddress() { var account = this.config.str([0, 'account']); var addr = await this.wallet.createAddress(account); this.log(addr); }; CLI.prototype.createChange = async function createChange() { var account = this.config.str([0, 'account']); var addr = await this.wallet.createChange(account); this.log(addr); }; CLI.prototype.createNested = async function createNested() { var account = this.config.str([0, 'account']); var addr = await this.wallet.createNested(account); this.log(addr); }; CLI.prototype.getAccounts = async function getAccounts() { var accounts = await this.wallet.getAccounts(); this.log(accounts); }; CLI.prototype.getWallet = async function getWallet() { var info = await this.wallet.getInfo(); this.log(info); }; CLI.prototype.getTX = async function getTX() { var hash = this.config.str(0); var txs, tx; if (util.isBase58(hash)) { txs = await this.client.getTXByAddress(hash); this.log(txs); return; } tx = await this.client.getTX(hash); if (!tx) { this.log('TX not found.'); return; } this.log(tx); }; CLI.prototype.getBlock = async function getBlock() { var hash = this.config.str(0); var block; if (hash.length !== 64) hash = +hash; block = await this.client.getBlock(hash); if (!block) { this.log('Block not found.'); return; } this.log(block); }; CLI.prototype.getCoin = async function getCoin() { var hash = this.config.str(0); var index = this.config.num(1); var coins, coin; if (util.isBase58(hash)) { coins = await this.client.getCoinsByAddress(hash); this.log(coins); return; } coin = await this.client.getCoin(hash, index); if (!coin) { this.log('Coin not found.'); return; } this.log(coin); }; CLI.prototype.getWalletHistory = async function getWalletHistory() { var txs = await this.wallet.getHistory(this.config.str('account')); this.log(txs); }; CLI.prototype.getWalletPending = async function getWalletPending() { var txs = await this.wallet.getPending(this.config.str('account')); this.log(txs); }; CLI.prototype.getWalletCoins = async function getWalletCoins() { var coins = await this.wallet.getCoins(this.config.str('account')); this.log(coins); }; CLI.prototype.listenWallet = async function listenWallet() { var self = this; await this.wallet.open(); this.wallet.on('tx', function(details) { self.log('TX:'); self.log(details); }); this.wallet.on('confirmed', function(details) { self.log('TX confirmed:'); self.log(details); }); this.wallet.on('unconfirmed', function(details) { self.log('TX unconfirmed:'); self.log(details); }); this.wallet.on('conflict', function(details) { self.log('TX conflict:'); self.log(details); }); 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); }); return await this.wallet.onDisconnect(); }; CLI.prototype.getBalance = async function getBalance() { var balance = await this.wallet.getBalance(this.config.str('account')); this.log(balance); }; CLI.prototype.getMempool = async function getMempool() { var txs = await this.client.getMempool(); this.log(txs); }; CLI.prototype.sendTX = async function sendTX() { var output = {}; var options, tx; if (this.config.has('script')) { output.script = this.config.str('script'); output.value = this.config.str(['value', 0]); } else { output.address = this.config.str(['address', 0]); output.value = this.config.str(['value', 1]); } options = { account: this.config.str('account'), passphrase: this.config.str('passphrase'), outputs: [output], smart: this.config.bool('smart'), rate: this.config.str('rate'), subtractFee: this.config.bool('subtract-fee') }; tx = await this.wallet.send(options); this.log(tx); }; CLI.prototype.createTX = async function createTX() { var output = {}; var options, tx; if (this.config.has('script')) { output.script = this.config.str('script'); output.value = this.config.str(['value', 0]); } else { output.address = this.config.str(['address', 0]); output.value = this.config.str(['value', 1]); } options = { account: this.config.str('account'), passphrase: this.config.str('passphrase'), outputs: [output], smart: this.config.bool('smart'), rate: this.config.str('rate') }; tx = await this.wallet.createTX(options); this.log(tx); }; CLI.prototype.signTX = async function signTX() { var options = {}; var raw = this.config.str(['tx', 0]); var tx; options.passphrase = this.config.str('passphrase'); tx = await this.wallet.sign(raw, options); this.log(tx); }; CLI.prototype.zapWallet = async function zapWallet() { var age = this.config.num('age', 72 * 60 * 60); await this.wallet.zap(this.config.str('account'), age); this.log('Zapped!'); }; CLI.prototype.broadcast = async function broadcast() { var raw = this.config.str([0, 'tx']); var tx = await this.client.broadcast(raw); this.log('Broadcasted:'); this.log(tx); }; CLI.prototype.viewTX = async function viewTX() { var raw = this.config.str([0, 'tx']); var tx = await this.wallet.fill(raw); this.log(tx); }; CLI.prototype.getDetails = async function getDetails() { var hash = this.config.str(0); var details = await this.wallet.getTX(hash); this.log(details); }; CLI.prototype.getWalletBlocks = async function getWalletBlocks() { var blocks = await this.wallet.getBlocks(); this.log(blocks); }; CLI.prototype.getWalletBlock = async function getWalletBlock() { var height = this.config.num(0); var block = await this.wallet.getBlock(height); this.log(block); }; CLI.prototype.retoken = async function retoken() { var result = await this.wallet.retoken(); this.log(result); }; CLI.prototype.rescan = async function rescan() { var height = this.config.num(0); if (!util.isUInt32(height)) height = null; await this.client.rescan(height); this.log('Rescanning...'); }; CLI.prototype.reset = async function reset() { var hash = this.config.str(0); if (hash.length !== 64) hash = +hash; await this.client.reset(hash); this.log('Chain has been reset.'); }; CLI.prototype.resend = async function resend() { await this.client.resend(); this.log('Resending...'); }; CLI.prototype.resendWallet = async function resendWallet() { await this.wallet.resend(); this.log('Resending...'); }; CLI.prototype.backup = async function backup() { var path = this.config.str(0); await this.client.backup(path); this.log('Backup complete.'); }; CLI.prototype.importKey = async function importKey() { var key = this.config.str(0); var account = this.config.str('account'); if (!key) throw new Error('No key for import.'); if (util.isBase58(key)) { await this.wallet.importPrivate(account, key); this.log('Imported private key.'); return; } if (util.isHex(key)) { await this.wallet.importPublic(account, key); this.log('Imported public key.'); return; } throw new Error('Bad key for import.'); }; CLI.prototype.importAddress = async function importKey() { var address = this.config.str(0); var account = this.config.str('account'); await this.wallet.importAddress(account, address); this.log('Imported address.'); }; CLI.prototype.lock = async function lock() { await this.wallet.lock(); this.log('Locked.'); }; CLI.prototype.unlock = async function unlock() { var passphrase = this.config.str(0); var timeout = this.config.num(1); await this.wallet.unlock(passphrase, timeout); this.log('Unlocked.'); }; CLI.prototype.rpc = async function rpc() { var method = this.argv.shift(); var params = []; var i, arg, param, result; for (i = 0; i < this.argv.length; i++) { arg = this.argv[i]; try { param = JSON.parse(arg); } catch (e) { param = arg; } params.push(param); } try { result = await this.client.rpc.execute(method, params); } catch (e) { if (e.type === 'RPCError') { this.log(e.message); return; } throw e; } this.log(result); }; CLI.prototype.handleWallet = async function handleWallet() { this.wallet = new Wallet({ uri: this.config.str(['url', 'uri']), apiKey: this.config.str('api-key'), network: this.config.str('network'), id: this.config.str('id', 'primary'), token: this.config.str('token') }); switch (this.argv.shift()) { case 'listen': return await this.listenWallet(); case 'get': return await this.getWallet(); case 'master': return await this.getMaster(); case 'shared': if (this.argv[0] === 'add') { this.argv.shift(); return await this.addSharedKey(); } if (this.argv[0] === 'remove') { this.argv.shift(); return await this.removeSharedKey(); } if (this.argv[0] === 'list') this.argv.shift(); return await this.getSharedKeys(); case 'balance': return await this.getBalance(); case 'history': return await this.getWalletHistory(); case 'pending': return await this.getWalletPending(); case 'coins': return await this.getWalletCoins(); case 'account': if (this.argv[0] === 'list') { this.argv.shift(); return await this.getAccounts(); } if (this.argv[0] === 'create') { this.argv.shift(); return await this.createAccount(); } if (this.argv[0] === 'get') this.argv.shift(); return await this.getAccount(); case 'address': return await this.createAddress(); case 'change': return await this.createChange(); case 'nested': return await this.createNested(); case 'retoken': return await this.retoken(); case 'sign': return await this.signTX(); case 'mktx': return await this.createTX(); case 'send': return await this.sendTX(); case 'zap': return await this.zapWallet(); case 'tx': return await this.getDetails(); case 'blocks': return await this.getWalletBlocks(); case 'block': return await this.getWalletBlock(); case 'view': return await this.viewTX(); case 'import': return await this.importKey(); case 'watch': return await this.importAddress(); case 'key': return await this.getKey(); case 'dump': return await this.getWIF(); case 'lock': return await this.lock(); case 'unlock': return await this.unlock(); case 'resend': return await this.resendWallet(); default: this.log('Unrecognized command.'); this.log('Commands:'); this.log(' $ listen: Listen for events.'); this.log(' $ get: View wallet.'); this.log(' $ master: View wallet master key.'); this.log(' $ shared add [xpubkey]: Add key to wallet.'); this.log(' $ shared remove [xpubkey]: Remove key from wallet.'); this.log(' $ balance: Get wallet balance.'); this.log(' $ history: View TX history.'); this.log(' $ pending: View pending TXs.'); this.log(' $ coins: View wallet coins.'); this.log(' $ account list: List account names.'); this.log(' $ account create [account-name]: Create account.'); this.log(' $ account get [account-name]: Get account details.'); this.log(' $ address: Derive new address.'); this.log(' $ change: Derive new change address.'); this.log(' $ nested: Derive new nested address.'); this.log(' $ retoken: Create new api key.'); this.log(' $ send [address] [value]: Send transaction.'); this.log(' $ mktx [address] [value]: Create transaction.'); this.log(' $ sign [tx-hex]: Sign transaction.'); this.log(' $ zap [age?]: Zap pending wallet TXs.'); this.log(' $ tx [hash]: View transaction details.'); this.log(' $ blocks: List wallet blocks.'); this.log(' $ block [height]: View wallet block.'); this.log(' $ view [tx-hex]: Parse and view transaction.'); this.log(' $ import [wif|hex]: Import private or public key.'); this.log(' $ watch [address]: Import an address.'); this.log(' $ key [address]: Get wallet key by address.'); this.log(' $ dump [address]: Get wallet key WIF by address.'); this.log(' $ lock: Lock wallet.'); this.log(' $ unlock [passphrase] [timeout?]: Unlock wallet.'); this.log(' $ resend: Resend pending transactions.'); this.log('Other Options:'); this.log(' --passphrase [passphrase]: For signing and account creation.'); this.log(' --account [account-name]: Account name.'); return; } }; CLI.prototype.handleNode = async function handleNode() { this.client = new Client({ uri: this.config.str(['url', 'uri']), apiKey: this.config.str('api-key'), network: this.config.str('network') }); switch (this.argv.shift()) { case 'info': return await this.getInfo(); case 'wallets': return await this.getWallets(); case 'mkwallet': return await this.createWallet(); case 'broadcast': return await this.broadcast(); case 'mempool': return await this.getMempool(); case 'tx': return await this.getTX(); case 'coin': return await this.getCoin(); case 'block': return await this.getBlock(); case 'rescan': return await this.rescan(); case 'reset': return await this.reset(); case 'resend': return await this.resend(); case 'backup': return await this.backup(); case 'rpc': return await this.rpc(); default: this.log('Unrecognized command.'); this.log('Commands:'); this.log(' $ info: Get server info.'); this.log(' $ wallets: List all wallets.'); this.log(' $ wallet create [id]: Create wallet.'); this.log(' $ broadcast [tx-hex]: Broadcast transaction.'); this.log(' $ mempool: Get mempool snapshot.'); this.log(' $ tx [hash/address]: View transactions.'); this.log(' $ coin [hash+index/address]: View coins.'); this.log(' $ block [hash/height]: View block.'); this.log(' $ rescan [height]: Rescan for transactions.'); this.log(' $ reset [height/hash]: Reset chain to desired block.'); this.log(' $ resend: Resend pending transactions.'); this.log(' $ backup [path]: Backup the wallet db.'); this.log(' $ rpc [command] [args]: Execute RPC command.'); return; } }; CLI.prototype.open = async function open() { switch (this.argv[0]) { case 'w': case 'wallet': this.argv.shift(); if (this.argv[0] === 'create') { this.argv[0] = 'mkwallet'; return await this.handleNode(); } return await this.handleWallet(); default: return await this.handleNode(); } }; CLI.prototype.destroy = function destroy() { if (this.wallet) this.wallet.client.destroy(); if (this.client) this.client.destroy(); return Promise.resolve(); }; async function main() { var cli = new CLI(); await cli.open(); await cli.destroy(); } main().then(process.exit).catch(function(err) { console.error(err.stack + ''); return process.exit(1); });