#!/usr/bin/env node 'use strict'; const Config = require('../lib/node/config'); const util = require('../lib/utils/util'); const Client = require('../lib/http/client'); const Wallet = require('../lib/http/wallet'); const Amount = require('../lib/btc/amount'); 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() { let info = await this.client.getInfo(); this.log(info); }; CLI.prototype.getWallets = async function getWallets() { let wallets = await this.client.getWallets(); this.log(wallets); }; CLI.prototype.createWallet = async function createWallet() { let options, wallet; options = { id: this.config.str([0, 'id']), type: this.config.str('type'), master: this.config.str('master'), mnemonic: this.config.str('mnemonic'), m: this.config.num('m'), n: this.config.num('n'), witness: this.config.bool('witness'), passphrase: this.config.str('passphrase'), watchOnly: false, accountKey: null }; if (this.config.has('watch')) { options.watchOnly = true; options.accountKey = this.config.str('watch'); } wallet = await this.client.createWallet(options); wallet.state.confirmed = Amount.btc(wallet.state.confirmed); wallet.state.unconfirmed = Amount.btc(wallet.state.unconfirmed); this.log(wallet); }; CLI.prototype.getMaster = async function getMaster() { let master = await this.wallet.getMaster(); this.log(master); }; CLI.prototype.getKey = async function getKey() { let address = this.config.str(0); let key = await this.wallet.getKey(address); this.log(key); }; CLI.prototype.getWIF = async function getWIF() { let address = this.config.str(0); let passphrase = this.config.str('passphrase'); let key = await this.wallet.getWIF(address, passphrase); this.log(key.privateKey); }; CLI.prototype.addSharedKey = async function addSharedKey() { let key = this.config.str(0); let account = this.config.str('account'); await this.wallet.addSharedKey(account, key); this.log('Added key.'); }; CLI.prototype.removeSharedKey = async function removeSharedKey() { let key = this.config.str(0); let account = this.config.str('account'); await this.wallet.removeSharedKey(account, key); this.log('Removed key.'); }; CLI.prototype.getSharedKeys = async function getSharedKeys() { let acct = this.config.str([0, 'account']); let account = await this.wallet.getAccount(acct); this.log(account.keys); }; CLI.prototype.getAccount = async function getAccount() { let acct = this.config.str([0, 'account']); let account = await this.wallet.getAccount(acct); this.log(account); }; CLI.prototype.createAccount = async function createAccount() { let name = this.config.str([0, 'name']); let options, account; options = { type: this.config.str('type'), m: this.config.num('m'), n: this.config.num('n'), witness: this.config.bool('witness'), accountKey: this.config.str('watch') }; account = await this.wallet.createAccount(name, options); this.log(account); }; CLI.prototype.createAddress = async function createAddress() { let account = this.config.str([0, 'account']); let addr = await this.wallet.createAddress(account); this.log(addr); }; CLI.prototype.createChange = async function createChange() { let account = this.config.str([0, 'account']); let addr = await this.wallet.createChange(account); this.log(addr); }; CLI.prototype.createNested = async function createNested() { let account = this.config.str([0, 'account']); let addr = await this.wallet.createNested(account); this.log(addr); }; CLI.prototype.getAccounts = async function getAccounts() { let accounts = await this.wallet.getAccounts(); this.log(accounts); }; CLI.prototype.getWallet = async function getWallet() { let info = await this.wallet.getInfo(); info.state.confirmed = Amount.btc(info.state.confirmed); info.state.unconfirmed = Amount.btc(info.state.unconfirmed); this.log(info); }; CLI.prototype.getTX = async function getTX() { let hash = this.config.str(0); let 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() { let hash = this.config.str(0); let 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() { let hash = this.config.str(0); let index = this.config.num(1); let 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() { let account = this.config.str('account'); let txs = await this.wallet.getHistory(account); this.log(txs); }; CLI.prototype.getWalletPending = async function getWalletPending() { let account = this.config.str('account'); let txs = await this.wallet.getPending(account); this.log(txs); }; CLI.prototype.getWalletCoins = async function getWalletCoins() { let account = this.config.str('account'); let coins = await this.wallet.getCoins(account); this.log(coins); }; CLI.prototype.listenWallet = async function listenWallet() { await this.wallet.open(); this.wallet.on('tx', (details) => { this.log('TX:'); this.log(details); }); this.wallet.on('confirmed', (details) => { this.log('TX confirmed:'); this.log(details); }); this.wallet.on('unconfirmed', (details) => { this.log('TX unconfirmed:'); this.log(details); }); this.wallet.on('conflict', (details) => { this.log('TX conflict:'); this.log(details); }); this.wallet.on('address', (receive) => { this.log('New addresses allocated:'); this.log(receive); }); this.wallet.on('balance', (balance) => { this.log('Balance:'); this.log(balance); }); return await this.wallet.onDisconnect(); }; CLI.prototype.getBalance = async function getBalance() { let account = this.config.str('account'); let balance = await this.wallet.getBalance(account); balance.confirmed = Amount.btc(balance.confirmed); balance.unconfirmed = Amount.btc(balance.unconfirmed); this.log(balance); }; CLI.prototype.getMempool = async function getMempool() { let txs = await this.client.getMempool(); this.log(txs); }; CLI.prototype.sendTX = async function sendTX() { let output, options, tx; if (this.config.has('script')) { output = { script: this.config.str('script'), value: this.config.amt([0, 'value']) }; } else { output = { address: this.config.str([0, 'address']), value: this.config.amt([1, 'value']) }; } options = { account: this.config.str('account'), passphrase: this.config.str('passphrase'), outputs: [output], smart: this.config.bool('smart'), rate: this.config.amt('rate'), subtractFee: this.config.bool('subtract-fee') }; tx = await this.wallet.send(options); this.log(tx); }; CLI.prototype.createTX = async function createTX() { let output, options, tx; if (this.config.has('script')) { output = { script: this.config.str('script'), value: this.config.amt([0, 'value']) }; } else { output = { address: this.config.str([0, 'address']), value: this.config.amt([1, 'value']) }; } options = { account: this.config.str('account'), passphrase: this.config.str('passphrase'), outputs: [output], smart: this.config.bool('smart'), rate: this.config.amt('rate') }; tx = await this.wallet.createTX(options); this.log(tx); }; CLI.prototype.signTX = async function signTX() { let passphrase = this.config.str('passphrase'); let raw = this.config.str([0, 'tx']); let tx = await this.wallet.sign(raw, { passphrase }); this.log(tx); }; CLI.prototype.zapWallet = async function zapWallet() { let age = this.config.num([0, 'age'], 72 * 60 * 60); await this.wallet.zap(this.config.str('account'), age); this.log('Zapped!'); }; CLI.prototype.broadcast = async function broadcast() { let raw = this.config.str([0, 'tx']); let tx = await this.client.broadcast(raw); this.log('Broadcasted:'); this.log(tx); }; CLI.prototype.viewTX = async function viewTX() { let raw = this.config.str([0, 'tx']); let tx = await this.wallet.fill(raw); this.log(tx); }; CLI.prototype.getDetails = async function getDetails() { let hash = this.config.str(0); let details = await this.wallet.getTX(hash); this.log(details); }; CLI.prototype.getWalletBlocks = async function getWalletBlocks() { let blocks = await this.wallet.getBlocks(); this.log(blocks); }; CLI.prototype.getWalletBlock = async function getWalletBlock() { let height = this.config.num(0); let block = await this.wallet.getBlock(height); this.log(block); }; CLI.prototype.retoken = async function retoken() { let result = await this.wallet.retoken(); this.log(result); }; CLI.prototype.rescan = async function rescan() { let height = this.config.num(0); await this.client.rescan(height); this.log('Rescanning...'); }; CLI.prototype.reset = async function reset() { let 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() { let path = this.config.str(0); await this.client.backup(path); this.log('Backup complete.'); }; CLI.prototype.importKey = async function importKey() { let key = this.config.str(0); let 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() { let address = this.config.str(0); let 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() { let passphrase = this.config.str(0); let timeout = this.config.num(1); await this.wallet.unlock(passphrase, timeout); this.log('Unlocked.'); }; CLI.prototype.rpc = async function rpc() { let method = this.argv.shift(); let params = []; let result; for (let arg of this.argv) { let param; 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() { let cli = new CLI(); await cli.open(); await cli.destroy(); } main().then(process.exit).catch((err) => { console.error(err.stack + ''); return process.exit(1); });