Events and Sockets
Listen for Events
To listen for and react to events in bcoin, 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 bcoin
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/bcoin-org/bcoin/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 Bitcoin 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 bcoin 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 bcoin, you need to run a websocket client.
Luckily the bcoin 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 bcoin 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 bcoin.
// bsock-example.js
const bsock = require('bsock');
const {Network, ChainEntry} = require('bcoin');
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.fromRaw(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 bcoin however you usually do:
bcoin --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!
bcoin-cli rpc generatetoaddress 1 RJ14p2tpf5ANiFJBpKrTSPTgFnzScsDAhN
Sockets made easy: bclient
bsock
is a great low-level library for dealing with sockets, but we also have
bclient 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:
bwallet-cli listen
bclient
can also be used in a script to listen for events. If you're already familiar with
the bclient API this will look very familiar. Here's part of the
script above re-written using bclient
instead of bsock
:
const {NodeClient} = require('bclient');
const {Network, ChainEntry} = require('bcoin');
const network = Network.get('regtest');
const clientOptions = {
network: network.type,
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 |