From 2dc2c488d95adae93ed379cc37fcf5f4e6df5f51 Mon Sep 17 00:00:00 2001 From: Nodari Chkuaselidze Date: Mon, 16 Jan 2023 22:41:18 +0400 Subject: [PATCH 1/3] client: Import hs-client into the project. --- bin/hsd-cli | 266 ++++- bin/hsd-rpc | 23 + bin/hsw-cli | 719 +++++++++++- bin/hsw-rpc | 23 + docs/release-files.md | 59 +- lib/client/index.js | 16 + lib/client/node.js | 343 ++++++ lib/client/wallet.js | 1561 ++++++++++++++++++++++++++ lib/hsd.js | 5 + lib/wallet/client.js | 2 +- lib/wallet/nodeclient.js | 2 +- package-lock.json | 31 +- package.json | 2 +- scripts/gen-hsclient.js | 200 ++++ test/auction-rpc-test.js | 2 +- test/http-test.js | 2 +- test/interactive-swap-test.js | 2 +- test/node-http-test.js | 2 +- test/node-rpc-test.js | 2 +- test/wallet-accounts-auction-test.js | 2 +- test/wallet-http-test.js | 2 +- test/wallet-importname-test.js | 2 +- test/wallet-rpc-test.js | 2 +- test/wallet-test.js | 2 +- 24 files changed, 3211 insertions(+), 61 deletions(-) create mode 100755 bin/hsd-rpc create mode 100755 bin/hsw-rpc create mode 100644 lib/client/index.js create mode 100644 lib/client/node.js create mode 100644 lib/client/wallet.js create mode 100755 scripts/gen-hsclient.js diff --git a/bin/hsd-cli b/bin/hsd-cli index 46792ae78..4dda1bccc 100755 --- a/bin/hsd-cli +++ b/bin/hsd-cli @@ -1,5 +1,267 @@ #!/usr/bin/env node - 'use strict'; +'use strict'; - require('hs-client/bin/hsd-cli'); +const Config = require('bcfg'); +const {NodeClient} = require('../lib/client'); + +// 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: 12037, + testnet: 13037, + regtest: 14037, + simnet: 15037 +}; + +const HELP = ` +Commands: + $ block [hash/height]: View block. + $ broadcast [tx-hex]: Broadcast transaction. + $ coin [hash+index/address]: View coins. + $ header [hash/height]: View block header. + $ help: Show help message. + $ info: Get server info. + $ mempool: Get mempool snapshot. + $ reset [height/hash]: Reset chain to desired block. + $ rpc [command] [args]: Execute RPC command. + $ tx [hash/address]: View transactions. + +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': 'api-key', + 's': 'ssl', + 'h': 'httphost', + 'p': 'httpport' + } + }); + + this.config.load({ + argv: true, + env: true + }); + + this.config.open('hsd.conf'); + + this.argv = this.config.argv; + this.network = this.config.str('network', 'main'); + + this.client = new NodeClient({ + 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'), + limit: this.config.uint('limit') + }); + } + + log(json) { + if (typeof json === 'string') + return console.log.apply(console, arguments); + return console.log(JSON.stringify(json, null, 2)); + } + + async getInfo() { + const info = await this.client.getInfo(); + this.log(info); + } + + async getTX() { + const hash = this.config.str(0, ''); + + if (hash.length !== 64) { + const txs = await this.client.getTXByAddress(hash); + this.log(txs); + return; + } + + const tx = await this.client.getTX(hash); + + if (!tx) { + this.log('TX not found.'); + return; + } + + this.log(tx); + } + + async getBlock() { + let hash = this.config.str(0, ''); + + if (hash.length !== 64) + hash = parseInt(hash, 10); + + const block = await this.client.getBlock(hash); + + if (!block) { + this.log('Block not found.'); + return; + } + + this.log(block); + } + + async getBlockHeader() { + let hash = this.config.str(0, ''); + + if (hash.length !== 64) + hash = parseInt(hash, 10); + + const header = await this.client.getBlockHeader(hash); + + if (!header) { + this.log('Block header not found.'); + return; + } + + this.log(header); + } + + async getCoin() { + const hash = this.config.str(0, ''); + const index = this.config.uint(1); + + if (hash.length !== 64) { + const coins = await this.client.getCoinsByAddress(hash); + this.log(coins); + return; + } + + const coin = await this.client.getCoin(hash, index); + + if (!coin) { + this.log('Coin not found.'); + return; + } + + this.log(coin); + } + + async getMempool() { + const txs = await this.client.getMempool(); + + this.log(txs); + } + + async broadcast() { + const raw = this.config.str([0, 'tx']); + const tx = await this.client.broadcast(raw); + + this.log('Broadcasted:'); + this.log(tx); + } + + async reset() { + let hash = this.config.str(0); + + if (hash.length !== 64) + hash = parseInt(hash, 10); + + await this.client.reset(hash); + + this.log('Chain has been reset.'); + } + + 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 open() { + switch (this.argv.shift()) { + case 'block': + await this.getBlock(); + break; + case 'broadcast': + await this.broadcast(); + break; + case 'coin': + await this.getCoin(); + break; + case 'header': + await this.getBlockHeader(); + break; + case 'help': + process.stdout.write(HELP + '\n'); + break; + case 'info': + await this.getInfo(); + break; + case 'mempool': + await this.getMempool(); + break; + case 'reset': + await this.reset(); + break; + case 'rpc': + await this.rpc(); + break; + case 'tx': + await this.getTX(); + break; + default: + process.stdout.write('Unrecognized command.\n'); + process.stdout.write(HELP + '\n'); + break; + } + } + + async destroy() { + if (this.client && this.client.opened) + await this.client.close(); + } +} + +(async () => { + const cli = new CLI(); + await cli.open(); + await cli.destroy(); +})().catch((err) => { + console.error(err.stack); + process.exit(1); +}); diff --git a/bin/hsd-rpc b/bin/hsd-rpc new file mode 100755 index 000000000..3c262d4c6 --- /dev/null +++ b/bin/hsd-rpc @@ -0,0 +1,23 @@ +#!/bin/sh + +# NOTE: This is part of generated `hs-client`. + +rl=0 + +if ! type perl > /dev/null 2>& 1; then + if uname | grep -i 'darwin' > /dev/null; then + echo 'hsd-rpc requires perl to start on OSX.' >& 2 + exit 1 + fi + rl=1 +fi + +if test $rl -eq 1; then + file=$(readlink -f "$0") +else + file=$(perl -MCwd -e "print Cwd::realpath('$0')") +fi + +dir=$(dirname "$file") + +exec "${dir}/hsd-cli" rpc "$@" diff --git a/bin/hsw-cli b/bin/hsw-cli index b3650c5ad..163fb9763 100755 --- a/bin/hsw-cli +++ b/bin/hsw-cli @@ -1,5 +1,720 @@ #!/usr/bin/env node - 'use strict'; +'use strict'; - require('hs-client/bin/hsw-cli'); +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 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': 'api-key', + '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 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 account = this.config.str('account'); + const txs = await this.wallet.getHistory(account); + + this.log(txs); + } + + async getWalletPending() { + const account = this.config.str('account'); + const txs = await this.wallet.getPending(account); + + 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] === '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); +}); diff --git a/bin/hsw-rpc b/bin/hsw-rpc new file mode 100755 index 000000000..4e06573a7 --- /dev/null +++ b/bin/hsw-rpc @@ -0,0 +1,23 @@ +#!/bin/sh + +# NOTE: This is part of generated `hs-client`. + +rl=0 + +if ! type perl > /dev/null 2>& 1; then + if uname | grep -i 'darwin' > /dev/null; then + echo 'hsw-rpc requires perl to start on OSX.' >& 2 + exit 1 + fi + rl=1 +fi + +if test $rl -eq 1; then + file=$(readlink -f "$0") +else + file=$(perl -MCwd -e "print Cwd::realpath('$0')") +fi + +dir=$(dirname "$file") + +exec "${dir}/hsw-cli" rpc "$@" diff --git a/docs/release-files.md b/docs/release-files.md index a98b74d10..e1e2ba6ca 100644 --- a/docs/release-files.md +++ b/docs/release-files.md @@ -1,29 +1,32 @@ -Releasing hsd -============= +Releasing hsd and hs-client +=========================== This document contains information about bundling, signing and distributing the files. -`hsd` is distributed through several platforms: `github`, `npm`, `brew`. +`hsd/hs-client` is distributed through several platforms: `github`, `npm`, `brew`. -- [Deploying to github (tag)](#deploying-to-github-tag) - * [Major, minor and patches](#major-minor-and-patches) - + [Major](#major) - + [Minor, Patch](#minor-patch) -- [Deploying to npm](#deploying-to-npm) - * [Deploying latest version, minor and patches included](#deploying-latest-version-minor-and-patches-included) - * [Deploying support versions (previous and life-support)](#deploying-support-versions-previous-and-life-support) -- [Deploying to homebrew](#deploying-to-homebrew) -- [Deploying to handshake.org](#deploying-to-handshakeorg) - * [Building tarball](#building-tarball) - * [Signing and upload](#signing-and-upload) +- [hsd](#hsd) + * [Deploying to github (tag)](#deploying-to-github-tag) + + [Major, minor and patches](#major-minor-and-patches) + - [Major](#major) + - [Minor, Patch](#minor-patch) + * [Deploying to npm](#deploying-to-npm) + + [Deploying latest version, minor and patches included](#deploying-latest-version-minor-and-patches-included) + + [Deploying support versions (previous and life-support)](#deploying-support-versions-previous-and-life-support) + * [Deploying to homebrew](#deploying-to-homebrew) + * [Deploying to handshake.org](#deploying-to-handshakeorg) + + [Building tarball](#building-tarball) + + [Signing and upload](#signing-and-upload) +- [hs-client](#hs-client) +# hsd ## Deploying to github (tag) This does not need many additional actions as we use github as our primary @@ -119,6 +122,33 @@ and create PR with the relevant updates to the `download/index.html` and - Update `download/index.html` with new links. - Create PR to the main repository. +# hs-client + Since hsd v5 `hs-client` is part of the `hsd`. Original [hs-client repo][hsclient] is now used to +publish generated content. `hs-client` version will now be strictly tied to +the `hsd` version. It is then generated from `hsd` code to release separately on +`git` and `npm`. Most of the process is done by the introduced helper script +`scripts/gen-hsclient.js`. It can help you setup `hs-client` that just needs +publishing on `git` and `npm`. It also gives instructions how to do both. + After `hsd` has been released we can also release `hs-client` from the same +commit/tag, just run: `./scripts/gen-hsclient.js` which will generate `hs-client` +package with `git` setup in `tmp` directory. You can alternatively pass +`HS_CLIENT_DIR` env variable for custom place. If generating git failed for some +reason, it will list commands that needs executing and you can proceed manually +or fix the issues and rerun the script. NOTE, that the script will never try to +publish by itself, only generate files to review locally. + - `./scripts/gen-hsclient.js` - script will also list left commands that are + necessary for publishing. + - `cd /tmp/hs-client` + - `git push -f origin master` - rewrite whole `hs-client` repo with the new content. + - `git push -f origin vVersion` - push newly generated tag to the `hs-client`. + - You can check the `gen-hsclient` output for the proper version or + - `git tag -l` to list. + - `npm publish` - this will also tag it as `latest`. If you want to tag it differently + you can do so, same as above hsd `npm publish`. + - NOTE: You can use `npm publish --dry-run` to see the details before actual + release. + + [homebrew]: https://brew.sh/ [homebrew-repo]: https://github.com/Homebrew/homebrew-core [homebrew-new-formula]: https://github.com/Homebrew/homebrew-core/pull/51014 @@ -126,3 +156,4 @@ and create PR with the relevant updates to the `download/index.html` and [homebrew-guidelines]: https://github.com/Homebrew/homebrew-core/blob/master/CONTRIBUTING.md [handshake-web]: https://github.com/handshake-org/handshake-web/ [bpkg]: https://github.com/chjj/bpkg +[hsclient]: https://github.com/handshake-org/hs-client diff --git a/lib/client/index.js b/lib/client/index.js new file mode 100644 index 000000000..d1b09377c --- /dev/null +++ b/lib/client/index.js @@ -0,0 +1,16 @@ +/*! + * client/index.js - http clients for hs + * Copyright (c) 2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +// NOTE: This is part of generated `hs-client`. + +/** + * @module client + */ + +exports.NodeClient = require('./node'); +exports.WalletClient = require('./wallet'); diff --git a/lib/client/node.js b/lib/client/node.js new file mode 100644 index 000000000..3d0bca6c4 --- /dev/null +++ b/lib/client/node.js @@ -0,0 +1,343 @@ +/*! + * client.js - http client for wallets + * Copyright (c) 2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +// NOTE: This is part of generated `hs-client`. +// Don't introduce any unnecessary dependencies to this. + +const assert = require('bsert'); +const {Client} = require('bcurl'); + +/** + * Node Client + * @alias module:client.NodeClient + * @extends {bcurl.Client} + */ + +class NodeClient extends Client { + /** + * Creat a node client. + * @param {Object?} options + */ + + constructor(options) { + super(options); + } + + /** + * Auth with server. + * @returns {Promise} + */ + + async auth() { + await this.call('auth', this.password); + await this.watchChain(); + await this.watchMempool(); + } + + /** + * Make an RPC call. + * @returns {Promise} + */ + + execute(name, params) { + return super.execute('/', name, params); + } + + /** + * Get a mempool snapshot. + * @returns {Promise} + */ + + getMempool() { + return this.get('/mempool'); + } + + /** + * Get some info about the server (network and version). + * @returns {Promise} + */ + + getInfo() { + return this.get('/'); + } + + /** + * Get coins that pertain to an address from the mempool or chain database. + * Takes into account spent coins in the mempool. + * @param {String} address + * @returns {Promise} + */ + + getCoinsByAddress(address) { + assert(typeof address === 'string'); + return this.get(`/coin/address/${address}`); + } + + /** + * Get coins that pertain to addresses from the mempool or chain database. + * Takes into account spent coins in the mempool. + * @param {String[]} addresses + * @returns {Promise} + */ + + getCoinsByAddresses(addresses) { + assert(Array.isArray(addresses)); + return this.post('/coin/address', { addresses }); + } + + /** + * Retrieve a coin from the mempool or chain database. + * Takes into account spent coins in the mempool. + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} + */ + + getCoin(hash, index) { + assert(typeof hash === 'string'); + assert((index >>> 0) === index); + return this.get(`/coin/${hash}/${index}`); + } + + /** + * Retrieve transactions pertaining to an + * address from the mempool or chain database. + * @param {String} address + * @returns {Promise} + */ + + getTXByAddress(address) { + assert(typeof address === 'string'); + return this.get(`/tx/address/${address}`); + } + + /** + * Retrieve transactions pertaining to + * addresses from the mempool or chain database. + * @param {String[]} addresses + * @returns {Promise} + */ + + getTXByAddresses(addresses) { + assert(Array.isArray(addresses)); + return this.post('/tx/address', { addresses }); + } + + /** + * Retrieve a transaction from the mempool or chain database. + * @param {Hash} hash + * @returns {Promise} + */ + + getTX(hash) { + assert(typeof hash === 'string'); + return this.get(`/tx/${hash}`); + } + + /** + * Retrieve a block from the chain database. + * @param {Hash|Number} block + * @returns {Promise} + */ + + getBlock(block) { + assert(typeof block === 'string' || typeof block === 'number'); + return this.get(`/block/${block}`); + } + + /** + * Retrieve a block header. + * @param {Hash|Number} block + * @returns {Promise} + */ + + getBlockHeader(block) { + assert(typeof block === 'string' || typeof block === 'number'); + return this.get(`/header/${block}`); + } + + /** + * Add a transaction to the mempool and broadcast it. + * @param {TX} tx + * @returns {Promise} + */ + + broadcast(tx) { + assert(typeof tx === 'string'); + return this.post('/broadcast', { tx }); + } + + /** + * Add a claim to the mempool and broadcast it. + * @param {Claim} claim + * @returns {Promise} + */ + + broadcastClaim(claim) { + assert(typeof claim === 'string'); + return this.post('/claim', { claim }); + } + + /** + * Reset the chain. + * @param {Number} height + * @returns {Promise} + */ + + reset(height) { + return this.post('/reset', { height }); + } + + /** + * Watch the blockchain. + * @private + * @returns {Promise} + */ + + watchChain() { + return this.call('watch chain'); + } + + /** + * Watch the blockchain. + * @private + * @returns {Promise} + */ + + watchMempool() { + return this.call('watch mempool'); + } + + /** + * Get chain tip. + * @returns {Promise} + */ + + getTip() { + return this.call('get tip'); + } + + /** + * Get chain entry. + * @param {Hash} hash + * @returns {Promise} + */ + + getEntry(block) { + return this.call('get entry', block); + } + + /** + * Get hashes. + * @param {Number} [start=-1] + * @param {Number} [end=-1] + * @returns {Promise} + */ + + getHashes(start, end) { + return this.call('get hashes', start, end); + } + + /** + * Send a transaction. Do not wait for promise. + * @param {TX} tx + * @returns {Promise} + */ + + send(tx) { + assert(Buffer.isBuffer(tx)); + return this.call('send', tx); + } + + /** + * Send a claim. Do not wait for promise. + * @param {Claim} claim + * @returns {Promise} + */ + + sendClaim(claim) { + assert(Buffer.isBuffer(claim)); + return this.call('send claim', claim); + } + + /** + * Get name state. + * @param {Buffer} nameHash + * @returns {Promise} + */ + + getNameStatus(nameHash) { + assert(Buffer.isBuffer(nameHash)); + return this.call('get name', nameHash); + } + + /** + * Set bloom filter. + * @param {Bloom} filter + * @returns {Promise} + */ + + setFilter(filter) { + assert(Buffer.isBuffer(filter)); + return this.call('set filter', filter); + } + + /** + * Add data to filter. + * @param {Buffer} data + * @returns {Promise} + */ + + addFilter(chunks) { + if (!Array.isArray(chunks)) + chunks = [chunks]; + + return this.call('add filter', chunks); + } + + /** + * Reset filter. + * @returns {Promise} + */ + + resetFilter() { + return this.call('reset filter'); + } + + /** + * Esimate smart fee. + * @param {Number?} blocks + * @returns {Promise} + */ + + estimateFee(blocks) { + assert(blocks == null || typeof blocks === 'number'); + return this.call('estimate fee', blocks); + } + + /** + * Rescan for any missed transactions. + * @param {Number|Hash} start - Start block. + * @returns {Promise} + */ + + rescan(start) { + if (start == null) + start = 0; + + assert(typeof start === 'number' || Buffer.isBuffer(start)); + + return this.call('rescan', start); + } +} + +/* + * Expose + */ + +module.exports = NodeClient; diff --git a/lib/client/wallet.js b/lib/client/wallet.js new file mode 100644 index 000000000..0a9bc7b84 --- /dev/null +++ b/lib/client/wallet.js @@ -0,0 +1,1561 @@ +/*! + * wallet.js - http wallet for bcoin + * Copyright (c) 2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +// NOTE: This is part of generated `hs-client`. +// Don't introduce any unnecessary dependencies to this. + +const assert = require('bsert'); +const EventEmitter = require('events'); +const {Client} = require('bcurl'); + +/** + * Wallet Client + * @alias module:client.WalletClient + * @extends {bcurl.Client} + */ + +class WalletClient extends Client { + /** + * Create a wallet client. + * @param {Object?} options + */ + + constructor(options) { + super(options); + this.wallets = new Map(); + } + + /** + * Open the client. + * @private + * @returns {Promise} + */ + + init() { + this.bind('tx', (id, details) => { + this.dispatch(id, 'tx', details); + }); + + this.bind('confirmed', (id, details) => { + this.dispatch(id, 'confirmed', details); + }); + + this.bind('unconfirmed', (id, details) => { + this.dispatch(id, 'unconfirmed', details); + }); + + this.bind('conflict', (id, details) => { + this.dispatch(id, 'conflict', details); + }); + + this.bind('updated', (id, details) => { + this.dispatch(id, 'updated', details); + }); + + this.bind('address', (id, receive) => { + this.dispatch(id, 'address', receive); + }); + + this.bind('balance', (id, balance) => { + this.dispatch(id, 'balance', balance); + }); + } + + /** + * Dispatch event. + * @private + */ + + dispatch(id, event, ...args) { + const wallet = this.wallets.get(id); + + if (wallet) + wallet.emit(event, ...args); + } + + /** + * Open the client. + * @returns {Promise} + */ + + async open() { + await super.open(); + this.init(); + } + + /** + * Close the client. + * @returns {Promise} + */ + + async close() { + await super.close(); + this.wallets = new Map(); + } + + /** + * Auth with server. + * @returns {Promise} + */ + + async auth() { + await this.call('auth', this.password); + } + + /** + * Make an RPC call. + * @returns {Promise} + */ + + execute(name, params) { + return super.execute('/', name, params); + } + + /** + * Create a wallet object. + */ + + wallet(id, token) { + return new Wallet(this, id, token); + } + + /** + * Join a wallet. + */ + + all(token) { + return this.call('join', '*', token); + } + + /** + * Leave a wallet. + */ + + none() { + return this.call('leave', '*'); + } + + /** + * Join a wallet. + */ + + join(id, token) { + return this.call('join', id, token); + } + + /** + * Leave a wallet. + */ + + leave(id) { + return this.call('leave', id); + } + + /** + * Rescan the chain. + * @param {Number} height + * @returns {Promise} + */ + + rescan(height) { + return this.post('/rescan', { height }); + } + + /** + * Resend pending transactions. + * @returns {Promise} + */ + + resend() { + return this.post('/resend'); + } + + /** + * Backup the walletdb. + * @param {String} path + * @returns {Promise} + */ + + backup(path) { + return this.post('/backup', { path }); + } + + /** + * Get list of all wallet IDs. + * @returns {Promise} + */ + + getWallets() { + return this.get('/wallet'); + } + + /** + * Create a wallet. + * @param {Object} options + * @returns {Promise} + */ + + createWallet(id, options) { + if (id == null) + throw new Error('Wallet id is required.'); + + return this.put(`/wallet/${id}`, options); + } + + /** + * Get wallet transaction history. + * @param {String} account + * @returns {Promise} + */ + + getHistory(id, account) { + return this.get(`/wallet/${id}/tx/history`, { account }); + } + + /** + * Get wallet coins. + * @param {String} account + * @returns {Promise} + */ + + getCoins(id, account) { + return this.get(`/wallet/${id}/coin`, { account }); + } + + /** + * Get all unconfirmed transactions. + * @param {String} account + * @returns {Promise} + */ + + getPending(id, account) { + return this.get(`/wallet/${id}/tx/unconfirmed`, { account }); + } + + /** + * Calculate wallet balance. + * @param {String} account + * @returns {Promise} + */ + + getBalance(id, account) { + return this.get(`/wallet/${id}/balance`, { account }); + } + + /** + * Get last N wallet transactions. + * @param {String} account + * @param {Number} limit - Max number of transactions. + * @returns {Promise} + */ + + getLast(id, account, limit) { + return this.get(`/wallet/${id}/tx/last`, { account, limit }); + } + + /** + * Get wallet transactions by timestamp range. + * @param {String} account + * @param {Object} options + * @param {Number} options.start - Start time. + * @param {Number} options.end - End time. + * @param {Number?} options.limit - Max number of records. + * @param {Boolean?} options.reverse - Reverse order. + * @returns {Promise} + */ + + getRange(id, account, options) { + return this.get(`/wallet/${id}/tx/range`, { + account: account, + start: options.start, + end: options.end, + limit: options.limit, + reverse: options.reverse + }); + } + + /** + * Get transaction (only possible if the transaction + * is available in the wallet history). + * @param {Hash} hash + * @returns {Promise} + */ + + getTX(id, hash) { + return this.get(`/wallet/${id}/tx/${hash}`); + } + + /** + * Get wallet blocks. + * @param {Number} height + * @returns {Promise} + */ + + getBlocks(id) { + return this.get(`/wallet/${id}/block`); + } + + /** + * Get wallet block. + * @param {Number} height + * @returns {Promise} + */ + + getBlock(id, height) { + return this.get(`/wallet/${id}/block/${height}`); + } + + /** + * Get unspent coin (only possible if the transaction + * is available in the wallet history). + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} + */ + + getCoin(id, hash, index) { + return this.get(`/wallet/${id}/coin/${hash}/${index}`); + } + + /** + * Get name state for the given name. + * {@see hsd.NameState} + * @param {String} id + * @param {String} name + * @returns {Promise} + */ + + getName(id, name) { + return this.get(`/wallet/${id}/name/${name}`); + } + + /** + * Get name state for all names + * that the wallet is managing. + * {@see hsd.NameState} + * @param {String} id + * @returns {Promise} + */ + + getNames(id) { + return this.get(`/wallet/${id}/name`); + } + + /** + * Get bids, reveals and name state + * for the given name. + * {@see hsd.NameState} + * {@see hsd.BlindBid} + * {@see hsd.BidReveal} + * @param {String} id + * @param {String} name + * @returns {Promise} + */ + + getAuctionByName(id, name) { + return this.get(`/wallet/${id}/auction/${name}`); + } + + /** + * Get bids, reveals and name state + * for all names the wallet manages. + * {@see hsd.NameState} + * {@see hsd.BlindBid} + * {@see hsd.BidReveal} + * @param {String} id + * @param {Object} options + * @returns {Promise} + */ + + getAuctions(id, options) { + return this.get(`/wallet/${id}/auction`, options); + } + + /** + * Get bids for a given name. + * {@see hsd.BlindBid} + * @param {String} id + * @param {String?} name + * @param {Object?} options + * @param {Boolean?} options.own + * @returns {Promise} + */ + + getBidsByName(id, name, options) { + return this.get(`/wallet/${id}/bid/${name}`, options); + } + + /** + * Get bids for all names. + * the wallet manages. + * {@see hsd.BlindBid} + * @param {String} id + * @param {Object?} options + * @param {Boolean?} options.own + * @returns {Promise} + */ + + getBids(id, options) { + return this.get(`/wallet/${id}/bid`, options); + } + + /** + * Get wallet reveal for a given name. + * {@see hsd.BidReveal} + * @param {String} id + * @param {String?} name + * @param {Object?} options + * @param {Boolean?} options.own + * @returns {Promise} + */ + + getRevealsByName(id, name, options) { + return this.get(`/wallet/${id}/reveal/${name}`, options); + } + + /** + * Get wallet reveals for all names + * the wallet manages. + * {@see hsd.BidReveal} + * @param {String} id + * @param {Object?} options + * @param {Boolean?} options.own + * @returns {Promise} + */ + + getReveals(id, options) { + return this.get(`/wallet/${id}/reveal`, options); + } + + /** + * Get name resource. + * {@see hsd.Resource} + * @param {String} id + * @param {String} name + * @returns {Promise} + */ + + getResource(id, name) { + return this.get(`/wallet/${id}/resource/${name}`); + } + + /* + * Deterministically regenerate a bid's nonce. + * @param {String} id + * @param {String} name + * @param {Object} options + * @param {String} options.address + * @param {Number} options.bid + * @returns {Promise} + */ + + getNonce(id, name, options) { + return this.get(`/wallet/${id}/nonce/${name}`, options); + } + + /** + * @param {Number} now - Current time. + * @param {Number} age - Age delta. + * @returns {Promise} + */ + + zap(id, account, age) { + return this.post(`/wallet/${id}/zap`, { account, age }); + } + + /** + * @param {Number} id + * @param {Hash} hash + * @returns {Promise} + */ + + abandon(id, hash) { + return this.del(`/wallet/${id}/tx/${hash}`); + } + + /** + * Create a transaction, fill. + * @param {Object} options + * @returns {Promise} + */ + + createTX(id, options) { + return this.post(`/wallet/${id}/create`, options); + } + + /** + * Create pre-signed bid and reveal txs, + * fill, and optionally sign and broadcast. + * @param {Object} options + * @param {String} options.name + * @param {Number} options.bid + * @param {Number} options.lockup + * @param {String} options.passphrase + * @param {Boolean} options.sign + * @param {Boolean} options.broadcastBid + * @returns {Promise} + */ + + createAuctionTxs(id, options) { + return this.post(`/wallet/${id}/auction`, options); + } + + /** + * Create a transaction, fill, sign, and broadcast. + * @param {Object} options + * @param {String} options.address + * @param {Amount} options.value + * @returns {Promise} + */ + + send(id, options) { + return this.post(`/wallet/${id}/send`, options); + } + + /** + * Create open transaction. + * @param {String} id + * @param {Object} options + * @returns {Promise} + */ + + createOpen(id, options) { + return this.post(`/wallet/${id}/open`, options); + } + + /** + * Create bid transaction. + * @param {String} id + * @param {Object} options + * @returns {Promise} + */ + + createBid(id, options) { + return this.post(`/wallet/${id}/bid`, options); + } + + /** + * Create reveal transaction. + * @param {String} id + * @param {Object} options + * @returns {Promise} + */ + + createReveal(id, options) { + return this.post(`/wallet/${id}/reveal`, options); + } + + /** + * Create redeem transaction. + * @param {String} id + * @param {Object} options + * @returns {Promise} + */ + + createRedeem(id, options) { + return this.post(`/wallet/${id}/redeem`, options); + } + + /** + * Create update transaction. + * @param {String} id + * @param {Object} options + * @returns {Promise} + */ + + createUpdate(id, options) { + return this.post(`/wallet/${id}/update`, options); + } + + /** + * Create renewal transaction. + * @param {String} id + * @param {Object} options + * @returns {Promise} + */ + + createRenewal(id, options) { + return this.post(`/wallet/${id}/renewal`, options); + } + + /** + * Create transfer transaction. + * @param {String} id + * @param {Object} options + * @returns {Promise} + */ + + createTransfer(id, options) { + return this.post(`/wallet/${id}/transfer`, options); + } + + /** + * Create cancel transaction. + * @param {String} id + * @param {Object} options + * @returns {Promise} + */ + + createCancel(id, options) { + return this.post(`/wallet/${id}/cancel`, options); + } + + /** + * Create finalize transaction. + * @param {String} id + * @param {Object} options + * @returns {Promise} + */ + + createFinalize(id, options) { + return this.post(`/wallet/${id}/finalize`, options); + } + + /** + * Create revoke transaction. + * @param {String} id + * @param {Object} options + * @returns {Promise} + */ + + createRevoke(id, options) { + return this.post(`/wallet/${id}/revoke`, options); + } + + /** + * Sign a transaction. + * @param {Object} options + * @returns {Promise} + */ + + sign(id, options) { + return this.post(`/wallet/${id}/sign`, options); + } + + /** + * Get the raw wallet JSON. + * @returns {Promise} + */ + + getInfo(id) { + return this.get(`/wallet/${id}`); + } + + /** + * Get wallet accounts. + * @returns {Promise} - Returns Array. + */ + + getAccounts(id) { + return this.get(`/wallet/${id}/account`); + } + + /** + * Get wallet master key. + * @returns {Promise} + */ + + getMaster(id) { + return this.get(`/wallet/${id}/master`); + } + + /** + * Get wallet account. + * @param {String} account + * @returns {Promise} + */ + + getAccount(id, account) { + return this.get(`/wallet/${id}/account/${account}`); + } + + /** + * Create account. + * @param {String} name + * @param {Object} options + * @returns {Promise} + */ + + createAccount(id, name, options) { + return this.put(`/wallet/${id}/account/${name}`, options); + } + + /** + * Create address. + * @param {Object} options + * @returns {Promise} + */ + + createAddress(id, account) { + return this.post(`/wallet/${id}/address`, { account }); + } + + /** + * Create change address. + * @param {Object} options + * @returns {Promise} + */ + + createChange(id, account) { + return this.post(`/wallet/${id}/change`, { account }); + } + + /** + * Change or set master key`s passphrase. + * @param {String|Buffer} passphrase + * @param {(String|Buffer)?} old + * @returns {Promise} + */ + + setPassphrase(id, passphrase, old) { + return this.post(`/wallet/${id}/passphrase`, { passphrase, old }); + } + + /** + * Generate a new token. + * @param {(String|Buffer)?} passphrase + * @returns {Promise} + */ + + retoken(id, passphrase) { + return this.post(`/wallet/${id}/retoken`, { + passphrase + }); + } + + /** + * Import private key. + * @param {Number|String} account + * @param {String} key + * @returns {Promise} + */ + + importPrivate(id, account, privateKey, passphrase) { + return this.post(`/wallet/${id}/import`, { + account, + privateKey, + passphrase + }); + } + + /** + * Import public key. + * @param {Number|String} account + * @param {String} key + * @returns {Promise} + */ + + importPublic(id, account, publicKey) { + return this.post(`/wallet/${id}/import`, { + account, + publicKey + }); + } + + /** + * Import address. + * @param {Number|String} account + * @param {String} address + * @returns {Promise} + */ + + importAddress(id, account, address) { + return this.post(`/wallet/${id}/import`, { account, address }); + } + + /** + * Lock a coin. + * @param {String} hash + * @param {Number} index + * @returns {Promise} + */ + + lockCoin(id, hash, index) { + return this.put(`/wallet/${id}/locked/${hash}/${index}`); + } + + /** + * Unlock a coin. + * @param {String} hash + * @param {Number} index + * @returns {Promise} + */ + + unlockCoin(id, hash, index) { + return this.del(`/wallet/${id}/locked/${hash}/${index}`); + } + + /** + * Get locked coins. + * @returns {Promise} + */ + + getLocked(id) { + return this.get(`/wallet/${id}/locked`); + } + + /** + * Lock wallet. + * @returns {Promise} + */ + + lock(id) { + return this.post(`/wallet/${id}/lock`); + } + + /** + * Unlock wallet. + * @param {String} passphrase + * @param {Number} timeout + * @returns {Promise} + */ + + unlock(id, passphrase, timeout) { + return this.post(`/wallet/${id}/unlock`, { passphrase, timeout }); + } + + /** + * Get wallet key. + * @param {String} address + * @returns {Promise} + */ + + getKey(id, address) { + return this.get(`/wallet/${id}/key/${address}`); + } + + /** + * Get wallet key WIF dump. + * @param {String} address + * @param {String?} passphrase + * @returns {Promise} + */ + + getWIF(id, address, passphrase) { + return this.get(`/wallet/${id}/wif/${address}`, { passphrase }); + } + + /** + * Add a public account key to the wallet for multisig. + * @param {String} account + * @param {String} key - Account (bip44) key (base58). + * @returns {Promise} + */ + + addSharedKey(id, account, accountKey) { + return this.put(`/wallet/${id}/shared-key`, { account, accountKey }); + } + + /** + * Remove a public account key to the wallet for multisig. + * @param {String} account + * @param {String} key - Account (bip44) key (base58). + * @returns {Promise} + */ + + removeSharedKey(id, account, accountKey) { + return this.del(`/wallet/${id}/shared-key`, { account, accountKey }); + } + + /** + * Resend wallet transactions. + * @returns {Promise} + */ + + resendWallet(id) { + return this.post(`/wallet/${id}/resend`); + } +} + +/** + * Wallet Instance + * @extends {EventEmitter} + */ + +class Wallet extends EventEmitter { + /** + * Create a wallet client. + * @param {Object?} options + */ + + constructor(parent, id, token) { + super(); + this.parent = parent; + this.client = parent.clone(); + this.client.token = token; + this.id = id; + this.token = token; + } + + /** + * Open wallet. + * @returns {Promise} + */ + + async open() { + await this.parent.join(this.id, this.token); + this.parent.wallets.set(this.id, this); + } + + /** + * Close wallet. + * @returns {Promise} + */ + + async close() { + await this.parent.leave(this.id); + this.parent.wallets.delete(this.id); + } + + /** + * Get wallet transaction history. + * @param {String} account + * @returns {Promise} + */ + + getHistory(account) { + return this.client.getHistory(this.id, account); + } + + /** + * Get wallet coins. + * @param {String} account + * @returns {Promise} + */ + + getCoins(account) { + return this.client.getCoins(this.id, account); + } + + /** + * Get all unconfirmed transactions. + * @param {String} account + * @returns {Promise} + */ + + getPending(account) { + return this.client.getPending(this.id, account); + } + + /** + * Calculate wallet balance. + * @param {String} account + * @returns {Promise} + */ + + getBalance(account) { + return this.client.getBalance(this.id, account); + } + + /** + * Get last N wallet transactions. + * @param {String} account + * @param {Number} limit - Max number of transactions. + * @returns {Promise} + */ + + getLast(account, limit) { + return this.client.getLast(this.id, account, limit); + } + + /** + * Get wallet transactions by timestamp range. + * @param {String} account + * @param {Object} options + * @param {Number} options.start - Start time. + * @param {Number} options.end - End time. + * @param {Number?} options.limit - Max number of records. + * @param {Boolean?} options.reverse - Reverse order. + * @returns {Promise} + */ + + getRange(account, options) { + return this.client.getRange(this.id, account, options); + } + + /** + * Get transaction (only possible if the transaction + * is available in the wallet history). + * @param {Hash} hash + * @returns {Promise} + */ + + getTX(hash) { + return this.client.getTX(this.id, hash); + } + + /** + * Get wallet blocks. + * @param {Number} height + * @returns {Promise} + */ + + getBlocks() { + return this.client.getBlocks(this.id); + } + + /** + * Get wallet block. + * @param {Number} height + * @returns {Promise} + */ + + getBlock(height) { + return this.client.getBlock(this.id, height); + } + + /** + * Get unspent coin (only possible if the transaction + * is available in the wallet history). + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} + */ + + getCoin(hash, index) { + return this.client.getCoin(this.id, hash, index); + } + + /** + * Get name state for the given name. + * {@see hsd.NameState} + * @param {String} name + * @returns {Promise} + */ + + getName(name) { + return this.client.getName(this.id, name); + } + + /** + * Get name state for all names + * that the wallet is managing. + * {@see hsd.NameState} + * @returns {Promise} + */ + + getNames() { + return this.client.getNames(this.id); + } + + /** + * Get bids, reveals and name state + * for the given name. + * {@see hsd.NameState} + * {@see hsd.BlindBid} + * {@see hsd.BidReveal} + * @param {String} name + * @returns {Promise} + */ + + getAuctionByName(name) { + return this.client.getAuctionByName(this.id, name); + } + + /** + * Get bids, reveals and name state + * for all names the wallet manages. + * {@see hsd.NameState} + * {@see hsd.BlindBid} + * {@see hsd.BidReveal} + * @param {Object} options + * @returns {Promise} + */ + + getAuctions(options) { + return this.client.getAuctions(this.id, options); + } + + /** + * Get bids for a given name. + * {@see hsd.BlindBid} + * @param {String?} name + * @param {Object?} options + * @param {Boolean?} options.own + * @returns {Promise} + */ + + getBidsByName(name, options) { + return this.client.getBidsByName(this.id, name, options); + } + + /** + * Get bids for all names. + * the wallet manages. + * {@see hsd.BlindBid} + * @param {Object?} options + * @param {Boolean?} options.own + * @returns {Promise} + */ + + getBids(options) { + return this.client.getBids(this.id, options); + } + + /** + * Get wallet reveal for a given name. + * {@see hsd.BidReveal} + * @param {String?} name + * @param {Object?} options + * @param {Boolean?} options.own + * @returns {Promise} + */ + + getRevealsByName(name, options) { + return this.client.getRevealsByName(this.id, name, options); + } + + /** + * Get wallet reveals for all names + * the wallet manages. + * {@see hsd.BidReveal} + * @param {Object?} options + * @param {Boolean?} options.own + * @returns {Promise} + */ + + getReveals(options) { + return this.client.getReveals(this.id, options); + } + + /** + * Get name resource. + * {@see hsd.Resource} + * @param {String} name + * @returns {Promise} + */ + + getResource(name) { + return this.client.getResource(this.id, name); + } + + /* + * Deterministically regenerate a bid's nonce. + * @param {String} name + * @param {Object} options + * @param {String} options.address + * @param {Number} options.bid + * @returns {Promise} + */ + + getNonce(name, options) { + return this.client.getNonce(this.id, name, options); + } + + /** + * @param {Number} now - Current time. + * @param {Number} age - Age delta. + * @returns {Promise} + */ + + zap(account, age) { + return this.client.zap(this.id, account, age); + } + + /** + * Used to remove a pending transaction from the wallet. + * That is likely the case if it has a policy or low fee + * that prevents it from proper network propagation. + * @param {Hash} hash + * @returns {Promise} + */ + + abandon(hash) { + return this.client.abandon(this.id, hash); + } + + /** + * Create a transaction, fill. + * @param {Object} options + * @returns {Promise} + */ + + createTX(options) { + return this.client.createTX(this.id, options); + } + + /** + * Create pre-signed bid and reveal txs, + * fill, and optionally sign and broadcast. + * @param {Object} options + * @param {String} options.name + * @param {Number} options.bid + * @param {Number} options.lockup + * @param {String} options.passphrase + * @param {Boolean} options.sign + * @param {Boolean} options.broadcastBid + * @returns {Promise} + */ + + createAuctionTxs(options) { + return this.client.createAuctionTxs(this.id, options); + } + + /** + * Create a transaction, fill, sign, and broadcast. + * @param {Object} options + * @param {String} options.address + * @param {Amount} options.value + * @returns {Promise} + */ + + send(options) { + return this.client.send(this.id, options); + } + + /** + * Create open transaction. + * @param {Object} options + * @returns {Promise} + */ + + createOpen(options) { + return this.client.createOpen(this.id, options); + } + + /** + * Create bid transaction. + * @param {Object} options + * @returns {Promise} + */ + + createBid(options) { + return this.client.createBid(this.id, options); + } + + /** + * Create reveal transaction. + * @param {Object} options + * @returns {Promise} + */ + + createReveal(options) { + return this.client.createReveal(this.id, options); + } + + /** + * Create redeem transaction. + * @param {Object} options + * @returns {Promise} + */ + + createRedeem(options) { + return this.client.createRedeem(this.id, options); + } + + /** + * Create update transaction. + * @param {Object} options + * @returns {Promise} + */ + + createUpdate(options) { + return this.client.createUpdate(this.id, options); + } + + /** + * Create renewal transaction. + * @param {Object} options + * @returns {Promise} + */ + + createRenewal(options) { + return this.client.createRenewal(this.id, options); + } + + /** + * Create transfer transaction. + * @param {Object} options + * @returns {Promise} + */ + + createTransfer(options) { + return this.client.createTransfer(this.id, options); + } + + /** + * Create cancel transaction. + * @param {Object} options + * @returns {Promise} + */ + + createCancel(options) { + return this.client.createCancel(this.id, options); + } + + /** + * Create finalize transaction. + * @param {Object} options + * @returns {Promise} + */ + + createFinalize(options) { + return this.client.createFinalize(this.id, options); + } + + /** + * Create revoke transaction. + * @param {Object} options + * @returns {Promise} + */ + + createRevoke(options) { + return this.client.createRevoke(this.id, options); + } + + /** + * Sign a transaction. + * @param {Object} options + * @returns {Promise} + */ + + sign(options) { + return this.client.sign(this.id, options); + } + + /** + * Get the raw wallet JSON. + * @returns {Promise} + */ + + getInfo() { + return this.client.getInfo(this.id); + } + + /** + * Get wallet accounts. + * @returns {Promise} - Returns Array. + */ + + getAccounts() { + return this.client.getAccounts(this.id); + } + + /** + * Get wallet master key. + * @returns {Promise} + */ + + getMaster() { + return this.client.getMaster(this.id); + } + + /** + * Get wallet account. + * @param {String} account + * @returns {Promise} + */ + + getAccount(account) { + return this.client.getAccount(this.id, account); + } + + /** + * Create account. + * @param {String} name + * @param {Object} options + * @returns {Promise} + */ + + createAccount(name, options) { + if (name == null) + throw new Error('Account name is required.'); + + return this.client.createAccount(this.id, name, options); + } + + /** + * Create address. + * @param {Object} options + * @returns {Promise} + */ + + createAddress(account) { + return this.client.createAddress(this.id, account); + } + + /** + * Create change address. + * @param {Object} options + * @returns {Promise} + */ + + createChange(account) { + return this.client.createChange(this.id, account); + } + + /** + * Change or set master key`s passphrase. + * @param {String|Buffer} passphrase + * @param {(String|Buffer)?} old + * @returns {Promise} + */ + + setPassphrase(passphrase, old) { + return this.client.setPassphrase(this.id, passphrase, old); + } + + /** + * Generate a new token. + * @param {(String|Buffer)?} passphrase + * @returns {Promise} + */ + + async retoken(passphrase) { + const result = await this.client.retoken(this.id, passphrase); + + assert(result); + assert(typeof result.token === 'string'); + + this.token = result.token; + + return result; + } + + /** + * Import private key. + * @param {Number|String} account + * @param {String} key + * @returns {Promise} + */ + + importPrivate(account, privateKey, passphrase) { + return this.client.importPrivate(this.id, account, privateKey, passphrase); + } + + /** + * Import public key. + * @param {Number|String} account + * @param {String} key + * @returns {Promise} + */ + + importPublic(account, publicKey) { + return this.client.importPublic(this.id, account, publicKey); + } + + /** + * Import address. + * @param {Number|String} account + * @param {String} address + * @returns {Promise} + */ + + importAddress(account, address) { + return this.client.importAddress(this.id, account, address); + } + + /** + * Lock a coin. + * @param {String} hash + * @param {Number} index + * @returns {Promise} + */ + + lockCoin(hash, index) { + return this.client.lockCoin(this.id, hash, index); + } + + /** + * Unlock a coin. + * @param {String} hash + * @param {Number} index + * @returns {Promise} + */ + + unlockCoin(hash, index) { + return this.client.unlockCoin(this.id, hash, index); + } + + /** + * Get locked coins. + * @returns {Promise} + */ + + getLocked() { + return this.client.getLocked(this.id); + } + + /** + * Lock wallet. + * @returns {Promise} + */ + + lock() { + return this.client.lock(this.id); + } + + /** + * Unlock wallet. + * @param {String} passphrase + * @param {Number} timeout + * @returns {Promise} + */ + + unlock(passphrase, timeout) { + return this.client.unlock(this.id, passphrase, timeout); + } + + /** + * Get wallet key. + * @param {String} address + * @returns {Promise} + */ + + getKey(address) { + return this.client.getKey(this.id, address); + } + + /** + * Get wallet key WIF dump. + * @param {String} address + * @param {String?} passphrase + * @returns {Promise} + */ + + getWIF(address, passphrase) { + return this.client.getWIF(this.id, address, passphrase); + } + + /** + * Add a public account key to the wallet for multisig. + * @param {String} account + * @param {String} key - Account (bip44) key (base58). + * @returns {Promise} + */ + + addSharedKey(account, accountKey) { + return this.client.addSharedKey(this.id, account, accountKey); + } + + /** + * Remove a public account key to the wallet for multisig. + * @param {String} account + * @param {String} key - Account (bip44) key (base58). + * @returns {Promise} + */ + + removeSharedKey(account, accountKey) { + return this.client.removeSharedKey(this.id, account, accountKey); + } + + /** + * Resend wallet transactions. + * @returns {Promise} + */ + + resend() { + return this.client.resendWallet(this.id); + } +} + +/* + * Expose + */ + +module.exports = WalletClient; diff --git a/lib/hsd.js b/lib/hsd.js index e518d326d..14abb159b 100644 --- a/lib/hsd.js +++ b/lib/hsd.js @@ -56,6 +56,11 @@ hsd.define('blockchain', './blockchain'); hsd.define('Chain', './blockchain/chain'); hsd.define('ChainEntry', './blockchain/chainentry'); +// Client +hsd.define('client', './client'); +hsd.define('WalletClient', './client/wallet'); +hsd.define('NodeClient', './client/node'); + // Coins hsd.define('coins', './coins'); hsd.define('Coins', './coins/coins'); diff --git a/lib/wallet/client.js b/lib/wallet/client.js index c73f14e9c..92bb9a9f8 100644 --- a/lib/wallet/client.js +++ b/lib/wallet/client.js @@ -7,7 +7,7 @@ 'use strict'; const assert = require('bsert'); -const {NodeClient} = require('hs-client'); +const NodeClient = require('../client/node'); const TX = require('../primitives/tx'); const Coin = require('../primitives/coin'); const NameState = require('../covenants/namestate'); diff --git a/lib/wallet/nodeclient.js b/lib/wallet/nodeclient.js index 7144c3073..40594d03e 100644 --- a/lib/wallet/nodeclient.js +++ b/lib/wallet/nodeclient.js @@ -175,7 +175,7 @@ class NodeClient extends AsyncEmitter { // `data` is ignored because pool.spvFilter === walletDB.filter // and therefore is already updated. // Argument is kept here to be consistent with API in - // wallet/client.js (hs-client NodeClient) and wallet/nullclient.js + // wallet/client.js (client/node.js) and wallet/nullclient.js this.node.pool.queueFilterLoad(); } diff --git a/package-lock.json b/package-lock.json index 541f4a222..eef139423 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "bcfg": "~0.1.7", "bcrypto": "~5.4.0", + "bcurl": "^0.2.0", "bdb": "~1.4.0", "bdns": "~0.1.5", "bevent": "~0.1.5", @@ -33,7 +34,6 @@ "bval": "~0.1.6", "bweb": "~0.1.11", "goosig": "~0.10.0", - "hs-client": "~0.0.13", "n64": "~0.2.10", "urkel": "~1.0.2" }, @@ -423,25 +423,6 @@ "node": ">=8.0.0" } }, - "node_modules/hs-client": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/hs-client/-/hs-client-0.0.13.tgz", - "integrity": "sha512-3Vm/4S0TDstbOW+OfdTeP2EQ4dolPNqMulTSr31RihwX8cX1DyT4il1Fc9STXXToXTsZuFro2WD/+1m0MWi5Ag==", - "dependencies": { - "bcfg": "~0.1.7", - "bcurl": "~0.2.0", - "bsert": "~0.0.10" - }, - "bin": { - "hsd-cli": "bin/hsd-cli", - "hsd-rpc": "bin/hsd-rpc", - "hsw-cli": "bin/hsw-cli", - "hsw-rpc": "bin/hsw-rpc" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/loady": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/loady/-/loady-0.0.5.tgz", @@ -754,16 +735,6 @@ "loady": "~0.0.5" } }, - "hs-client": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/hs-client/-/hs-client-0.0.13.tgz", - "integrity": "sha512-3Vm/4S0TDstbOW+OfdTeP2EQ4dolPNqMulTSr31RihwX8cX1DyT4il1Fc9STXXToXTsZuFro2WD/+1m0MWi5Ag==", - "requires": { - "bcfg": "~0.1.7", - "bcurl": "~0.2.0", - "bsert": "~0.0.10" - } - }, "loady": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/loady/-/loady-0.0.5.tgz", diff --git a/package.json b/package.json index 7656b074e..d9a7d5d1d 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "dependencies": { "bcfg": "~0.1.7", "bcrypto": "~5.4.0", + "bcurl": "^0.2.0", "bdb": "~1.4.0", "bdns": "~0.1.5", "bevent": "~0.1.5", @@ -44,7 +45,6 @@ "bval": "~0.1.6", "bweb": "~0.1.11", "goosig": "~0.10.0", - "hs-client": "~0.0.13", "n64": "~0.2.10", "urkel": "~1.0.2" }, diff --git a/scripts/gen-hsclient.js b/scripts/gen-hsclient.js new file mode 100755 index 000000000..35b61e665 --- /dev/null +++ b/scripts/gen-hsclient.js @@ -0,0 +1,200 @@ +#!/usr/bin/env node + +'use strict'; + +const assert = require('assert'); +const path = require('path'); +const fs = require('bfile'); +const os = require('os'); +const util = require('util'); +const cp = require('child_process'); +const exec = util.promisify(cp.exec); + +const ROOT = path.dirname(__dirname); +const HSD_PKG = require(path.join(ROOT, 'package.json')); + +const REMOTE = 'git@github.com:handshake-org/hs-client.git'; + +const INIT_HS_CLIENT_PKG = { + name: "hs-client", + description: "HSD node and wallet client", + keywords: [ + "http", + "request", + "socket.io", + "websockets" + ], + main: "./lib/client/index.js", + bin: { + "hsd-cli": "./bin/hsd-cli", + "hsd-rpc": "./bin/hsd-rpc", + "hsw-cli": "./bin/hsw-cli", + "hsw-rpc": "./bin/hsw-rpc" + }, + engines: { + node: ">=8.0.0" + } +} + +const INHERIT_PROPERTIES = [ + "version", + "license", + "repository", + "homepage", + "bugs", + "author" +]; + +const DEPENDENCIES = [ + "bcfg", + "bcurl", + "bsert" +]; + +const COPY_FILES = [ + ".npmignore", + ".gitignore", + "bin/hsd-cli", + "bin/hsw-cli", + "bin/hsd-rpc", + "bin/hsw-rpc", + "lib/client/index.js", + "lib/client/wallet.js", + "lib/client/node.js", +]; + +const README = ` +hs-client +========= + +Autogenerated from https://github.com/handshake-org/hsd. + +REST and RPC client for handshake. + +## Usage + +\`\`\` js +const {NodeClient, WalletClient} = require('hs-client'); +\`\`\` +`; + +async function ensureDir() { + if (!await fs.exists(ROOT)) + throw new Error(`${ROOT} does not exist.`); + + const {HS_CLIENT_DIR} = process.env; + + let HS_PKG = HS_CLIENT_DIR ? path.resolve(HS_CLIENT_DIR) : null; + + if (!HS_PKG) + HS_PKG = path.join(os.tmpdir(), `hs-client`); + + + if (HS_PKG.startsWith(ROOT)) + throw new Error(`hs-client needs to be outside of the hsd. ${HS_PKG}`); + + console.log('hs-client directory: ', HS_PKG); + + if (await fs.exists(HS_PKG)) { + throw new Error( + `Directory ${HS_PKG} already exists.` + + ' Please remove to proceed or choose different directory.' + ); + } + + await fs.mkdir(HS_PKG); + + return HS_PKG; +} + +async function setupPackageContent(dir) { + for (const file of COPY_FILES) { + const src = path.join(ROOT, file); + const dst = path.join(dir, file); + const dstDir = path.dirname(dst); + + if (!await fs.exists(dstDir)) + await fs.mkdirp(dstDir); + + await fs.copy(src, dst); + } + + const hsClientPkg = { + ...INIT_HS_CLIENT_PKG, + }; + + for (const name of INHERIT_PROPERTIES) { + assert(HSD_PKG[name]); + hsClientPkg[name] = HSD_PKG[name]; + } + + hsClientPkg.dependencies = {}; + for (const dep of DEPENDENCIES) { + assert(HSD_PKG.dependencies[dep], `Dependency "${dep}" not found for hsd.`); + hsClientPkg.dependencies[dep] = HSD_PKG.dependencies[dep]; + } + + await fs.writeJSON(path.join(dir, 'package.json'), hsClientPkg); + await fs.writeFile(path.join(dir, 'README.md'), README); + + return hsClientPkg; +} + +async function setupGit(dir, version) { + console.log('Setting up git: ', dir); + const commands = [ + 'git init -b master', + `git remote add origin ${REMOTE}`, + 'git add .', + `git commit --gpg-sign -m "v${version}"`, + `git tag --sign v${version} -m "v${version}"` + ]; + + const manualCommands = [ + 'git push -f origin master', + `git push -f origin v${version}` + ]; + + for (const cmd of [...commands]) { + console.log(`executing: ${cmd} in ${dir}.`); + + try { + console.log(await execCmd(cmd, dir)); + } catch (e) { + console.log(`Failed to execute: ${cmd}.`); + console.log(e.message); + console.log(' You can proceed manually'); + break; + } + + commands.shift(); + } + + for (const command of [...commands, ...manualCommands]) + console.log(`Needs exec: ${command}`); +} + +(async () => { + const HS_PKG = await ensureDir(); + const pkg = await setupPackageContent(HS_PKG); + + await setupGit(HS_PKG, pkg.version); + console.log('Needs: npm publish'); +})().catch((err) => { + console.error(err.stack); + process.exit(1); +}); + +async function execCmd(cmd, cwd, timeout = 2000) { + assert(cwd, 'CWD is required.'); + + const {stdout, stderr} = await exec(cmd, { + cwd, + timeout + }); + + if (stderr.length != 0) + throw new Error(stderr); + + return stdout; +} diff --git a/test/auction-rpc-test.js b/test/auction-rpc-test.js index e152cf343..6e27efdae 100644 --- a/test/auction-rpc-test.js +++ b/test/auction-rpc-test.js @@ -14,7 +14,7 @@ const { Network, Path } = require('..'); -const {NodeClient, WalletClient} = require('hs-client'); +const {NodeClient, WalletClient} = require('../lib/client'); const {forValue} = require('./util/common'); class TestUtil { diff --git a/test/http-test.js b/test/http-test.js index b5c6a978b..fdb1c49d3 100644 --- a/test/http-test.js +++ b/test/http-test.js @@ -21,7 +21,7 @@ const node = new FullNode({ plugins: [require('../lib/wallet/plugin')] }); -const {NodeClient, WalletClient} = require('hs-client'); +const {NodeClient, WalletClient} = require('../lib/client'); const nclient = new NodeClient({ port: network.rpcPort, diff --git a/test/interactive-swap-test.js b/test/interactive-swap-test.js index 13aa28e18..c2bfd8fb8 100644 --- a/test/interactive-swap-test.js +++ b/test/interactive-swap-test.js @@ -10,7 +10,7 @@ const Script = require('../lib/script/script'); const rules = require('../lib/covenants/rules'); const {types} = rules; const {Resource} = require('../lib/dns/resource'); -const {WalletClient} = require('hs-client'); +const WalletClient = require('../lib/client/wallet'); const network = Network.get('regtest'); diff --git a/test/node-http-test.js b/test/node-http-test.js index b7335a880..ac626e2ee 100644 --- a/test/node-http-test.js +++ b/test/node-http-test.js @@ -2,7 +2,7 @@ const assert = require('bsert'); const bio = require('bufio'); -const {NodeClient} = require('hs-client'); +const NodeClient = require('../lib/client/node'); const Network = require('../lib/protocol/network'); const FullNode = require('../lib/node/fullnode'); const Address = require('../lib/primitives/address'); diff --git a/test/node-rpc-test.js b/test/node-rpc-test.js index 1bc8c7937..f61442f2f 100644 --- a/test/node-rpc-test.js +++ b/test/node-rpc-test.js @@ -7,7 +7,7 @@ const Network = require('../lib/protocol/network'); const consensus = require('../lib/protocol/consensus'); const MemWallet = require('./util/memwallet'); const TX = require('../lib/primitives/tx'); -const {NodeClient} = require('hs-client'); +const NodeClient = require('../lib/client/node'); const TIMEOUT = 15000; const API_KEY = 'foo'; diff --git a/test/wallet-accounts-auction-test.js b/test/wallet-accounts-auction-test.js index f370a4472..892bfac94 100644 --- a/test/wallet-accounts-auction-test.js +++ b/test/wallet-accounts-auction-test.js @@ -6,7 +6,7 @@ const FullNode = require('../lib/node/fullnode'); const Address = require('../lib/primitives/address'); const rules = require('../lib/covenants/rules'); const Resource = require('../lib/dns/resource'); -const {WalletClient} = require('hs-client'); +const WalletClient = require('../lib/client/wallet'); const network = Network.get('regtest'); diff --git a/test/wallet-http-test.js b/test/wallet-http-test.js index a633214ef..9d542c47b 100644 --- a/test/wallet-http-test.js +++ b/test/wallet-http-test.js @@ -1,6 +1,6 @@ 'use strict'; -const {NodeClient, WalletClient} = require('hs-client'); +const {NodeClient, WalletClient} = require('../lib/client'); const Network = require('../lib/protocol/network'); const FullNode = require('../lib/node/fullnode'); const MTX = require('../lib/primitives/mtx'); diff --git a/test/wallet-importname-test.js b/test/wallet-importname-test.js index f6eb17e24..088a63d0e 100644 --- a/test/wallet-importname-test.js +++ b/test/wallet-importname-test.js @@ -5,7 +5,7 @@ const Network = require('../lib/protocol/network'); const FullNode = require('../lib/node/fullnode'); const Address = require('../lib/primitives/address'); const rules = require('../lib/covenants/rules'); -const {WalletClient} = require('hs-client'); +const WalletClient = require('../lib/client/wallet'); const {forValue} = require('./util/common'); const network = Network.get('regtest'); diff --git a/test/wallet-rpc-test.js b/test/wallet-rpc-test.js index 9ab9f7879..385126f34 100644 --- a/test/wallet-rpc-test.js +++ b/test/wallet-rpc-test.js @@ -1,7 +1,7 @@ 'use strict'; -const {NodeClient,WalletClient} = require('hs-client'); const assert = require('bsert'); +const {NodeClient, WalletClient} = require('../lib/client'); const FullNode = require('../lib/node/fullnode'); const Network = require('../lib/protocol/network'); const Mnemonic = require('../lib/hd/mnemonic'); diff --git a/test/wallet-test.js b/test/wallet-test.js index d6d90f48c..9f2bda81d 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -1,7 +1,7 @@ 'use strict'; const assert = require('bsert'); -const {WalletClient} = require('hs-client'); +const WalletClient = require('../lib/client/wallet'); const consensus = require('../lib/protocol/consensus'); const Network = require('../lib/protocol/network'); const util = require('../lib/utils/util'); From a349a445078b6878d7269a24f7732b5464a20c1b Mon Sep 17 00:00:00 2001 From: Nodari Chkuaselidze Date: Mon, 29 May 2023 17:53:16 +0400 Subject: [PATCH 2/3] pkg: expose hsd-rpc and hsw-rpc. --- package-lock.json | 4 +++- package.json | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index eef139423..fa8946cea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,8 +43,10 @@ "hsd": "bin/hsd", "hsd-cli": "bin/hsd-cli", "hsd-node": "bin/node", + "hsd-rpc": "bin/hsd-rpc", "hsd-spvnode": "bin/spvnode", - "hsw-cli": "bin/hsw-cli" + "hsw-cli": "bin/hsw-cli", + "hsw-rpc": "bin/hsw-rpc" }, "devDependencies": { "bmocha": "^2.1.8" diff --git a/package.json b/package.json index d9a7d5d1d..a7de47025 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,9 @@ "hs-seeder": "./bin/hs-seeder", "hs-wallet": "./bin/hsw", "hsd-cli": "./bin/hsd-cli", - "hsw-cli": "./bin/hsw-cli" + "hsd-rpc": "./bin/hsd-rpc", + "hsw-cli": "./bin/hsw-cli", + "hsw-rpc": "./bin/hsw-rpc" }, "scripts": { "build-docs": "jsdoc -c jsdoc.json", From b0661265f1e5e596071dd842d01d11312619c311 Mon Sep 17 00:00:00 2001 From: Nodari Chkuaselidze Date: Mon, 29 May 2023 19:07:53 +0400 Subject: [PATCH 3/3] scripts: Add more options to hs-client generator. --- .eslintfiles | 1 + docs/release-files.md | 8 +- scripts/gen-hsclient.js | 176 +++++++++++++++++++++++++--------------- 3 files changed, 117 insertions(+), 68 deletions(-) diff --git a/.eslintfiles b/.eslintfiles index a30ce329a..7bd79fa2b 100644 --- a/.eslintfiles +++ b/.eslintfiles @@ -11,3 +11,4 @@ bin/hsw-cli etc/genesis lib/ test/ +scripts/ diff --git a/docs/release-files.md b/docs/release-files.md index e1e2ba6ca..e673ac402 100644 --- a/docs/release-files.md +++ b/docs/release-files.md @@ -123,7 +123,7 @@ and create PR with the relevant updates to the `download/index.html` and - Create PR to the main repository. # hs-client - Since hsd v5 `hs-client` is part of the `hsd`. Original [hs-client repo][hsclient] is now used to + Since hsd v6 `hs-client` is part of the `hsd`. Original [hs-client repo][hsclient] is now used to publish generated content. `hs-client` version will now be strictly tied to the `hsd` version. It is then generated from `hsd` code to release separately on `git` and `npm`. Most of the process is done by the introduced helper script @@ -132,12 +132,12 @@ publishing on `git` and `npm`. It also gives instructions how to do both. After `hsd` has been released we can also release `hs-client` from the same commit/tag, just run: `./scripts/gen-hsclient.js` which will generate `hs-client` package with `git` setup in `tmp` directory. You can alternatively pass -`HS_CLIENT_DIR` env variable for custom place. If generating git failed for some +`DIRECTORY` as argument for custom path. If generating git failed for some reason, it will list commands that needs executing and you can proceed manually or fix the issues and rerun the script. NOTE, that the script will never try to publish by itself, only generate files to review locally. - - `./scripts/gen-hsclient.js` - script will also list left commands that are - necessary for publishing. + - `./scripts/gen-hsclient.js /tmp/hs-client` - script will also list the commands, + that needs running for publishing to the git and npm. - `cd /tmp/hs-client` - `git push -f origin master` - rewrite whole `hs-client` repo with the new content. - `git push -f origin vVersion` - push newly generated tag to the `hs-client`. diff --git a/scripts/gen-hsclient.js b/scripts/gen-hsclient.js index 35b61e665..8abe24459 100755 --- a/scripts/gen-hsclient.js +++ b/scripts/gen-hsclient.js @@ -2,65 +2,67 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const path = require('path'); -const fs = require('bfile'); const os = require('os'); const util = require('util'); const cp = require('child_process'); const exec = util.promisify(cp.exec); +const fs = require('bfile'); +const Config = require('bcfg'); const ROOT = path.dirname(__dirname); +const NAME = 'hs-client'; const HSD_PKG = require(path.join(ROOT, 'package.json')); const REMOTE = 'git@github.com:handshake-org/hs-client.git'; const INIT_HS_CLIENT_PKG = { - name: "hs-client", - description: "HSD node and wallet client", + name: 'hs-client', + description: 'HSD node and wallet client', keywords: [ - "http", - "request", - "socket.io", - "websockets" + 'http', + 'request', + 'socket.io', + 'websockets' ], - main: "./lib/client/index.js", + main: './lib/client/index.js', bin: { - "hsd-cli": "./bin/hsd-cli", - "hsd-rpc": "./bin/hsd-rpc", - "hsw-cli": "./bin/hsw-cli", - "hsw-rpc": "./bin/hsw-rpc" + 'hsd-cli': './bin/hsd-cli', + 'hsd-rpc': './bin/hsd-rpc', + 'hsw-cli': './bin/hsw-cli', + 'hsw-rpc': './bin/hsw-rpc' }, engines: { - node: ">=8.0.0" + node: '>=8.0.0' } -} +}; const INHERIT_PROPERTIES = [ - "version", - "license", - "repository", - "homepage", - "bugs", - "author" + 'version', + 'license', + 'repository', + 'homepage', + 'bugs', + 'author' ]; const DEPENDENCIES = [ - "bcfg", - "bcurl", - "bsert" + 'bcfg', + 'bcurl', + 'bsert' ]; const COPY_FILES = [ - ".npmignore", - ".gitignore", - "bin/hsd-cli", - "bin/hsw-cli", - "bin/hsd-rpc", - "bin/hsw-rpc", - "lib/client/index.js", - "lib/client/wallet.js", - "lib/client/node.js", + '.npmignore', + '.gitignore', + 'bin/hsd-cli', + 'bin/hsw-cli', + 'bin/hsd-rpc', + 'bin/hsw-rpc', + 'lib/client/index.js', + 'lib/client/wallet.js', + 'lib/client/node.js' ]; const README = ` @@ -78,33 +80,30 @@ const {NodeClient, WalletClient} = require('hs-client'); \`\`\` `; -async function ensureDir() { - if (!await fs.exists(ROOT)) - throw new Error(`${ROOT} does not exist.`); - - const {HS_CLIENT_DIR} = process.env; - - let HS_PKG = HS_CLIENT_DIR ? path.resolve(HS_CLIENT_DIR) : null; - - if (!HS_PKG) - HS_PKG = path.join(os.tmpdir(), `hs-client`); +const HELP = `Usage: gen-hsclient [DIRECTORY] [OPTIONS]... +Generate hs-client package in DIRECTORY. +OPTIONS: + --no-git - Don't setup git. + --verbose - Verbose output. + --no-steps - Don't print next steps. +`; - if (HS_PKG.startsWith(ROOT)) - throw new Error(`hs-client needs to be outside of the hsd. ${HS_PKG}`); +async function ensureDir(dir) { + dir = path.resolve(dir); - console.log('hs-client directory: ', HS_PKG); + if (dir.startsWith(ROOT)) + throw new Error(`${NAME} needs to be outside of the hsd. ${dir}`); - if (await fs.exists(HS_PKG)) { + if (await fs.exists(dir)) { throw new Error( - `Directory ${HS_PKG} already exists.` + `Directory ${dir} already exists.` + ' Please remove to proceed or choose different directory.' ); } - await fs.mkdir(HS_PKG); - - return HS_PKG; + await fs.mkdir(dir); + return dir; } async function setupPackageContent(dir) { @@ -120,7 +119,7 @@ async function setupPackageContent(dir) { } const hsClientPkg = { - ...INIT_HS_CLIENT_PKG, + ...INIT_HS_CLIENT_PKG }; for (const name of INHERIT_PROPERTIES) { @@ -140,8 +139,7 @@ async function setupPackageContent(dir) { return hsClientPkg; } -async function setupGit(dir, version) { - console.log('Setting up git: ', dir); +async function setupGit(dir, version, log) { const commands = [ 'git init -b master', `git remote add origin ${REMOTE}`, @@ -155,36 +153,86 @@ async function setupGit(dir, version) { `git push -f origin v${version}` ]; + log('Setting up git: ', dir); + for (const cmd of [...commands]) { - console.log(`executing: ${cmd} in ${dir}.`); + log(` > ${cmd} in ${dir}.`); try { - console.log(await execCmd(cmd, dir)); + log(await execCmd(cmd, dir)); } catch (e) { console.log(`Failed to execute: ${cmd}.`); console.log(e.message); - console.log(' You can proceed manually'); + console.log('You need to proceed manually.'); break; } commands.shift(); } - for (const command of [...commands, ...manualCommands]) - console.log(`Needs exec: ${command}`); + return [...commands, ...manualCommands]; } (async () => { - const HS_PKG = await ensureDir(); - const pkg = await setupPackageContent(HS_PKG); + const config = new Config('hsd', { + alias: { + 'v': 'verbose' + } + }); + + config.load({ + argv: true, + env: true + }); - await setupGit(HS_PKG, pkg.version); - console.log('Needs: npm publish'); + if (config.bool('help')) { + console.log(HELP); + process.exit(0); + } + + const dir = config.str(0, tmpdir()); + const verbose = config.bool('verbose', false); + const noGit = config.bool('no-git', false); + const noSteps = config.bool('no-steps', false); + + const log = verbose ? console.log : () => {}; + + const pkgDir = await ensureDir(dir); + + log(`Copying files to ${pkgDir}...`); + const pkg = await setupPackageContent(pkgDir); + let gitNext = null; + + if (!noGit) + gitNext = await setupGit(pkgDir, pkg.version, log); + + if (noSteps) + return; + + console.log(`Generated ${pkgDir}.`); + console.log('Next steps:'); + console.log(` $ cd ${pkgDir}`); + + if (!noGit) { + assert(gitNext); + console.log('Git Next:'); + for (const cmd of gitNext) + console.log(` $ ${cmd}`); + } + + console.log('NPM Next:'); + console.log(' $ npm publish --dry-run # check if everything is ok'); + console.log(' $ npm publish'); })().catch((err) => { + console.log('Try passing --help for help.'); console.error(err.stack); process.exit(1); }); +function tmpdir() { + return path.join(os.tmpdir(), NAME + '-' + Date.now()); +} + async function execCmd(cmd, cwd, timeout = 2000) { assert(cwd, 'CWD is required.'); @@ -193,7 +241,7 @@ async function execCmd(cmd, cwd, timeout = 2000) { timeout }); - if (stderr.length != 0) + if (stderr.length !== 0) throw new Error(stderr); return stdout;