Ethers.js: Contract events: filter indexed.

Created on 18 Jun 2018  路  13Comments  路  Source: ethers-io/ethers.js

Hi,

I find the documentation on events a little confusing. How do I find events filtered by event type and an indexed parameter?

Web3 has a really simple and good syntax to do this with .watch() :

const event = myContract.myEvent({_from: '0x.......})
event.watch().then(...

As I see it there are two methods to retrieve contract events with ethersjs.

contract.on_Eventname_().then()

As far as I can tell there is no way to add a filter object to this method like we can do with getLogs()?
I assume just because we use the contract object this doesn't necessarily mean it binds to wallet.address and looks only for events in which wallet.address is indexed?

And provider.getLogs :

provider.getLogs({
fromBlock: 1,
toBlock: 'latest',
address: 0x.....,
topics: event.topics
}).then()

Is this the correct syntax for retrieving all events with a topic that has user addresses indexed?

For example can we get all ERC20 transfer events originating from an address like follows? :
const event = this.contract.interface.events.Transfer
event.topics[1] = wallet.address

provider.getLogs({
fromBlock: 1,
toBlock: 'latest',
address: 0x.... _(token contract address)_ ,
topics: event.topics
})

What do we put at 'topics'?
Since event.topics[0] is the signature we should keep that one
Should we replace event.topics[1] (which would be the 'from' address in Transfer event) with the address we want to index by?

What's the correct approach?
Perhaps a more unified approach comparable to the web3 one is interesting ?

Thanks in advance.

enhancement

Most helpful comment

Thanks @ricmoo. I tried it and it works fine. I think there is a slight issue with the typings on 4.0.1. The EventFilter which is contract.filters.Transfer(myAddress, null) is type EventFilter which doesn't have fromBlock on it:

export declare type EventFilter = {
    address?: string;
    topics?: Array<string>;
};

Perhaps instead it should be

export declare type EventFilter = {
    address?: string;
    topics?: Array<string>;
    fromBlock?: BlockTag;
    toBlock?: BLockTag
};

All 13 comments

So from what I've found this is the correct answer I think:

let event = this.contract.interface.events.Transfer
event.topics[1] = keccak256(wallet.address)
const logs = provider.getLogs({
fromBlock: 1,
toBlock: 'latest',
address: 0x..... ,
topics: event.topics
})

If we want to filter by both sender and recipient we would add event.topics[2] = keccak256(recipientAddress)

Is it possible to update the documentation for this perhaps?

I turned this into a semi-generic function. Need to add errors on contract, wallet or eventName null.

/** retrieves contract events
* @param {Object} contract  - contract to get events from
* @param {string} eventName - Name of the event to get logs from
* @param {Array} extraTopics - Array of extra filtering options , in the exact order as the ABI specifies for the event (indexed event args)
* @param {number} fromBlock - block to start lookup from
* @param (number) toBlock - last block to end lookup at
* @returns logs - Parsed event logs
*/

async function _getEvents(contract, eventName, wallet, extraTopics = null, fromBlock = 0, toBlock = 'latest') {
  try {
    //get Provider
    const provider = initProvider()
    //Get event
    let event = contract.interface.events[eventName]
     if (extraTopics!== null) {
      event.topics[1] = keccak256(wallet.address)
      for (let i = 0; i < extraTopics.length; i++) {
        event.topics[i+1] = keccak256(extraTopics[i])
      }
    }
    let logs = await provider.getLogs({
      fromBlock,
      toBlock,
      address: contract.address,
      topics: event.topics
    })
    return logs.map(log => event.parse(log.topics, log.data))
  } catch (err) {
    throw new Error ("Something went wrong while retrieving event: " + err)
  }
}

A simpler method is coming in v4 (the TypeScript branch) which should address this. I鈥檓 just updat Ng the documentation n now and it should be available this week. :)

Cool cant wait to check it out!

One more note regarding the to and from addresses; you do not want to hash them, since they fit, so you just have to pad it with 0鈥檚.

This now works in the v4 (npm install ethers@next).

let filterFromMe = contract.filters.Transfer(myAddress, null);
let filterToMe = contract.filters.Transfer(null, myAddress);
let handleEvent = function(from, to) {
};
contract.on(filterFromMe, handleEvent);
contract.on(filterToMe, handleEvent);

A future (backwards compatible) feature will be coming to create OR patterns, where you would be able to do contract.filters.Transfer(OR(myAddress, null), OR(myAddress, null)) to accomplish this in one event, but for now the above should satisfy most peoples needs. :)

@ricmoo is there also the possibility of browsing for events through the provider prototype? The documentation isn't really clear there.

This might seem like an unnecessary feature but to me it's necessary when browsing for events when the contract address is unknown or we have multiple contract addresses to fetch events from with the same signature (eg getting ALL ERC20 transfer events from ALL tokens)

something similar to:

event myEvent(address indexed _address, uint _something, bytes32  _somethingElse);
eth.filter({
fromBlock: 1,
toBlock: 'latest',
topics: [sha3("myEvent(address, uint, bytes32)", sha3(address)]
})

Where we swap the 'eth' for provider prototype perhaps?

I'll try to come up with something as for some reason I'm getting errors with web3 when ethers is already installed. Weird, I didn't know web3 used ethers for ABI Encoding? -> https://github.com/ethereum/web3.js/issues/1946

Sorry, my bad it's already there. It just took me a while to realise I have to encode manually and can not use the ABI since no contract address is known (results in ENS error)

Edit: Or use the Interface I just found out about !

export async function submissionsFrom(userAddress) {
  const event = (new Interface(Bounty.abi)).events.logSubmission
  event.topics[1] = keccak256(userAddress)
   const logs = await this.provider.getLogs({
    fromBlock: 1,
    toBlock: 'latest',
    topics: event.topics
  })
  return logs.map(log => event.parse(log.topics, log.data)) 
}

Regarding hashing the addresses, it works but you're probably right (as always!). Do they get fit automatically like function params in solidity or do I have to add the padding manually here?

For clarity, does the filters.[EVENTNAME] method only work with indexed (in Solidity) parameters?

Yes, only indexed parameters are placed in topics by the EVM.

It would certainly be possible though, for me to do client side filtering on non-indexed parameters.

I would need to think more about that, since it would need some way of communicating that the filter is not saving bandwidth... interesting idea though, thanks! :)

Is it possible to apply the the v4 filters to provider.getLogs to get historical events? So perhaps something like:

let filterFromMe = contract.filters.Transfer(myAddress, null);
const logs = await provider.getLogs(filterFromMe);
... decode

Or perhaps I need to use the existing filter like

const filter = {
  address: contract.address,
  fromBlock: 0,
  topics: [contract.interface.events.Transfer.topic]
};

And somehow manipulate the topics with a nested array? (my hunch)

Thanks :)

That will totally work, and is how it is handled internally:

let filterFromMe = contract.filters.Transfer(myAddress, null);
filterFromMe.fromBlock = 0;

let logs = await provider.getLogs(filterFromMe);

More advanced filters are coming soon, which will allow:

// Check from is either myAddress OR otherAddress
let filterFromMe = contract.filters.Transfer(OR(myAddress, otherAddress), null);

I'm just trying to figure out the best way to add OR to the API. :)

Thanks @ricmoo. I tried it and it works fine. I think there is a slight issue with the typings on 4.0.1. The EventFilter which is contract.filters.Transfer(myAddress, null) is type EventFilter which doesn't have fromBlock on it:

export declare type EventFilter = {
    address?: string;
    topics?: Array<string>;
};

Perhaps instead it should be

export declare type EventFilter = {
    address?: string;
    topics?: Array<string>;
    fromBlock?: BlockTag;
    toBlock?: BLockTag
};

@ricmoo did you end up implementing the more advanced OR event filtering?

Was this page helpful?
0 / 5 - 0 ratings