6

I have the following event filters defined:

const filter1 = contract.Foo(null, null, null)
const filter2 = contract.Bar(null, null, null)

In order to get all historical event emissions, I need to make two queries as follows and then combine the results.

const fooEvents = await contract.queryFilter(filter1, fromBlock, toBlock)
const barEvents = await contract.queryFilter(filter1, fromBlock, toBlock)
const events = [...fooEvents, ...barEvents]

Is there a way to combine filter1 and filter2 so that I would only need to make a single call to queryFilter(..) instead of two, while also optimizing the archive node web3 provider usage?

4 Answers 4

4

This is how to do it with Ethers v6 :

const filter1 = await myContract.filters.Event1().getTopicFilter()
const filter2 = await myContract.filters.Event2().getTopicFilter()

const topicFilter = [
    filter1.concat(filter2)
] as TopicFilter

await myContract.queryFilter(topicFilter, blockStart, blockEnd)
    .then(async (events: any) => {
        // Do something
    })

More info about the TopicFilter type in the docs : https://docs.ethers.org/v6/api/providers/#TopicFilter

2
  • This only seems to work if you don't have indexed parameters. For eg. for Transfer events that usually are indexed by a sender, this doesn't work
    – sayandcode
    Commented Feb 20 at 2:22
  • Thanks for your feedback, @sayandcode. I'll keep my eyes open for a better solution. Commented Feb 20 at 11:54
2

It's not well documented but you can indeed do this:

const filter1 = contract.filters.MyEvent()
const filter2 = contract.filters.MyOtherEvent()

const events = await contract.queryFilter({
  address: contract.address,
  topics: filter1.topics.concat(filter2.topics)
}, fromBlock, toBlock)
3
  • Don't you think this be incorrect as the topics need to be [[filter1.topics[0], filter2.topics[0]]]. Commented Sep 24, 2021 at 14:19
  • The Ethereum JSON RPC supports running a filter against multiple addresses. But it seems Ethers does not support this. Ethers.js issue: github.com/ethers-io/ethers.js/issues/473 Commented Oct 16, 2021 at 2:47
  • It appears that the suggested approach isn't functioning as expected. Specifically, when scanning multiple events simultaneously, some are not being captured without any clue. To address this, I've divided the process into two distinct filters and queried them independently. Commented Aug 31, 2023 at 12:53
2

Combining two filters may or may not be possible depending on what kind of filters you are combining.

According to Eth JSON RPC spec, a topic can be:

topics: Array<string | string[]> // a single bytes32 value, if multiple then pass an array

If you want to combine two filters that do not contain any indexed params, than it is easy:

const combinedFilter = {
  address: [filter1.address, filter2.address] // combining addresses of two contracts
  topics: [
    [ filter1.topics[0], filter2.topics[0] ]
  ],
  fromBlock: ...,
  toBlock: ...
}

const result = await provider.getLogs(combinedFilter)

But if you have different event types (e.g. Transfer and Mint events) and had indexed params in your filter, then it gets complicated. You could still do it but might face issues. It would be trivial if you are querying same event from multiple contract addresses.

1
1

You can get both and then sort the events once you combine them into an array. The solution here is hacky, but works. It puts the block number and transaction index within the block into a floating point number and sorts based on that.

let transferSingleEvents = await contract.queryFilter(contract.filters.TransferSingle())
let transferBatchEvents = await contract.queryFilter(contract.filters.TransferBatch())

let allEvents = [...transferSingleEvents, ...transferBatchEvents] // concatenate arrays using spread operator
allEvents.sort((a, b) => parseFloat(`${a.blockNumber}.${a.transactionIndex}`) - parseFloat(`${b.blockNumber}.${b.transactionIndex}`))

(There is probably a more efficient way that doesn't have the overhead of converting to/from strings. I'm making this a community wiki answer; feel free to edit it if there is a more efficient solution!)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.