Understanding Smart Contract Event Listeners in Web3.js

Posted By : Amit

Sep 29, 2020

On a blockchain, smart contracts are self-executing contracts. Without the use of middlemen, they enable transparent and safe transactions. The capacity to emit events when specific conditions are met is one of the essential characteristics of smart contracts. External apps can listen to and respond to these events, giving developers the ability to create decentralized, real-time applications. In this tutorial, we will explore smart contract listeners in Web3.js.

 

Contract Event Subscriptions using Web3.js

 

Here are all the subscription functions provided by Web3.js to listen to events emitted by a smart contract. 



a) myContract.once(event[, options], callback) - to fetch an event only once returns undefined
b) myContract.events.MyEvent([options][, callback]) - to fetch events named "MyEvent", returns an eventEmitter
c) myContract.events.allEvents([options][, callback]) - to fetch all events continuously, also returns an EventEmitter
d) myContract.getPastEvents(event[, options][, callback]) - to fetch all past events available in the block range, returns a list of event objects

 

To use all of these subscription functions, we need to first create a contract instance using web3 like done here:- 

 

let contractInstance = new web3Instance.eth.Contract(
    contractABI,
    contractAddress
)

 

Now suppose the contract we are interacting with is an ERC20 token and has a Transfer event. All of these functions have
parameters very similar to the following syntax options object with the following properties, which may or may not be optional depending on the functions

 

a) filter - Object (optional): Lets you filter events by indexed parameters, for example, 
b) fromBlock - Number (optional): The block number from which to get events on
c) toBlock - Number (optional): The block number to get events up to (Defaults to "latest").
d) topics - Array (optional): This allows manually setting the topics for the event filter

 

//Transfer event emitted on an ERC20 token consisting "0x8aB7e3f2A693268348CcBf9111dEEd5dd3640c11" as the indexed from parameters  
{filter:{from:"0x8aB7e3f2A693268348CcBf9111dEEd5dd3640c11"}}

//required to specify the block number from which to start fetching events.
fromBlock : 100000

//similar to fromBlock , the upper limit to the block number range
toBlock : 200000

//complete object to pass to the listener function
let options = {filter,fromBlock,toBlock}

 

Returned object details

 

The event object has the following details - 

 

a) event - event name
b) address - address that generated the event
c) transactionHash - transaction hash of the transaction that resulted in the emission of this event
d) blockHash - hash of the block in which the transaction got mined, null if a transaction is pending
e) returnValues - an object consisting of all parameters in the event signature
for example for a transfer event in an erc20 token, they would depict from, to, and value named parameters
f) blockNumber
g) logIndex - index differentiating all the events emitted from this transaction, and others

 

An example depicting subscription of the events and reading the event object is the following:-

 

let contractSubObject = contractInstance.events.allEvents({
      fromBlock: '2300000',
})
contractSubObject.on('connected', function (subId) {
      logger.debug("New contract subscription active for %s with sub id %s", candidateAddress , subId);
      contractSubscriptionDetail.subscriptionObject = contractSubObject
})
contractSubObject.on('data',function (event) {
    saveEventTransaction(event,candidateAddress)
})

function saveEventTransaction(event, candidateAddress) {
    let eventName = event.event.toLowerCase()
    let transaction = new db.Transaction({
      hash: event.transactionHash,
      blockNumber: event.blockNumber,
      logIndex: event.logIndex,
      event: eventName,
      candidate: candidateAddress,
    })
    if(eventName.includes("stake")){
        transaction.staker = event.returnValues._staker;
        transaction.capacity = event.returnValues._amount;
    }else{
        transaction.staker = event.returnValues._owner;
        transaction.capacity = event.returnValues._cap;
        transaction.blockNumber = event.returnValues._blockNumber;
    }
    web3HttpInstance.eth.getBlock(event.blockNumber,function(err,data){
      let createdAt = moment.unix(data.timestamp).utc()
      transaction.createdAt = createdAt;
      if(!err){
        transaction.save(function (ex, tx) {
          if (ex) {
            logger.error("Error saving event %s with hash %s on candidate address %s , error %s", eventName, event.transactionHash,
             candidateAddress, ex)
          } else {
            logger.debug("saved event %s with hash %s on candidate address %s", eventName, event.transactionHash, candidateAddress);
          }
        })
      }else{
        logger.error("GetBlock Related Error saving event %s with hash %s on candidate address %s , error %s", eventName, event.transactionHash,
             candidateAddress, ex)
      }
    })
}

 

Resubscription of events

 

For the myEvent and allEvents subscribers, we can also resubscribe this after a certain interval of time, so that we 
guarantee fetching events in case of our process Event function has failed.

 

For this, we need access to the subscriptionObject that you saw in the above code.  

 

You may also like | A Developer Guide to Smart Contract ABI

 

function resubscribeAllListeners(){
  for(let cslItem of contractSubscriptionList){
    updateSubscription(cslItem)
  }
}

function updateSubscription(subItem) {
  logger.debug("Resubscribing allEventListener sub for candidate %s subObject %o", subItem.address, subItem.subscriptionObject)
  let olderSubscription = subItem.subscriptionObject;
  olderSubscription.unsubscribe(function(err,data){
    if(err){
      logger.error("Error unsubscribing allEventListener with id %s , and candidate %s", olderSubscription.id,subItem.address)
    }else{
      if(data === true){
        logger.debug(`unsubscribed  allEventListener with id ${olderSubscription.id} for candidate ${subItem.address}`);
        let contractInstance = new web3WsInstance.eth.Contract(candidateABI,subItem.address);
        let contractSubObject = contractInstance.events.allEvents({fromBlock: 'genesis',})
        contractSubObject.on('connected', function (subId) {
        logger.debug("Resubscription now active with sub id %s for candidate %s ", subId, subItem.address)
        subItem.subscriptionObject = contractSubObject
        })
        //let the events now be processed using this function
        contractSubObject.on('data', function (event) {
          saveEventTransaction(event,subItem.address)
        })
      }
    }
  })
}

 

For further details look into the contract topic on web3.js documentation at - web3-contract

 

Subscription of events will require a Web3 instance using a WebSocket provider, for that look into the web3-provider options.

 

For more information related to Ethereum smart contract development, you may connect with our blockchain developers

Leave a

Comment

Name is required

Invalid Name

Comment is required

Recaptcha is required.

blog-detail

April 4, 2024 at 10:46 am

Your comment is awaiting moderation.

By using this site, you allow our use of cookies. For more information on the cookies we use and how to delete or block them, please read our cookie notice.

Chat with Us
Contact Us

Oodles | Blockchain Development Company

Name is required

Please enter a valid Name

Please enter a valid Phone Number

Please remove URL from text