___ /\__\ /:/__/_ /::\/\__\ \/\::/ / /:/ / \/__/
Events and Sockets
To listen for and react to events in hsd, a listener must be added in the runtime script. If you are running a full node (for example) you might already be familiar with the hsd full node launch script, which instantiates a FullNode
object, adds a wallet, and begins the connection and synchronization process. The script ends with an async
function that actually starts these processes, and this is a good place to add event listeners. Using the table below you can discover which object needs to be listened on
for each event type. Notice that because the wallet is added as a plugin, its object hierarchy path is a little… awkward :-)
// Based on https://github.com/handshake-org/hsd/blob/master/bin/node
(async () => {
await node.ensure();
await node.open();
await node.connect();
node.startSync();
// add event listeners after everything is open, connected, and started
// NODE
node.on('tx', (details) => {
console.log(' -- node tx -- \n', details)
});
node.on('block', (details) => {
console.log(' -- node block -- \n', details)
});
// MEMPOOL
node.mempool.on('confirmed', (details) => {
console.log(' -- mempool confirmed -- \n', details)
});
// WALLET
node.plugins.walletdb.wdb.on('balance', (details) => {
console.log(' -- wallet balance -- \n', details)
});
node.plugins.walletdb.wdb.on('confirmed', (details) => {
console.log(' -- wallet confirmed -- \n', details)
});
node.plugins.walletdb.wdb.on('address', (details) => {
console.log(' -- wallet address -- \n', details)
});
})().catch((err) => {
console.error(err.stack);
process.exit(1);
});
Events Directory
This list is comprehensive for all Handshake transaction, wallet, and blockchain activity. Events regarding errors, socket connections and peer connections have been omitted for clarity. Notice that certain methods emit the same events but with different return objects, and not all re-emitters return everything they receive.
Event | Returns | Origin | Re-Emitters |
---|---|---|---|
tip |
ChainEntry | Chain: open() , disconnect() , reconnect() , setBestChain() , reset() |
|
connect |
ChainEntry, Block, CoinView | Chain: setBestChain() , reconnect() |
chain→FullNode (returns ChainEntry, Block only) chain→SPVNode (returns ChainEntry, Block only) SPVNode, FullNode→NodeClient (emits as block connect , returns ChainEntry, Block.txs only) |
disconnect |
ChainEntry, Headers, CoinView | Chain: reorganizeSPV() |
chain→FullNode (returns ChainEntry, Headers only) chain→SPVNode (returns ChainEntry, Headers only) SPVNode, FullNode→NodeClient (emits as block disconnect , returns ChainEntry only) |
disconnect |
ChainEntry, Block, CoinView | Chain: disconnect() |
chain→FullNode (returns ChainEntry, Block only) chain→SPVNode (returns ChainEntry, Block only) SPVNode, FullNode→NodeClient (emits as block disconnect , returns ChainEntry only) |
reconnect |
ChainEntry, Block | Chain: reconnect() |
|
reorganize |
tip (ChainEntry), competitor (ChainEntry) | Chain: reorganize() , reorganizeSPV() |
chain→FullNode chain→SPVNode |
block |
Block, ChainEntry | Chain: setBestChain() CPUMiner: _start() |
chain→Pool chain→FullNode (returns Block only) chain→SPVNode (returns Block only) |
competitor |
Block, ChainEntry | Chain: saveAlternate() |
|
bad orphan |
Error, ID | Chain: handleOrphans() Mempool: handleOrphans() |
|
resolved |
Block, ChainEntry | Chain: handleOrphans() |
|
checkpoint |
Hash, Height | Chain: verifyCheckpoint() |
|
orphan |
Block | Chain: storeOrphan() |
|
full |
Chain: maybeSync() |
chain→Pool | |
confirmed |
TX, ChainEntry | Mempool: _addBlock() |
|
confirmed |
TX, Details | TXDB: confirm() |
txdb→WalletDB (also returns Wallet) txdb→Wallet |
unconfirmed |
TX, Block | Mempool: _removeBlock() |
|
unconfirmed |
TX, Details | TXDB: disconnect() |
txdb→WalletDB (also returns Wallet) txdb→Wallet |
conflict |
TX | Mempool: _removeBlock() |
|
conflict |
TX, Details | TXDB: disconnect() |
txdb→WalletDB (also returns Wallet) txdb→Wallet |
tx |
TX, CoinView | Mempool: addEntry() |
mempool→Pool (returns TX only) mempool→pool→SPVNode (returns TX only) mempool→FullNode (returns TX only) SPVNode, FullNode→NodeClient (returns TX only) |
tx |
TX | Pool: _handleTX() (only if there is no mempool, i.e. SPV) |
pool→SPVNode |
tx |
TX, Details | TXDB: insert() |
txdb→WalletDB (also returns Wallet) txdb→Wallet |
double spend |
MempoolEntry | Mempool: removeDoubleSpends() |
|
balance |
Balance | TXDB: insert() , confirm() , disconnect() , erase() |
txdb→WalletDB (also returns Wallet) txdb→Wallet |
address |
[WalletKey] | Wallet: _add() , |
wallet→WalletDB (returns parent Wallet, [WalletKey]) |
Socket Events
Websocket connections in hsd are handled by two servers, one for Node
and one for Wallet
. Those servers each have child objects such as Chain
, Mempool
, Pool
, and WalletDB
, and relay events from them out the socket. To receive an event, the socket client must watch a channel (such as chain
, mempool
, or auth
) or join a wallet (which would be user-defined like primary
, hot-wallet
, or multisig1
). All wallets can be joined at once by joining '*'
.
Listen for Socket Events with bsock
To make a socket connection to hsd, you need to run a websocket client. Luckily the bcoin and hsd developers have developed bsock, a minimal websocket-only implementation of the socket.io protocol. By default, bsock listens on localhost
, and you only need to pass it a port number to connect to one of the hsd servers. The example below illustrates how to establish the socket connection, authenticate with your user-defined API key and then send and receive events! See the tables below for a complete list of calls and events available in hsd.
// bsock-example.js
const bsock = require('bsock');
const {Network, ChainEntry} = require('hsd');
const network = Network.get('regtest');
const apiKey = 'api-key';
nodeSocket = bsock.connect(network.rpcPort);
walletSocket = bsock.connect(network.walletPort);
nodeSocket.on('connect', async (e) => {
try {
console.log('Node - Connect event:\n', e);
// `auth` must be called before any other actions
console.log('Node - Attempting auth:\n', await nodeSocket.call('auth', apiKey));
// `watch chain` subscribes us to chain events like `block`
console.log('Node - Attempting watch chain:\n', await nodeSocket.call('watch chain'));
// Some calls simply request information from the server like an http request
console.log('Node - Attempting get tip:');
const tip = await nodeSocket.call('get tip');
console.log(ChainEntry.from(tip));
} catch (e) {
console.log('Node - Connection Error:\n', e);
}
});
// listen for new blocks
nodeSocket.bind('chain connect', (raw, txs) => {
console.log('Node - Chain Connect Event:\n', ChainEntry.fromRaw(raw));
});
walletSocket.on('connect', async (e) => {
try {
console.log('Wallet - Connect event:\n', e);
// `auth` is required before proceeding
console.log('Wallet - Attempting auth:\n', await walletSocket.call('auth', apiKey));
// here we join all wallets, but we could also just join `primary` or any other wallet
console.log('Wallet - Attempting join *:\n', await walletSocket.call('join', '*'));
} catch (e) {
console.log('Wallet - Connection Error:\n', e);
}
});
// listen for new wallet transactions
walletSocket.bind('tx', (wallet, tx) => {
console.log('Wallet - TX Event -- wallet:\n', wallet);
console.log('Wallet - TX Event -- tx:\n', tx);
});
// listen for new address events
// (only fired when current account address receives a transaction)
walletSocket.bind('address', (wallet, json) => {
console.log('Wallet - Address Event -- wallet:\n', wallet);
console.log('Wallet - Address Event -- json:\n', json);
});
To see this script in action, first start hsd however you usually do:
Run the script (this is where the event output will be printed):
Then, in a separate terminal window, run some commands to trigger the events!
Sockets made easy: hs-client
bsock
is a great low-level library for dealing with sockets, but we also have hs-client which simplifies both socket and regular http connections. A simple one-line command in the terminal can listen to all wallet events and print all the returned data:
hs-client
can also be used in a script to listen for events. If you’re already familiar with the hsd-cli API this will look very familiar. Here’s part of the script above re-written using hs-client
instead of bsock
:
const {NodeClient} = require('hs-client');
const {Network, ChainEntry} = require('hsd');
const network = Network.get('regtest');
const clientOptions = {
port: network.rpcPort,
apiKey: 'api-key'
}
const client = new NodeClient(clientOptions);
(async () => {
// bclient handles the connection, the auth, and the channel subscriptions
await client.open();
// use socket connection to request data
const tip = await client.getTip();
console.log(tip);
})();
// listen for new blocks
client.bind('chain connect', (raw) => {
console.log('Node - Chain Connect Event:\n', ChainEntry.fromRaw(raw));
});
Socket Events Directory
Wallet
All wallet events are emitted by a WalletDB
object, which may have been triggered by its parent TXDB
or Wallet
. The socket emits the event along with the wallet ID, and the same “Returns” as listed above.
Event | Returns |
---|---|
tx |
WalletID, TX Details |
confirmed |
WalletID, TX Details |
unconfirmed |
WalletID, TX Details |
conflict |
WalletID, TX Details |
balance |
WalletID, Balance |
address |
WalletID, [WalletKey] |
Node
Event | Returns | Channel | Origin | Original Event |
---|---|---|---|---|
chain connect |
ChainEntry.toRaw() | chain |
Chain | connect |
block connect |
ChainEntry.toRaw(), Block.txs | chain |
Chain | connect |
chain disconnect |
ChainEntry.toRaw() | chain |
Chain | disconnect |
block disconnect |
ChainEntry.toRaw() | chain |
Chain | disconnect |
tx |
TX.toRaw() | mempool |
Pool | tx |
Server Hooks
Certain events can also be sent back to the server from the client to request new data or trigger a server action. The client action is a “call” and the server waits with a “hook”.
Wallet
Event | Args | Returns |
---|---|---|
auth |
1. api key | null |
join |
1. wallet id 2. wallet token |
null |
leave |
1. wallet id | null |
Node
Event | Args | Returns |
---|---|---|
auth |
1. api key | null |
watch chain |
(none) | null |
unwatch chain |
(none) | null |
watch mempool |
(none) | null |
unwatch mempool |
(none) | null |
set filter |
1. Bloom filter (Buffer) | null |
get tip |
(none) | ChainEntry.toRaw() |
get entry |
1. hash | ChainEntry.toRaw() |
get hashes |
1. start (int) 2. end (int) |
[hashes] |
add filter |
1. filter ([Buffer]) | null |
reset filter |
(none) | null |
estimate fee |
1. blocks (int) | fee rate (float) |
send |
1. tx (Buffer) | null |
rescan |
1. hash | null |