1. Home
  2. Docs
  3. Handshake Guides
  4. Events and Sockets

Events and Sockets

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.

EventReturnsOriginRe-Emitters
tipChainEntryChainopen()disconnect()reconnect()setBestChain()reset()
connectChainEntryBlockCoinViewChainsetBestChain()reconnect()chainβ†’FullNode (returns ChainEntry, Block only)
chainβ†’SPVNode (returns ChainEntry, Block only)
SPVNodeFullNodeβ†’NodeClient (emits as block connect, returns ChainEntry, Block.txs only)
disconnectChainEntryHeadersCoinViewChainreorganizeSPV()chainβ†’FullNode (returns ChainEntry, Headers only)
chainβ†’SPVNode (returns ChainEntry, Headers only)
SPVNodeFullNodeβ†’NodeClient (emits as block disconnect, returns ChainEntry only)
disconnectChainEntryBlockCoinViewChaindisconnect()chainβ†’FullNode (returns ChainEntry, Block only)
chainβ†’SPVNode (returns ChainEntry, Block only)
SPVNodeFullNodeβ†’NodeClient (emits as block disconnect, returns ChainEntry only)
reconnectChainEntryBlockChainreconnect()
reorganizetip (ChainEntry), competitor (ChainEntry)Chainreorganize()reorganizeSPV()chain→FullNode
chain→SPVNode
blockBlockChainEntryChainsetBestChain()
CPUMiner_start()
chain→Pool
chainβ†’FullNode (returns Block only)
chainβ†’SPVNode (returns Block only)
competitorBlockChainEntryChainsaveAlternate()
bad orphanError, IDChainhandleOrphans()
MempoolhandleOrphans()
resolvedBlockChainEntryChainhandleOrphans()
checkpointHash, HeightChainverifyCheckpoint()
orphanBlockChainstoreOrphan()
fullChainmaybeSync()chain→Pool
confirmedTXChainEntryMempool_addBlock()
confirmedTXDetailsTXDBconfirm()txdbβ†’WalletDB (also returns Wallet)
txdb→Wallet
unconfirmedTXBlockMempool_removeBlock()
unconfirmedTXDetailsTXDBdisconnect()txdbβ†’WalletDB (also returns Wallet)
txdb→Wallet
conflictTXMempool_removeBlock()
conflictTXDetailsTXDBdisconnect()txdbβ†’WalletDB (also returns Wallet)
txdb→Wallet
txTXCoinViewMempooladdEntry()mempoolβ†’Pool (returns TX only)
mempoolβ†’poolβ†’SPVNode (returns TX only)
mempoolβ†’FullNode (returns TX only)
SPVNodeFullNodeβ†’NodeClient (returns TX only)
txTXPool_handleTX()
(only if there is no mempool, i.e. SPV)
pool→SPVNode
txTXDetailsTXDBinsert()txdbβ†’WalletDB (also returns Wallet)
txdb→Wallet
double spendMempoolEntryMempoolremoveDoubleSpends()
balanceBalanceTXDBinsert()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 ChainMempoolPool, and WalletDB, and relay events from them out the socket. To receive an event, the socket client must watch a channel (such as chainmempool, or auth) or join a wallet (which would be user-defined like primaryhot-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` subscirbes 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:

hsd --daemon --network=regtest

Run the script (this is where the event output will be printed):

node bsock-example.js

Then, in a separate terminal window, run some commands to trigger the events!

hsd-cli rpc generatetoaddress 1  rs1qyep4stuujjkdfv483nfse53vvgtfuqr9z6v4cm

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:

hsw-cli listen

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.

EventReturns
txWalletID, TX Details
confirmedWalletID, TX Details
unconfirmedWalletID, TX Details
conflictWalletID, TX Details
balanceWalletID, Balance
addressWalletID, [WalletKey]

Node

EventReturnsChannelOriginOriginal Event
chain connectChainEntry.toRaw()chainChainconnect
block connectChainEntry.toRaw()Block.txschainChainconnect
chain disconnectChainEntry.toRaw()chainChaindisconnect
block disconnectChainEntry.toRaw()chainChaindisconnect
txTX.toRaw()mempoolPooltx

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

EventArgsReturns
auth1. api keynull
join1. wallet id
2. wallet token
null
leave1. wallet idnull

Node

EventArgsReturns
auth1. api keynull
watch chain(none)null
unwatch chain(none)null
watch mempool(none)null
unwatch mempool(none)null
set filter1. Bloom filter (Buffer)null
get tip(none)ChainEntry.toRaw()
get entry1. hashChainEntry.toRaw()
get hashes1. start (int)
2. end (int)
[hashes]
add filter1. filter ([Buffer])null
reset filter(none)null
estimate fee1. blocks (int)fee rate (float)
send1. tx (Buffer)null
rescan1. hashnull

Originally published @ https://hsd-dev.org/guides/events.html

Was this article helpful to you? Yes No

How can we help?