Back to Tutorials
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

  • indexed parameters are searchable
  • Maximum 3 indexed parameters per event
  • Non-indexed parameters are stored in data

Querying Past Events

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! šŸŽ‰

Next Steps

Beatoz Developer Portal