Intermediate25 min
Handling Contract Events
Listen and respond to smart contract events
Handling Contract Events
In this tutorial, you'll learn how to listen for and process events emitted by smart contracts on Beatoz.
What are Events?
Events are a way for smart contracts to communicate that something happened on the blockchain. They're useful for logging, notifications, and building reactive applications.
Understanding Events in Solidity
Defining Events
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract EventExample {
// Define events
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
event ValueChanged(uint256 oldValue, uint256 newValue);
uint256 public value;
function setValue(uint256 newValue) public {
uint256 oldValue = value;
value = newValue;
// Emit event
emit ValueChanged(oldValue, newValue);
}
}
Testnet
Indexed Parameters
indexedparameters are searchable- Maximum 3 indexed parameters per event
- Non-indexed parameters are stored in data
Querying Past Events
Using Transaction Search
const { Web3 } = require('@beatoz/web3');
const web3 = new Web3('https://rpc-testnet0.beatoz.io');
async function getContractEvents(contractAddress) {
// Search for transactions involving this contract
const result = await web3.beatoz.txSearch({
query: `tx.to='${contractAddress}'`,
per_page: 100,
order_by: 'desc',
});
const events = [];
for (const tx of result.txs || []) {
// Parse events from transaction result
const txEvents = parseEvents(tx.tx_result.events);
events.push(...txEvents);
}
return events;
}
function parseEvents(rawEvents) {
if (!rawEvents) return [];
return rawEvents.map(event => ({
type: event.type,
attributes: event.attributes.reduce((acc, attr) => {
acc[attr.key] = attr.value;
return acc;
}, {}),
}));
}
Filtering by Event Type
async function getTransferEvents(contractAddress) {
const allEvents = await getContractEvents(contractAddress);
return allEvents.filter(event => event.type === 'Transfer');
}
Decoding Event Data
Using ABI Decoder
const abi = [
{
"anonymous": false,
"inputs": [
{"indexed": true, "name": "from", "type": "address"},
{"indexed": true, "name": "to", "type": "address"},
{"indexed": false, "name": "value", "type": "uint256"}
],
"name": "Transfer",
"type": "event"
}
];
function decodeTransferEvent(log) {
const topics = log.topics;
const data = log.data;
// Topic[0] is the event signature
// Topic[1] is the 'from' address (indexed)
// Topic[2] is the 'to' address (indexed)
const from = '0x' + topics[1].slice(-40);
const to = '0x' + topics[2].slice(-40);
// Data contains non-indexed parameters
const value = web3.beatoz.abi.decodeParameter('uint256', data);
return { from, to, value };
}
Building an Event Monitor
async function monitorEvents(contractAddress, intervalMs = 5000) {
let lastBlockHeight = 0;
console.log(`Monitoring events for ${contractAddress}...`);
setInterval(async () => {
try {
// Get current block height
const status = await web3.beatoz.status();
const currentHeight = parseInt(status.sync_info.latest_block_height);
if (currentHeight > lastBlockHeight) {
// Check new blocks for events
for (let h = lastBlockHeight + 1; h <= currentHeight; h++) {
const blockResults = await web3.beatoz.blockResults(h);
// Process each transaction result
for (const txResult of blockResults.txs_results || []) {
const events = txResult.events || [];
for (const event of events) {
if (isRelevantEvent(event, contractAddress)) {
handleEvent(event, h);
}
}
}
}
lastBlockHeight = currentHeight;
}
} catch (error) {
console.error('Monitor error:', error.message);
}
}, intervalMs);
}
function isRelevantEvent(event, contractAddress) {
// Check if event is from our contract
const addressAttr = event.attributes.find(a => a.key === 'contract_address');
return addressAttr && addressAttr.value === contractAddress;
}
function handleEvent(event, blockHeight) {
console.log(`\nš¢ Event at block ${blockHeight}:`);
console.log('Type:', event.type);
for (const attr of event.attributes) {
console.log(` ${attr.key}: ${attr.value}`);
}
}
Event-Driven Application
Token Transfer Tracker
class TransferTracker {
constructor(tokenAddress) {
this.tokenAddress = tokenAddress;
this.transfers = [];
this.web3 = new Web3('https://rpc-testnet0.beatoz.io');
}
async getRecentTransfers(limit = 50) {
const result = await this.web3.beatoz.txSearch({
query: `tx.to='${this.tokenAddress}'`,
per_page: limit,
order_by: 'desc',
});
const transfers = [];
for (const tx of result.txs || []) {
const transferEvent = this.findTransferEvent(tx.tx_result.events);
if (transferEvent) {
transfers.push({
hash: tx.hash,
height: tx.height,
...transferEvent,
});
}
}
return transfers;
}
findTransferEvent(events) {
for (const event of events || []) {
if (event.type === 'Transfer') {
return {
from: this.getAttribute(event, 'from'),
to: this.getAttribute(event, 'to'),
value: this.getAttribute(event, 'value'),
};
}
}
return null;
}
getAttribute(event, key) {
const attr = event.attributes.find(a => a.key === key);
return attr ? attr.value : null;
}
}
// Usage
const tracker = new TransferTracker('0x...token_address');
const transfers = await tracker.getRecentTransfers();
console.log('Recent transfers:');
for (const t of transfers) {
console.log(`${t.from} ā ${t.to}: ${t.value}`);
}
WebSocket for Real-time Events
Coming Soon
WebSocket support for real-time event streaming will be available in future SDK versions.
// Example of future WebSocket event subscription
const ws = new web3.beatoz.WebsocketProvider('wss://testnet-ws.beatoz.io');
// Subscribe to events (future API)
ws.subscribe('newBlock', (block) => {
console.log('New block:', block.height);
});
ws.subscribe('logs', {
address: contractAddress,
topics: ['Transfer'],
}, (log) => {
console.log('Transfer event:', log);
});
Practical Use Cases
1. Transaction Confirmation
async function waitForEvent(txHash, eventType, timeout = 60000) {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
try {
const tx = await web3.beatoz.tx(txHash);
const event = tx.tx_result.events?.find(e => e.type === eventType);
if (event) {
return event;
}
} catch {
// Transaction not found yet
}
await new Promise(r => setTimeout(r, 2000));
}
throw new Error('Event timeout');
}
2. Balance Change Notifications
async function watchBalanceChanges(address, callback) {
let lastBalance = await getBalance(address);
setInterval(async () => {
const newBalance = await getBalance(address);
if (newBalance !== lastBalance) {
callback({
address,
oldBalance: lastBalance,
newBalance,
change: newBalance - lastBalance,
});
lastBalance = newBalance;
}
}, 5000);
}
// Usage
watchBalanceChanges('0x...', (change) => {
console.log(`Balance changed: ${change.change} BTZ`);
});
You now understand how to work with contract events on Beatoz! š
