Staking Monitoring (all contract events)
{
"subscribe": {
"events": ["TxnLog"],
"filters": [{
"event_name": "TxnLog",
"field_filters": [
{"field": "address", "filter": {"values": ["0x0000000000000000000000000000000000001000"]}}
]
}]
}
}
Uses TxnLog filtered by the staking contract address.
Only Delegate + Undelegate (OR logic)
{
"subscribe": {
"events": ["TxnLog"],
"filters": [
{
"event_name": "TxnLog",
"field_filters": [
{"field": "address", "filter": {"values": ["0x0000000000000000000000000000000000001000"]}},
{"field": "topics", "filter": {"values": ["0xe4d4df1e1827dd28252fd5c3cd7ebccd3da6e0aa31f74c828f3c8542af49d840"]}}
]
},
{
"event_name": "TxnLog",
"field_filters": [
{"field": "address", "filter": {"values": ["0x0000000000000000000000000000000000001000"]}},
{"field": "topics", "filter": {"values": ["0x3e53c8b91747e1b72a44894db10f2a45fa632b161fdcdd3a17bd6be5482bac62"]}}
]
}
]
}
}
Distinguishing delegate() vs compound() via function_selector
Both calls emit the Delegate event. The difference is in the function selector:
{
"subscribe": {
"events": ["TxnHeaderStart"],
"filters": [{
"event_name": "TxnHeaderStart",
"field_filters": [
{"field": "to", "filter": {"values": ["0x0000000000000000000000000000000000001000"]}},
{"field": "function_selector", "filter": {"values": ["0x84994fec"]}}
]
}],
"correlate": true
}
}
0x84994fec = delegate(uint64). With correlate: true you get the full chain: TxnHeaderStart → TxnLog → TxnEvmOutput → TxnEnd.
ERC-20 Transfer Monitoring
{
"subscribe": {
"events": ["TxnLog"],
"filters": [{
"event_name": "TxnLog",
"field_filters": [
{"field": "address", "filter": {"values": ["0xTOKEN_ADDRESS"]}},
{"field": "topics", "filter": {"values": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}}
]
}]
}
}
0xddf252ad... = keccak256("Transfer(address,address,uint256)").
Bridge Relay — Finalized Events
{
"subscribe": {
"events": ["TxnLog"],
"filters": [{
"event_name": "TxnLog",
"field_filters": [
{"field": "address", "filter": {"values": ["0xBRIDGE_CONTRACT"]}}
]
}],
"min_stage": "Finalized"
}
}
min_stage: "Finalized" ensures only irreversible events are received.
Wallet Tracking (all transactions by sender)
{
"subscribe": {
"events": ["TxnHeaderStart"],
"filters": [{
"event_name": "TxnHeaderStart",
"field_filters": [
{"field": "sender", "filter": {"values": ["0xYOUR_WALLET"]}}
]
}],
"correlate": true
}
}
Dashboard: TPS + Lifecycle + ContentionData
{"subscribe": ["TPS", "Lifecycle", "ContentionData"]}
3 items = within the default limit. Uses TPS, Lifecycle, and ContentionData.
Only Finalized Logs
{
"subscribe": {
"events": ["TxnLog", "BlockFinalized"],
"min_stage": "Finalized"
}
}
Finality-Safe Event Processing (Lifecycle Buffering)
For applications that must only act on finalized data (bridges, exchanges, payment processors):
// Subscribe to events + Lifecycle (3 items = within default limit)
ws.send(JSON.stringify({ subscribe: ["TxnLog", "Lifecycle", "BlockFinalized"] }));
const pendingBlocks = new Map(); // block_number → events[]
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
// Buffer events by block
if (msg.Events) {
for (const e of msg.Events) {
if (e.block_number == null) continue;
if (!pendingBlocks.has(e.block_number)) {
pendingBlocks.set(e.block_number, []);
}
pendingBlocks.get(e.block_number).push(e);
}
}
// Process on finalization, discard on rejection
if (msg.lifecycle) {
const { block_number, to_stage } = msg.Lifecycle;
if (to_stage === "Finalized") {
const events = pendingBlocks.get(block_number);
if (events) {
for (const e of events) {
processConfirmedEvent(e); // safe — this block is irreversible
}
pendingBlocks.delete(block_number);
}
}
if (to_stage === "Rejected") {
pendingBlocks.delete(block_number); // block was dropped, discard events
}
}
// Cleanup: remove old pending blocks (safety net)
for (const [bn] of pendingBlocks) {
if (pendingBlocks.size > 100) {
pendingBlocks.delete(bn);
break; // remove oldest
}
}
};
Why not just use min_stage: "Finalized"? You can, and it's simpler. But the buffering pattern gives you:
- Real-time visibility: You see events as they arrive (Proposed stage) and can show pending state
- Confirmed processing: You only act on finalized events
- Rejection awareness: You know when a block is rejected and can update UI accordingly
Staking Precompile
Contract address: 0x0000000000000000000000000000000000001000
Event Signatures (topic[0])
| Event | topic[0] (keccak256) |
|---|---|
Delegate(uint64,address,uint256,uint64) | 0xe4d4df1e1827dd28252fd5c3cd7ebccd3da6e0aa31f74c828f3c8542af49d840 |
Undelegate(uint64,address,uint8,uint256,uint64) | 0x3e53c8b91747e1b72a44894db10f2a45fa632b161fdcdd3a17bd6be5482bac62 |
Withdraw(uint64,address,uint8,uint256,uint64) | 0x63030e4238e1146c63f38f4ac81b2b23c8be28882e68b03f0887e50d0e9bb18f |
ClaimRewards(uint64,address,uint256,uint64) | 0xcb607e6b63c89c95f6ae24ece9fe0e38a7971aa5ed956254f1df47490921727b |
ValidatorRewarded(uint64,address,uint256,uint64) | 0x3a420a01486b6b28d6ae89c51f5c3bde3e0e74eecbb646a0c481ccba3aae3754 |
ValidatorCreated(uint64,address,uint256) | 0x6f8045cd38e512b8f12f6f02947c632e5f25af03aad132890ecf50015d97c1b2 |
CommissionChanged(uint64,uint256,uint256) | 0xd1698d3454c5b5384b70aaae33f1704af7c7e055f0c75503ba3146dc28995920 |
ValidatorStatusChanged(uint64,uint64) | 0xc95966754e882e03faffaf164883d98986dda088d09471a35f9e55363daf0c53 |
EpochChanged(uint64,uint64) | 0x4fae4dbe0ed659e8ce6637e3c273cd8e4d3bf029b9379a9e8b3f3f27dbef809b |
Function Selectors
| Function | Selector |
|---|---|
delegate(uint64) | 0x84994fec |
undelegate(uint64,uint256,uint8) | 0x5cf41514 |
withdraw(uint64,uint8) | 0xaed2ee73 |
compound(uint64) | 0xb34fea67 |
claimRewards(uint64) | 0xa76e2ca5 |