Search code examples
ethereumethers.jsdecentralized-applications

Ethers.js event listeners - Expected behavior for many getLogs/chainId/blocknumber requests?


I'm building a minting site that requires me to check the number of NFTs minted and display that number in real time to the user.

At first I was just making a request every few seconds to retrieve the number, but then I figured I could use an event listener to cut down on the requests, as people would only be minting in short bursts.

However, after using the event listener, the volume of requests has gone way up. Looks like it is constantly calling blockNumber, chainId, and getLogs. Is this just how an event listener works under the hood? Or do am I doing something wrong here?

enter image description here

This is a next js API route and here is the code:

// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { ethers } from 'ethers'
import { contractAddress } from '../../helpers'
import type { NextApiRequest, NextApiResponse } from 'next'
import abi from '../../data/abi.json'
const NEXT_PUBLIC_ALCHEMY_KEY_GOERLI =
  process.env.NEXT_PUBLIC_ALCHEMY_KEY_GOERLI

let count = 0
let lastUpdate = 0

const provider = new ethers.providers.JsonRpcProvider(
  NEXT_PUBLIC_ALCHEMY_KEY_GOERLI,
  'goerli'
)

const getNumberMinted = async () => {
  console.log('RUNNING NUMBER MINTED - MAKING REQUEST', Date.now())
  const provider = new ethers.providers.JsonRpcProvider(
    NEXT_PUBLIC_ALCHEMY_KEY_GOERLI,
    'goerli'
  )
  const contract = new ethers.Contract(contractAddress, abi.abi, provider)
  const numberMinted = await contract.functions.totalSupply()
  count = Number(numberMinted)
  lastUpdate = Date.now()
}
const contract = new ethers.Contract(contractAddress, abi.abi, provider)
contract.on('Transfer', (to, amount, from) => {
  console.log('running event listener')
  if (lastUpdate < Date.now() - 5000) {
    getNumberMinted()
  }
})

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    res.setHeader('Content-Type', 'application/json')
    res.status(200).json({ count })
  } catch (err) {
    res
      .status(500)
      .json({ error: 'There was an error from the server, please try again' })
  }
}


Solution

  • If you use the AlchemyProvider or directly the StaticJsonRpcProvider (which ApchemyProvider inherits) you will eliminate the chainId calls; those are used to ensure the network hasn’t changed, but if you using a third-party service, like Alchemy or INFURA, this isn’t a concern which is why the StaticJsonRpcProvider exists. :)

    Then every pollingInterval, a getBlockNumber is made (because this is a relatively cheap call) to detect when a new block occurs; when a new block occurs, it uses the getLogs method to find any logs that occurred during that block. This minimizes the number of expensive getLogs method.

    You can increase or decrease the pollingInterval to trade-off latency for server resource cost.

    And that’s how events work. :)

    Does that make sense?