pending.
hsw-cli:
- hsw-cli: `history` now accepts new args on top of `--account`: `--reverse`,
`--limit`, `--after`, `--after`.
- hsw-cli: `pending` now accepts new args, same as above.
wallet-http:
- Deprecate and remove: `GET /wallet/:id/tx/range`
- Deprecate and remove: `GET /wallet/:id/tx/last`
752 lines
18 KiB
JavaScript
Executable file
752 lines
18 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
'use strict';
|
|
|
|
const Config = require('bcfg');
|
|
const {WalletClient} = require('../lib/client');
|
|
const EXP = 6;
|
|
|
|
// NOTE: This is part of generated `hs-client`.
|
|
// Don't introduce any unnecessary dependencies to this.
|
|
// This needs to be remain as is for hs-client to be simple.
|
|
|
|
const ports = {
|
|
main: 12039,
|
|
testnet: 13039,
|
|
regtest: 14039,
|
|
simnet: 15039
|
|
};
|
|
|
|
const HELP = `
|
|
Commands:
|
|
$ abandon [hash]: Abandon a transaction.
|
|
$ account create [account-name]: Create account.
|
|
$ account modify [account-name]: Set account options.
|
|
$ account get [account-name]: Get account details.
|
|
$ account list: List account names.
|
|
$ address [account-name]: Derive new address.
|
|
$ balance: Get wallet balance.
|
|
$ block [height]: View wallet block.
|
|
$ blocks: List wallet blocks.
|
|
$ change [account-name]: Derive new change address.
|
|
$ coins: View wallet coins.
|
|
$ dump [address]: Get wallet key WIF by address.
|
|
$ get: View wallet.
|
|
$ help: Show help message.
|
|
$ history: View TX history.
|
|
$ import [wif|hex]: Import private or public key.
|
|
$ key [address]: Get wallet key by address.
|
|
$ listen: Listen for events.
|
|
$ lock: Lock wallet.
|
|
$ mkauctiontxs [name] [bid] [lockup] [broadcast]: Create bid and reveal TXs.
|
|
$ mktx [address] [value]: Create transaction.
|
|
$ mkwallet [id]: Create wallet.
|
|
$ pending: View pending TXs.
|
|
$ resendwallet [id]: Resend pending transactions for a single wallet.
|
|
$ retoken: Create new api key.
|
|
$ send [address] [value]: Send transaction.
|
|
$ shared add [account-name] [xpubkey]: Add key to account.
|
|
$ shared remove [account-name] [xpubkey]: Remove key from account.
|
|
$ shared list [account-name]: List keys in account.
|
|
$ sign [tx-hex]: Sign transaction.
|
|
$ tx [hash]: View transaction details.
|
|
$ unlock [passphrase] [timeout?]: Unlock wallet.
|
|
$ view [tx-hex]: Parse and view transaction.
|
|
$ watch [address]: Import an address.
|
|
$ zap [age]: Zap pending wallet TXs.
|
|
|
|
If node is run with wallet-auth flag, then wallet commands
|
|
require authorization token.
|
|
Admin commands require admin permissions for provided authorization token:
|
|
$ backup [path]: Backup the wallet db.
|
|
$ master: View wallet master key.
|
|
$ rescan [height]: Rescan for transactions.
|
|
$ resend: Resend pending transactions for all wallets.
|
|
$ rpc [command] [args]: Execute RPC command.
|
|
$ wallets: List all wallets.
|
|
|
|
Other options:
|
|
--id [wallet id]: Wallet id.
|
|
--passphrase [passphrase]: For signing/account-creation.
|
|
--account [account-name]: Account name.
|
|
--token [token]: Wallet-specific or admin authorization token.
|
|
--api-key [key]: General API authorization key.
|
|
|
|
For additional information and a complete list of commands
|
|
visit https://hsd-dev.org/api-docs/
|
|
`;
|
|
|
|
class CLI {
|
|
constructor() {
|
|
this.config = new Config('hsd', {
|
|
suffix: 'network',
|
|
fallback: 'main',
|
|
alias: {
|
|
'n': 'network',
|
|
'u': 'url',
|
|
'uri': 'url',
|
|
'k': 'apikey',
|
|
's': 'ssl',
|
|
'h': 'httphost',
|
|
'p': 'httpport'
|
|
}
|
|
});
|
|
|
|
this.config.load({
|
|
argv: true,
|
|
env: true
|
|
});
|
|
|
|
this.config.open('hsw.conf');
|
|
|
|
this.argv = this.config.argv;
|
|
this.network = this.config.str('network', 'main');
|
|
|
|
const id = this.config.str('id', 'primary');
|
|
const token = this.config.str('token', '');
|
|
|
|
this.client = new WalletClient({
|
|
url: this.config.str('url'),
|
|
apiKey: this.config.str('api-key'),
|
|
ssl: this.config.bool('ssl'),
|
|
host: this.config.str('http-host'),
|
|
port: this.config.uint('http-port')
|
|
|| ports[this.network]
|
|
|| ports.main,
|
|
timeout: this.config.uint('timeout'),
|
|
token
|
|
});
|
|
|
|
this.wallet = this.client.wallet(id, token);
|
|
}
|
|
|
|
log(json) {
|
|
if (typeof json === 'string')
|
|
return console.log.apply(console, arguments);
|
|
return console.log(JSON.stringify(json, null, 2));
|
|
}
|
|
|
|
async getWallets() {
|
|
const wallets = await this.client.getWallets();
|
|
this.log(wallets);
|
|
}
|
|
|
|
async createWallet() {
|
|
const id = this.config.str([0, 'id']);
|
|
|
|
const options = {
|
|
type: this.config.str('type'),
|
|
master: this.config.str('master'),
|
|
mnemonic: this.config.str('mnemonic'),
|
|
m: this.config.uint('m'),
|
|
n: this.config.uint('n'),
|
|
witness: this.config.bool('witness'),
|
|
passphrase: this.config.str('passphrase'),
|
|
bip39Passphrase: this.config.str('bip39Passphrase'),
|
|
watchOnly: this.config.has('key') ? true : this.config.bool('watch'),
|
|
accountKey: this.config.str('key'),
|
|
lookahead: this.config.uint('lookahead'),
|
|
language: this.config.str('language')
|
|
};
|
|
|
|
const wallet = await this.client.createWallet(id, options);
|
|
|
|
this.log(wallet);
|
|
}
|
|
|
|
async getMaster() {
|
|
const master = await this.wallet.getMaster();
|
|
|
|
this.log(master);
|
|
}
|
|
|
|
async getKey() {
|
|
const address = this.config.str(0);
|
|
const key = await this.wallet.getKey(address);
|
|
|
|
this.log(key);
|
|
}
|
|
|
|
async getWIF() {
|
|
const address = this.config.str(0);
|
|
const passphrase = this.config.str('passphrase');
|
|
const key = await this.wallet.getWIF(address, passphrase);
|
|
|
|
if (!key) {
|
|
this.log('Key not found.');
|
|
return;
|
|
}
|
|
|
|
this.log(key.privateKey);
|
|
}
|
|
|
|
async addSharedKey() {
|
|
const key = this.config.str(0);
|
|
const account = this.config.str('account');
|
|
|
|
await this.wallet.addSharedKey(account, key);
|
|
|
|
this.log('Added key.');
|
|
}
|
|
|
|
async removeSharedKey() {
|
|
const key = this.config.str(0);
|
|
const account = this.config.str('account');
|
|
|
|
await this.wallet.removeSharedKey(account, key);
|
|
|
|
this.log('Removed key.');
|
|
}
|
|
|
|
async getSharedKeys() {
|
|
const acct = this.config.str([0, 'account']);
|
|
const account = await this.wallet.getAccount(acct);
|
|
|
|
if (!account) {
|
|
this.log('Account not found.');
|
|
return;
|
|
}
|
|
|
|
this.log(account.keys);
|
|
}
|
|
|
|
async getAccount() {
|
|
const acct = this.config.str([0, 'account']);
|
|
const account = await this.wallet.getAccount(acct);
|
|
|
|
this.log(account);
|
|
}
|
|
|
|
async createAccount() {
|
|
const name = this.config.str([0, 'name']);
|
|
|
|
const options = {
|
|
type: this.config.str('type'),
|
|
m: this.config.uint('m'),
|
|
n: this.config.uint('n'),
|
|
witness: this.config.bool('witness'),
|
|
accountKey: this.config.str('key'),
|
|
lookahead: this.config.uint('lookahead')
|
|
};
|
|
|
|
const account = await this.wallet.createAccount(name, options);
|
|
|
|
this.log(account);
|
|
}
|
|
|
|
async modifyAccount() {
|
|
const name = this.config.str([0, 'name']);
|
|
|
|
const options = {
|
|
lookahead: this.config.uint('lookahead')
|
|
};
|
|
|
|
const account = await this.wallet.modifyAccount(name, options);
|
|
|
|
this.log(account);
|
|
}
|
|
|
|
async createAddress() {
|
|
const account = this.config.str([0, 'account']);
|
|
const addr = await this.wallet.createAddress(account);
|
|
|
|
this.log(addr);
|
|
}
|
|
|
|
async createChange() {
|
|
const account = this.config.str([0, 'account']);
|
|
const addr = await this.wallet.createChange(account);
|
|
|
|
this.log(addr);
|
|
}
|
|
|
|
async getAccounts() {
|
|
const accounts = await this.wallet.getAccounts();
|
|
this.log(accounts);
|
|
}
|
|
|
|
async getWallet() {
|
|
const info = await this.wallet.getInfo();
|
|
this.log(info);
|
|
}
|
|
|
|
async getWalletHistory() {
|
|
const options = {
|
|
account: this.config.str('account'),
|
|
limit: this.config.uint('limit'),
|
|
reverse: this.config.bool('reverse'),
|
|
after: this.config.str('after'),
|
|
time: this.config.uint('time')
|
|
};
|
|
|
|
const txs = await this.wallet.getHistory(options);
|
|
|
|
this.log(txs);
|
|
}
|
|
|
|
async getWalletPending() {
|
|
const options = {
|
|
account: this.config.str('account'),
|
|
limit: this.config.uint('limit'),
|
|
reverse: this.config.bool('reverse'),
|
|
after: this.config.str('after'),
|
|
time: this.config.uint('time')
|
|
};
|
|
|
|
const txs = await this.wallet.getPending(options);
|
|
|
|
this.log(txs);
|
|
}
|
|
|
|
async getWalletCoins() {
|
|
const account = this.config.str('account');
|
|
const coins = await this.wallet.getCoins(account);
|
|
|
|
this.log(coins);
|
|
}
|
|
|
|
async listenWallet() {
|
|
await this.client.open();
|
|
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 new Promise((resolve, reject) => {
|
|
this.client.once('disconnect', resolve);
|
|
});
|
|
}
|
|
|
|
async getBalance() {
|
|
const account = this.config.str('account');
|
|
const balance = await this.wallet.getBalance(account);
|
|
|
|
this.log(balance);
|
|
}
|
|
|
|
async getMempool() {
|
|
const txs = await this.wallet.getMempool();
|
|
|
|
this.log(txs);
|
|
}
|
|
|
|
async sendTX() {
|
|
const outputs = [];
|
|
|
|
if (this.config.has('script')) {
|
|
outputs.push({
|
|
script: this.config.str('script'),
|
|
value: this.config.ufixed([0, 'value'], EXP)
|
|
});
|
|
} else {
|
|
outputs.push({
|
|
address: this.config.str([0, 'address']),
|
|
value: this.config.ufixed([1, 'value'], EXP)
|
|
});
|
|
}
|
|
|
|
const options = {
|
|
account: this.config.str('account'),
|
|
passphrase: this.config.str('passphrase'),
|
|
outputs: outputs,
|
|
smart: this.config.bool('smart'),
|
|
rate: this.config.ufixed('rate', EXP),
|
|
subtractFee: this.config.bool('subtract-fee')
|
|
};
|
|
|
|
const tx = await this.wallet.send(options);
|
|
|
|
this.log(tx);
|
|
}
|
|
|
|
async createAuctionTXs() {
|
|
const options = {
|
|
name: this.config.str([0, 'name']),
|
|
bid: this.config.ufixed([1, 'bid'], EXP),
|
|
lockup: this.config.ufixed([2, 'lockup'], EXP),
|
|
broadcastBid: this.config.bool([3, 'broadcastBid']),
|
|
passphrase: this.config.str('passphrase')
|
|
};
|
|
|
|
const txs = await this.wallet.createAuctionTXs(options);
|
|
|
|
this.log(txs);
|
|
}
|
|
|
|
async createTX() {
|
|
let output;
|
|
|
|
if (this.config.has('script')) {
|
|
output = {
|
|
script: this.config.str('script'),
|
|
value: this.config.ufixed([0, 'value'], EXP)
|
|
};
|
|
} else {
|
|
output = {
|
|
address: this.config.str([0, 'address']),
|
|
value: this.config.ufixed([1, 'value'], EXP)
|
|
};
|
|
}
|
|
|
|
const options = {
|
|
account: this.config.str('account'),
|
|
passphrase: this.config.str('passphrase'),
|
|
outputs: [output],
|
|
smart: this.config.bool('smart'),
|
|
rate: this.config.ufixed('rate', EXP),
|
|
subtractFee: this.config.bool('subtract-fee')
|
|
};
|
|
|
|
const tx = await this.wallet.createTX(options);
|
|
|
|
this.log(tx);
|
|
}
|
|
|
|
async signTX() {
|
|
const passphrase = this.config.str('passphrase');
|
|
const tx = this.config.str([0, 'tx']);
|
|
const signedTx = await this.wallet.sign({tx, passphrase});
|
|
|
|
this.log(signedTx);
|
|
}
|
|
|
|
async zapWallet() {
|
|
const age = this.config.uint([0, 'age'], 72 * 60 * 60);
|
|
const account = this.config.str('account');
|
|
|
|
await this.wallet.zap(account, age);
|
|
|
|
this.log('Zapped!');
|
|
}
|
|
|
|
async abandonTX() {
|
|
const hash = this.config.str(0);
|
|
|
|
await this.wallet.abandon(hash);
|
|
|
|
this.log('Abandoned tx: ' + hash);
|
|
}
|
|
|
|
async viewTX() {
|
|
const raw = this.config.str([0, 'tx']);
|
|
const tx = await this.wallet.fill(raw);
|
|
|
|
this.log(tx);
|
|
}
|
|
|
|
async getDetails() {
|
|
const hash = this.config.str(0);
|
|
const details = await this.wallet.getTX(hash);
|
|
|
|
this.log(details);
|
|
}
|
|
|
|
async getWalletBlocks() {
|
|
const blocks = await this.wallet.getBlocks();
|
|
this.log(blocks);
|
|
}
|
|
|
|
async getWalletBlock() {
|
|
const height = this.config.uint(0);
|
|
const block = await this.wallet.getBlock(height);
|
|
|
|
this.log(block);
|
|
}
|
|
|
|
async retoken() {
|
|
const passphrase = this.config.str('passphrase');
|
|
const result = await this.wallet.retoken(passphrase);
|
|
|
|
this.log(result);
|
|
}
|
|
|
|
async rescan() {
|
|
const height = this.config.uint(0);
|
|
|
|
await this.client.rescan(height);
|
|
|
|
this.log('Rescanning...');
|
|
}
|
|
|
|
async resend() {
|
|
await this.client.resend();
|
|
|
|
this.log('Resending...');
|
|
}
|
|
|
|
async resendWallet() {
|
|
await this.wallet.resend();
|
|
|
|
this.log('Resending...');
|
|
}
|
|
|
|
async backup() {
|
|
const path = this.config.str(0);
|
|
|
|
await this.client.backup(path);
|
|
|
|
this.log('Backup complete.');
|
|
}
|
|
|
|
async importKey() {
|
|
const key = this.config.str(0);
|
|
const account = this.config.str('account');
|
|
const passphrase = this.config.str('passphrase');
|
|
|
|
if (!key)
|
|
throw new Error('No key for import.');
|
|
|
|
if (key.length === 66 || key.length === 130) {
|
|
await this.wallet.importPublic(account, key);
|
|
this.log('Imported public key.');
|
|
return;
|
|
}
|
|
|
|
await this.wallet.importPrivate(account, key, passphrase);
|
|
|
|
this.log('Imported private key.');
|
|
}
|
|
|
|
async importAddress() {
|
|
const address = this.config.str(0);
|
|
const account = this.config.str('account');
|
|
|
|
await this.wallet.importAddress(account, address);
|
|
|
|
this.log('Imported address.');
|
|
}
|
|
|
|
async lock() {
|
|
await this.wallet.lock();
|
|
|
|
this.log('Locked.');
|
|
}
|
|
|
|
async unlock() {
|
|
const passphrase = this.config.str(0);
|
|
const timeout = this.config.uint(1);
|
|
|
|
await this.wallet.unlock(passphrase, timeout);
|
|
|
|
this.log('Unlocked.');
|
|
}
|
|
|
|
async rpc() {
|
|
const method = this.argv.shift();
|
|
if (!method) {
|
|
this.log('Missing RPC method');
|
|
return;
|
|
}
|
|
const params = [];
|
|
|
|
for (const arg of this.argv) {
|
|
let param;
|
|
try {
|
|
param = JSON.parse(arg);
|
|
} catch (e) {
|
|
param = arg;
|
|
}
|
|
params.push(param);
|
|
}
|
|
|
|
let result;
|
|
try {
|
|
result = await this.client.execute(method, params);
|
|
} catch (e) {
|
|
if (e.type === 'RPCError') {
|
|
this.log(e.message);
|
|
return;
|
|
}
|
|
throw e;
|
|
}
|
|
|
|
this.log(result);
|
|
}
|
|
|
|
async handleWallet() {
|
|
switch (this.argv.shift()) {
|
|
case 'abandon':
|
|
await this.abandonTX();
|
|
break;
|
|
case 'account':
|
|
if (this.argv[0] === 'list') {
|
|
this.argv.shift();
|
|
await this.getAccounts();
|
|
break;
|
|
}
|
|
if (this.argv[0] === 'create') {
|
|
this.argv.shift();
|
|
await this.createAccount();
|
|
break;
|
|
}
|
|
if (this.argv[0] === 'modify') {
|
|
this.argv.shift();
|
|
await this.modifyAccount();
|
|
break;
|
|
}
|
|
if (this.argv[0] === 'get')
|
|
this.argv.shift();
|
|
await this.getAccount();
|
|
break;
|
|
case 'address':
|
|
await this.createAddress();
|
|
break;
|
|
case 'backup':
|
|
await this.backup();
|
|
break;
|
|
case 'balance':
|
|
await this.getBalance();
|
|
break;
|
|
case 'block':
|
|
await this.getWalletBlock();
|
|
break;
|
|
case 'blocks':
|
|
await this.getWalletBlocks();
|
|
break;
|
|
case 'change':
|
|
await this.createChange();
|
|
break;
|
|
case 'coins':
|
|
await this.getWalletCoins();
|
|
break;
|
|
case 'dump':
|
|
await this.getWIF();
|
|
break;
|
|
case 'get':
|
|
await this.getWallet();
|
|
break;
|
|
case 'help':
|
|
process.stdout.write(HELP + '\n');
|
|
break;
|
|
case 'history':
|
|
await this.getWalletHistory();
|
|
break;
|
|
case 'import':
|
|
await this.importKey();
|
|
break;
|
|
case 'key':
|
|
await this.getKey();
|
|
break;
|
|
case 'listen':
|
|
await this.listenWallet();
|
|
break;
|
|
case 'lock':
|
|
await this.lock();
|
|
break;
|
|
case 'master':
|
|
await this.getMaster();
|
|
break;
|
|
case 'mkauctiontxs':
|
|
await this.createAuctionTXs();
|
|
break;
|
|
case 'mktx':
|
|
await this.createTX();
|
|
break;
|
|
case 'mkwallet':
|
|
await this.createWallet();
|
|
break;
|
|
case 'pending':
|
|
await this.getWalletPending();
|
|
break;
|
|
case 'rescan':
|
|
await this.rescan();
|
|
break;
|
|
case 'resend':
|
|
await this.resend();
|
|
break;
|
|
case 'resendwallet':
|
|
await this.resendWallet();
|
|
break;
|
|
case 'retoken':
|
|
await this.retoken();
|
|
break;
|
|
case 'rpc':
|
|
await this.rpc();
|
|
break;
|
|
case 'send':
|
|
await this.sendTX();
|
|
break;
|
|
case 'shared':
|
|
if (this.argv[0] === 'add') {
|
|
this.argv.shift();
|
|
await this.addSharedKey();
|
|
break;
|
|
}
|
|
if (this.argv[0] === 'remove') {
|
|
this.argv.shift();
|
|
await this.removeSharedKey();
|
|
break;
|
|
}
|
|
if (this.argv[0] === 'list')
|
|
this.argv.shift();
|
|
await this.getSharedKeys();
|
|
break;
|
|
case 'sign':
|
|
await this.signTX();
|
|
break;
|
|
case 'tx':
|
|
await this.getDetails();
|
|
break;
|
|
case 'unlock':
|
|
await this.unlock();
|
|
break;
|
|
case 'view':
|
|
await this.viewTX();
|
|
break;
|
|
case 'wallets':
|
|
await this.getWallets();
|
|
break;
|
|
case 'watch':
|
|
await this.importAddress();
|
|
break;
|
|
case 'zap':
|
|
await this.zapWallet();
|
|
break;
|
|
default:
|
|
process.stdout.write('Unrecognized command.\n');
|
|
process.stdout.write(HELP + '\n');
|
|
break;
|
|
}
|
|
}
|
|
|
|
async destroy() {
|
|
if (this.client.opened)
|
|
await this.client.close();
|
|
}
|
|
}
|
|
|
|
(async () => {
|
|
const cli = new CLI();
|
|
await cli.handleWallet();
|
|
await cli.destroy();
|
|
})().catch((err) => {
|
|
console.error(err.stack);
|
|
process.exit(1);
|
|
});
|