#!/usr/bin/env node 'use strict'; var config = require('../lib/node/config'); var utils = require('../lib/utils/utils'); var co = require('../lib/utils/co'); var Client = require('../lib/http/client'); var Wallet = require('../lib/http/wallet'); var main; 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; } 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.createWallet = co(function* createWallet() { var options = { id: this.argv[0] }; var wallet; if (this.config.type) options.type = this.config.type; if (this.config.master) options.master = this.config.master; if (this.config.mnemonic) options.master = this.config.mnemonic; if (this.config.m) options.m = this.config.m >>> 0; if (this.config.n) options.n = this.config.n >>> 0; if (this.config.witness != null) options.witness = !!this.config.witness; if (this.config.passphrase) options.passphrase = this.config.passphrase; if (this.config.watch) { options.watchOnly = true; options.accountKey = this.config.watch; } wallet = yield this.client.createWallet(options); this.log(wallet); }); CLI.prototype.addKey = co(function* addKey() { var key = this.argv[0]; yield this.wallet.addKey(this.config.account, key); this.log('Added key.'); }); CLI.prototype.removeKey = co(function* removeKey() { var key = this.argv[0]; yield this.wallet.removeKey(this.config.account, key); this.log('Removed key.'); }); CLI.prototype.getAccount = co(function* getAccount() { var account = this.argv[0] || this.config.account; yield this.wallet.getAccount(account); this.log(account); }); CLI.prototype.createAccount = co(function* createAccount() { var name = this.argv[0]; var account = yield this.wallet.createAccount(name); this.log(account); }); CLI.prototype.createAddress = co(function* createAddress() { var account = this.argv[0]; var addr = yield this.wallet.createAddress(account); this.log(addr); }); CLI.prototype.createNested = co(function* createNested() { var account = this.argv[0]; var addr = yield this.wallet.createNested(account); this.log(addr); }); CLI.prototype.getAccounts = co(function* getAccounts() { var accounts = yield this.wallet.getAccounts(); this.log(accounts); }); CLI.prototype.getWallet = co(function* getWallet() { var info = yield this.wallet.getInfo(); this.log(info); }); CLI.prototype.getTX = co(function* getTX() { var hash = this.argv[0]; var txs, tx; if (utils.isBase58(hash)) { txs = yield this.client.getTXByAddress(hash); this.log(txs); return; } tx = yield this.client.getTX(hash); if (!tx) { this.log('TX not found.'); return; } this.log(tx); }); CLI.prototype.getBlock = co(function* getBlock() { var hash = this.argv[0]; var block; if (hash.length !== 64) hash = +hash; block = yield this.client.getBlock(hash); if (!block) { this.log('Block not found.'); return; } this.log(block); }); CLI.prototype.getCoin = co(function* getCoin() { var hash = this.argv[0]; var index = this.argv[1]; var coins, coin; if (utils.isBase58(hash)) { coins = yield this.client.getCoinsByAddress(hash); this.log(coins); return; } coin = yield this.client.getCoin(hash, index); if (!coin) { this.log('Coin not found.'); return; } this.log(coin); }); CLI.prototype.getWalletHistory = co(function* getWalletHistory() { var txs = yield this.wallet.getHistory(this.config.account); this.log(txs); }); CLI.prototype.listenWallet = function listenWallet() { var self = this; 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 new Promise(function() {}); }; CLI.prototype.getBalance = co(function* getBalance() { var balance = yield this.wallet.getBalance(this.config.account); this.log(balance); }); CLI.prototype.getMempool = co(function* getMempool() { var txs = yield this.client.getMempool(); this.log(txs); }); CLI.prototype.sendTX = co(function* sendTX() { var output = {}; var options, tx; if (this.config.script) { output.script = this.config.script; output.value = utils.satoshi(this.config.value || this.argv[0]); } else { output.address = this.config.address || this.argv[0]; output.value = utils.satoshi(this.config.value || this.argv[1]); } options = { account: this.config.account, passphrase: this.config.passphrase, outputs: [output] }; tx = yield this.wallet.send(options); this.log(tx); }); CLI.prototype.createTX = co(function* createTX() { var output = {}; var options, tx; if (this.config.script) { output.script = this.config.script; output.value = utils.satoshi(this.config.value || this.argv[0]); } else { output.address = this.config.address || this.argv[0]; output.value = utils.satoshi(this.config.value || this.argv[1]); } options = { account: this.config.account, passphrase: this.config.passphrase, outputs: [output] }; tx = yield this.wallet.createTX(options); this.log(tx); }); CLI.prototype.signTX = co(function* signTX() { var options = { passphrase: this.config.passphrase }; var raw = options.tx || this.argv[0]; var tx = yield this.wallet.sign(raw, options); this.log(tx); }); CLI.prototype.zapWallet = co(function* zapWallet() { var age = (this.config.age >>> 0) || 72 * 60 * 60; yield this.wallet.zap(this.config.account, age); this.log('Zapped!'); }); CLI.prototype.broadcast = co(function* broadcast() { var raw = this.argv[0] || this.config.tx; var tx = yield this.client.broadcast(raw); this.log('Broadcasted:'); this.log(tx); }); CLI.prototype.viewTX = co(function* viewTX() { var raw = this.argv[0] || this.config.tx; var tx = yield this.wallet.fill(raw); this.log(tx); }); CLI.prototype.getDetails = co(function* getDetails() { var hash = this.argv[0]; var details = yield this.wallet.getTX(hash); this.log(details); }); CLI.prototype.retoken = co(function* retoken() { var result = yield this.wallet.retoken(); this.log(result); }); CLI.prototype.rescan = co(function* rescan() { var hash = this.argv[0]; if (hash.length !== 64) hash = +hash; yield this.client.rescan(hash); this.log('Rescanning...'); }); CLI.prototype.zap = co(function* zap() { var age = +this.argv[0] || 72 * 3600; yield this.client.zap(age); this.log('Zapped.'); }); CLI.prototype.backup = co(function* backup() { var path = this.argv[0]; yield this.client.backup(path); this.log('Backup complete.'); }); CLI.prototype.importKey = co(function* importKey() { var key = this.argv[0]; if (!key) throw new Error('No key for import.'); if (utils.isBase58(key)) { yield this.wallet.importPrivate(key); this.log('Imported private key.'); return; } if (utils.isHex(key)) { yield this.wallet.importPublic(key); this.log('Imported public key.'); return; } throw new Error('Bad key for import.'); }); CLI.prototype.importAddress = co(function* importKey() { var address = this.argv[0]; yield this.wallet.importAddress(address); this.log('Imported address.'); }); CLI.prototype.lock = co(function* lock() { yield this.wallet.lock(); this.log('Locked.'); }); CLI.prototype.unlock = co(function* unlock() { var passphrase = this.argv[0]; var timeout = +this.argv[1] || null; yield this.wallet.unlock(passphrase, timeout); this.log('Unlocked.'); }); CLI.prototype.rpc = co(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); } result = yield this.client.rpc.call(method, params); this.log(result); }); CLI.prototype.handleWallet = co(function* handleWallet() { var options = { id: this.config.id || 'primary', token: this.config.token }; this.wallet = new Wallet({ uri: this.config.url || this.config.uri, apiKey: this.config.apikey, network: this.config.network }); yield this.wallet.open(options); switch (this.argv.shift()) { case 'listen': return yield this.listenWallet(); case 'get': return yield this.getWallet(); case 'addkey': return yield this.addKey(); case 'rmkey': return yield this.removeKey(); case 'balance': return yield this.getBalance(); case 'history': return yield this.getWalletHistory(); case 'account': if (this.argv[0] === 'list') { this.argv.shift(); return yield this.getAccounts(); } if (this.argv[0] === 'create') { this.argv.shift(); return yield this.createAccount(); } if (this.argv[0] === 'get') this.argv.shift(); return yield this.getAccount(); case 'address': return yield this.createAddress(); case 'nested': return yield this.createNested(); case 'retoken': return yield this.retoken(); case 'sign': return yield this.signTX(); case 'mktx': return yield this.createTX(); case 'send': return yield this.sendTX(); case 'zap': return yield this.zapWallet(); case 'tx': return yield this.getDetails(); case 'view': return yield this.viewTX(); case 'import': return yield this.importKey(); case 'watch': return yield this.importAddress(); case 'lock': return yield this.lock(); case 'unlock': return yield this.unlock(); default: this.log('Unrecognized command.'); this.log('Commands:'); this.log(' $ listen: Listen for events.'); this.log(' $ get: View wallet.'); this.log(' $ addkey [xpubkey]: Add key to wallet.'); this.log(' $ rmkey [xpubkey]: Remove key from wallet.'); this.log(' $ balance: Get wallet balance.'); this.log(' $ history: View wallet TX history.'); 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(' $ 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(' $ 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(' $ lock: Lock wallet.'); this.log(' $ unlock [passphrase] [timeout?]: Unlock wallet.'); this.log('Other Options:'); this.log(' --passphrase [passphrase]: For signing and account creation.'); this.log(' --account [account-name]: Account name.'); return; } }); CLI.prototype.handleNode = co(function* handleNode() { var info; this.client = new Client({ uri: this.config.url || this.config.uri, apiKey: this.config.apikey, network: this.config.network }); info = yield this.client.getInfo(); switch (this.argv.shift()) { case 'mkwallet': return yield this.createWallet(); case 'broadcast': return yield this.broadcast(); case 'mempool': return yield this.getMempool(); case 'tx': return yield this.getTX(); case 'coin': return yield this.getCoin(); case 'block': return yield this.getBlock(); case 'rescan': return yield this.rescan(); case 'zap': return yield this.zap(); case 'backup': return yield this.backup(); case 'rpc': return yield this.rpc(); default: this.log('Unrecognized command.'); this.log('Commands:'); 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/hash]: Rescan for transactions.'); this.log(' $ zap [age?]: Zap stale transactions from walletdb.'); this.log(' $ backup [path]: Backup the wallet db.'); this.log(' $ rpc [command] [args]: Execute RPC command.'); return; } }); CLI.prototype.open = co(function* open() { switch (this.argv[0]) { case 'w': case 'wallet': this.argv.shift(); if (this.argv[0] === 'create') { this.argv[0] = 'mkwallet'; return yield this.handleNode(); } return yield this.handleWallet(); default: return yield this.handleNode(); } }); CLI.prototype.destroy = function destroy() { if (this.wallet && !this.wallet.client.loading) this.wallet.client.destroy(); if (this.client && !this.client.loading) this.client.destroy(); return Promise.resolve(); }; main = co(function* main() { var cli = new CLI(); yield cli.open(); yield cli.destroy(); }); main().then(process.exit).catch(function(err) { console.error(err.stack + ''); return process.exit(1); });