ChainGate 1.3 — Real-time events in the SDK: onBlock, onBalance, onTransaction
Earlier this week the API learned to push events over WebSocket. Today the SDK does the work for you: subscribe with a callback and stop polling. New blocks, balance changes, transactions, pending activity and contract interactions arrive as typed events on Bitcoin, Litecoin, Dogecoin, Bitcoin Cash, Bitcoin Testnet, Ethereum and Avalanche — no sockets to open, no reconnection code, no JSON to parse.
import { ChainGate } from 'chaingate'
const cg = new ChainGate()
const btc = cg.explore(cg.networks.bitcoin)
btc.onBlock((block) => console.log(`Block ${block.height} — ${block.numTxs} transactions`))
btc.onBalance('bc1q...', async ({ confirmed }) => {
console.log('New balance:', confirmed.base(), 'BTC', '≈', await confirmed.toCurrency('usd'), 'USD')
})
Events for any address, or for your wallet
Explorers follow any address you pass. Connectors follow the wallet's own address — derived for you, with the usual address options:
// Any address, no wallet needed
const eth = cg.explore(cg.networks.ethereum)
eth.onTransaction('0x...', ({ height }) => console.log('Activity in block', height))
eth.onContractInteraction('0x...', ({ contract }) => console.log('New contract:', contract))
// Your wallet
const wallet = cg.connect(cg.networks.litecoin, myWallet)
wallet.onTransaction(({ received, amount, txid }) => {
console.log(received ? 'Received' : 'Sent', amount.base(), 'LTC in', txid)
})
wallet.onBalance(({ confirmed }) => console.log(confirmed.base(), 'LTC'), { index: 3 })
| Method | What you receive |
|---|---|
onBlock(cb) / onFullBlock(cb) | Every new block — a summary, or the whole block |
onBalance(address, cb) / onPendingBalance(address, cb) | The confirmed balance after every change, or the pending delta |
onTransaction(address, cb) / onPendingTransaction(address, cb) | Every confirmed transaction of an address, or every pending one |
onMempoolTransaction(cb) | Every pending transaction on the network |
onContractInteraction(address, cb) | Each new contract an address interacts with (Ethereum, Avalanche) |
onError(cb) | Connection and subscription errors |
Every event is a plain, typed object — camelCase fields and Amount values, so .base(), .min() and .toCurrency() work exactly as they do everywhere else in the SDK.
One handle to rule them
Every method returns a Subscription right away:
const sub = wallet.onBalance(({ confirmed }) => render(confirmed))
await sub.ready // the server confirmed the subscription
sub.address // the address being followed — the wallet's, once derived
sub.unsubscribe() // done
Invalid addresses are rejected before anything is sent, and ready rejects if the server refuses a subscription.
Connections you never have to think about
All the subscriptions of a network share one connection, opened with the first subscription and closed when the last one goes away — so a script that unsubscribes from everything simply exits. Subscribing twice to the same thing (even with the address written differently) costs a single subscription.
If the connection drops, it is re-established with backoff and every subscription is restored. A silent connection is checked with a ping and replaced if it stopped answering. Subscriptions are sent at a steady pace that never trips the rate limits, and everything that goes wrong on a connection is reported through onError() — including RateLimitError / RateLimitQuotaError when a limit is reached.
btc.onError((error) => console.warn('Events:', error.message))
Confirmations without polling
broadcasted.onConfirmed() used to check the transaction every few seconds. In 1.3 it is driven by events: the SDK follows the sender's transactions and fires the moment the block that includes yours is finalized — with a single lookup when the subscription starts, and none at all on UTXO networks when the event itself carries the confirmation.
const broadcasted = await tx.signAndBroadcast()
broadcasted.onConfirmed(({ blockHeight }) => {
console.log('Confirmed in block', blockHeight)
})
Nothing changes in your code: same method, same callback, same cancel function — and a new callback registered after cancelling all the previous ones picks the tracking back up.
Highlights
| Change | Description |
|---|---|
onBlock / onFullBlock | New blocks on every natively supported network |
onBalance / onPendingBalance | Confirmed balance changes and pending deltas per address |
onTransaction / onPendingTransaction | Confirmed and pending transactions per address |
onMempoolTransaction | Every pending transaction on the network |
onContractInteraction | New contract interactions on Ethereum and Avalanche |
onError | Connection and subscription errors, including rate limits |
Subscription | ready, address and unsubscribe() on every subscription |
| Shared connections | One per network, opened and closed on demand, reconnected automatically |
Event-driven onConfirmed() | Transaction confirmations fire as soon as the block is finalized |
Works in Node.js 22+, Bun and the browser, with no new dependency. Events are included with the free API key and also work without one.
npm install chaingate@latest
Read the real-time events guide, grab your free API key at ChainGate's Dashboard, star us on GitHub and join the Discord to tell us what you're building.