client: Import hs-client into the project.

This commit is contained in:
Nodari Chkuaselidze 2023-01-16 22:41:18 +04:00
parent 6314c1aa08
commit 2dc2c488d9
No known key found for this signature in database
GPG key ID: B018A7BB437D1F05
24 changed files with 3211 additions and 61 deletions

View file

@ -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);
});

23
bin/hsd-rpc Executable file
View file

@ -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 "$@"

View file

@ -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);
});

23
bin/hsw-rpc Executable file
View file

@ -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 "$@"

View file

@ -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`.
<!-- markdown-toc -i release-files.md -->
<!-- toc -->
- [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)
<!-- tocstop -->
# 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

16
lib/client/index.js Normal file
View file

@ -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');

343
lib/client/node.js Normal file
View file

@ -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;

1561
lib/client/wallet.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -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');

View file

@ -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');

View file

@ -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();
}

31
package-lock.json generated
View file

@ -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",

View file

@ -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"
},

200
scripts/gen-hsclient.js Executable file
View file

@ -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;
}

View file

@ -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 {

View file

@ -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,

View file

@ -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');

View file

@ -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');

View file

@ -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';

View file

@ -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');

View file

@ -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');

View file

@ -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');

View file

@ -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');

View file

@ -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');