@pulsadev/event-watcher
v0.1.0
Published
Lightweight real-time EVM event watcher — polling, historical scan, auto-reconnect, checkpoint resume, zero dependencies
Downloads
28
Readme
@pulsadev/event-watcher
Lightweight real-time EVM event watcher — polling, historical scan, auto-reconnect, checkpoint resume. Zero dependencies.
Watch contract events as they happen or scan historical blocks. One API for both, no ethers/viem required.
Features
- Real-time watching — poll for new events at configurable intervals
- Historical scanning — scan block ranges with automatic chunking
- Checkpoint resume — save and restore position, never miss or re-process events
- Auto-reconnect — handles RPC errors gracefully with configurable error callbacks
- Block confirmations — wait N confirmations before delivering events
- Topic helpers — build filters from event signatures with built-in ERC20/721/1155/Uniswap constants
- AbortSignal — cancel scans mid-flight
- Progress callbacks — track scan progress for large block ranges
- Zero dependencies — ~16 KB bundled, ESM + CJS, pure TypeScript
Install
npm install @pulsadev/event-watcherQuick Start
Watch live events
import { EventWatcher, eventSignatureToTopic, EVENTS } from '@pulsadev/event-watcher'
const watcher = new EventWatcher('https://eth.llamarpc.com', {
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
topics: [eventSignatureToTopic(EVENTS.ERC20_TRANSFER)],
})
watcher.on((log) => {
console.log(`Transfer at block ${log.blockNumber}`)
console.log(` tx: ${log.transactionHash}`)
console.log(` from: ${log.topics[1]}`)
console.log(` to: ${log.topics[2]}`)
})
watcher.watch({ pollIntervalMs: 4000 })
// Later...
watcher.stop()Scan historical events
const logs = await watcher.scan({
fromBlock: 25700000n,
toBlock: 25700100n,
chunkSize: 50,
onProgress: (scanned, total) => {
console.log(`${Number(scanned)}/${Number(total)} blocks scanned`)
},
})
console.log(`Found ${logs.length} events`)API
new EventWatcher(rpcUrl, filter?)
const watcher = new EventWatcher('https://eth.llamarpc.com', {
address: '0xA0b8...', // single address
address: ['0xA0b8...', '0xdAC1...'], // or multiple
topics: [transferTopic], // topic filters
})watcher.watch(options?)
Start watching for new events.
watcher.watch({
pollIntervalMs: 4000, // polling interval (default: 4000ms)
fromBlock: 25700000n, // start from specific block
confirmations: 2, // wait 2 confirmations
maxBlockRange: 2000, // max blocks per poll (default: 2000)
onError: (err) => {}, // error handler
})watcher.scan(options)
Scan a block range for historical events.
const logs = await watcher.scan({
fromBlock: 25700000n,
toBlock: 'latest', // or specific block number
chunkSize: 2000, // blocks per RPC call (default: 2000)
onProgress: (scanned, total) => {},
signal: abortController.signal,
})watcher.on(handler) / watcher.off(handler)
Register/remove event handlers.
const handler = (log) => console.log(log)
watcher.on(handler)
watcher.off(handler)watcher.stop()
Stop watching. Can be resumed by calling watch() again.
Checkpoint
Save and restore position to resume without reprocessing.
// Save
const checkpoint = watcher.getCheckpoint()
// { lastBlock: 25700050n, lastLogIndex: 3, timestamp: 1723... }
// Restore
watcher.setCheckpoint(checkpoint)
watcher.watch() // resumes from checkpointTopic Helpers
import { eventSignatureToTopic, buildTopicFilter, EVENTS } from '@pulsadev/event-watcher'
// Compute topic from signature
const topic = eventSignatureToTopic('Transfer(address,address,uint256)')
// Build filter with indexed params
const topics = buildTopicFilter(
['Transfer(address,address,uint256)'],
[null, '0x000...recipient'], // filter by 'to' address
)
// Built-in event signatures
EVENTS.ERC20_TRANSFER // 'Transfer(address,address,uint256)'
EVENTS.ERC20_APPROVAL // 'Approval(address,address,uint256)'
EVENTS.ERC721_TRANSFER // 'Transfer(address,address,uint256)'
EVENTS.ERC721_APPROVAL_FOR_ALL
EVENTS.ERC1155_TRANSFER_SINGLE
EVENTS.ERC1155_TRANSFER_BATCH
EVENTS.UNISWAP_V2_SWAP
EVENTS.UNISWAP_V3_SWAP
EVENTS.WETH_DEPOSIT
EVENTS.WETH_WITHDRAWALDecodedLog
Every event delivered to handlers has this shape:
{
address: '0xA0b8...', // contract address
blockNumber: 25700050n, // block number (bigint)
transactionHash: '0x1234...', // tx hash
logIndex: 3, // position in block
removed: false, // true if reorged
topics: ['0xddf2...', ...], // raw topics
data: '0x0000...', // raw data
}License
MIT © Yuto Nakamura
