How to listen for contract events and get logs using Wagmi

In this tutorial, we are going to learn how to listen for smart contract events and get logs and filter them in your React app using Wagmi hooks and Ethers JS.

In this tutorial, we are going to learn how to listen for smart contract events and get logs and filter them in your React app using Wagmi hooks and Ethers JS.

For that, we are going to use the useContractEvent. Here is an example using the Transfer event of an ERc-20 token (the Uniswap token):

import { useContractEvent, useContract } from 'wagmi'
import { useState } from 'React'

const uniAddress = '0xdAC17F958D2ee523a2206206994597C13D831ec7'
const erc20Abi = [
  'event Transfer(address indexed from, address indexed to, uint amount)'
]

export function ContractEvents() {
  const [newEvent, setNewEvent] = useState()
  const [previousLogs, setPreviousLogs] = useState()
  
  // Create a contract instance to get the past logs
  const contract = useContract({
    address: uniAddress,
    abi: erc20Abi,
  })

  // Listen for Transfer events on the UNI token
  useContractEvent({
    address: uniAddress,
    abi: erc20Abi,
    eventName: 'Transfer',
    listener: (from, to, amount) => {
      // this will run every time a new Transfer event is fired
      // the parameters of that function are the parameters of the
      // event
      setNewEvent({ from, to, amount })
    }
  })
  
  // Get the Transfer logs of the smart contract
  useEffect(() => {
    async function getPreviousLogs() {
      const myAddress = "0x5b8f1310A956ee1521A7bB56160451C786289aa9"
      const toAddress = "0x5F70Ddd9908B04f952b9cB2A6F8E4D451725ceDC"
      
      // Create a filter to get the logs
      const filter = contract.filters.Transfer(myAddress, toAddress)

      // Get the logs using the filter
      const logs = await contract.queryFilter(filter)
      setPreviousLogs(logs)
    }
    
    if (contract) getPreviousLogs()
  }, [contract])
  
  return (
    <div>
      <p>New event: {JSON.stringigy(newEvent)}</p>
      <ul>
        {previousLogs.map((log, i) => (
          <li key={i}>{JSON.stringify(log)}</li>
        ))}
      </ul>
    </div>
  )
}

How to listen for smart contract events using Wagmi

Using Wagmi's useContractEvent hook, we can listen for smart contract events and run a function when a new event is emitted.

That hook takes in parameter an object and you have with the following properties to make it work:

  • address: the address of the smart contract to listen to
  • abi: the ABI of the smart contract to listen to, it should contain the definition of the event to listen to
  • eventName: the name of the event to listen to
  • listener: the function that will be called when a new event is emitted by the smart contract.
    It receives in parameters the arguments the smart contract passed to the event.

In the example above, we listen to Transfer events on an ERC-20 token, the Uniswap token. So the address is the address of that token, the ABI needs to contain the definition of the Transfer event and the eventName is "Transfer".

The Transfer event in ERC-20 tokens is fired when someone sends tokens to another address and has 3 arguments:

  • the address the tokens were sent from
  • the address the tokens were sent to
  • the amount of tokens that were sent

So the listener function will receive these 3 values in parameters.

To know what arguments an event has, you can look in the ABI where the event is declared.

You can also pass a few other parameters to the useContractEvent hook. Check out the documentation to see the complete list of values you can pass to that hook.

How to get and filter previous contract logs using Ethers JS

Unfortunately, we can't use Wagmi to get the previous logs of a smart contract. Instead, we are going to use Ethers JS which is the library that Wagmi is built on.

For that, first we need to get a Contract instance using the useContract hook. Just like for useContractEvent, we need to pass the contract address and the ABI in the parameters and the ABI needs to contain the declaration of the event.

Then, using that Contract instance, we create a filter for the events we want to get using the filters property. The filters property will contain methods for each event in the ABI. So in our case, we call the Transfer method in filters.

In the parameters of that method, we can pass values for each indexed parameter that is in the events.

Smart contract events can take up to 17 parameters in total (arrays count for 2 parameters) and 3 indexed parameters maximum.

Filters in Ethers JS work like this:

  • If you don't pass any value to that function, it won't filter the values of the parameters
  • The parameters of the function you call to create the filter are the indexed parameters of the event in the same order as they are in the ABI
  • If you pass a value for a parameter, it will filter the logs and you'll only get the event where the value of the parameter you want to filter matches the value you passed.
  • If you pass an array of values for a parameter, it will return the events where the value of the parameter is one of the values of the array
  • If you pass null, it won't filter that parameter. That means if the parameter you want to filter is the second argument of the function, you can just do: contract.filters.MyEvent(null, myValue).

Once we have our filter, we can call the queryFilter method of our Contract instance and pass in the parameters the filter we just created.

The queryFilter method is asynchronous and will return an array containing the logs that were found.

You can also pass 2 other parameters to the queryFilter function which are respectively the block to start looking for logs and the block where it should stop looking for logs.

By default, the queryFilter function search for logs from the earliest block on the blockchain to the latest.

You can pass the following values to these parameters:

  • A string: "latest" for the latest block, "earliest" for the earliest block and "pending" for the currently pending block
  • A block number
  • A block hash

Now, the events that the queryFilter function returns are objects that have this format:

Then, a log object returned by queryFilter looks like this:

  • blockNumber: the number of the block that contains the log
  • blockHash: the hash of the block that contains the log
  • removed: true if the transaction was cancelled, otherwise false
  • transactionLogIndex: the index of this log (among the other logs) in the transaction that created this log
  • address: the address of the smart contract that created this log
  • data: an hexadecimal string containing the parameters passed by the smart contract when the log was created
  • topics: the list of topics of this event. Topics are what we use to get and filter events behind the scenes.
    The topics are the hash of this log and the values of the indexed parameters for this log (so up to 4 in total).
    This property is an array containing the values of each topic as an hexadecimal string.
  • transactionHash: the hash of the transaction in which the log was created.
  • transactionIndex: the index of the transaction in the block that mined the transaction in which the log was created.
  • logIndex: the index of the log across all the other logs in the block that created this log

As you can see, you can't read the values of the parameters that were passed to the event when it was created because both data and topics are hexadecimal strings.

To convert the parameters from their hexadecimal form into their original value, we create an interface out of the ABI of the smart contract and the call the parseLog function:

import { Interface } from 'ethers'

// ... THE REST OF YOUR COMPONENT ...

const logs = await contract.queryFilter(filter)

const interface = new Interface(ABI)

const parsedLogs = logs.map(log => {
    return interface.parseLog({ data: log.data, topics: log.topics });
})

// ... THE REST OF YOUR COMPONENT ...

The parseLog function will return an object that has an args property. That property is an object in which the keys are the names of the parameters and the values are the values that the smart contract passed to the log when it was created.

And that's it 🎉

Thank you for reading this article