Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: better historical event API #463

Closed
rstormsf opened this issue Mar 20, 2019 · 18 comments
Closed

Feature: better historical event API #463

rstormsf opened this issue Mar 20, 2019 · 18 comments
Labels
discussion Questions, feedback and general information. enhancement New feature or improvement.

Comments

@rstormsf
Copy link

I really like the library, but I spent an hour trying to figure out how to get past events, and when I did, it was painfully slow with provider.resetEventsBlock(fromBlock)
Please let me know how I can have the same speed as web3.getPastEvents

Thank you

@ricmoo
Copy link
Member

ricmoo commented Mar 20, 2019

You should be able to just use the provider.getLogs(filter) to query for historic events. The speed shouldn’t be any different though, are you using the most recent version of ethers?

In v5, contracts have a simpler interface too. Coming soon. :)

@ricmoo ricmoo added the discussion Questions, feedback and general information. label Mar 20, 2019
@jochenonline
Copy link

jochenonline commented Mar 20, 2019

I can confirm that getting past events with provider.getLogs is fast. I am using the latest version of ethers.

@ricmoo ricmoo added enhancement New feature or improvement. next version labels Mar 20, 2019
@ricmoo
Copy link
Member

ricmoo commented Mar 20, 2019

For a quick note about the upcoming v5, I've already added a method to Contract, which can be used as: contract.queryFilter(filter [ , fromBlock [ , toBlock ] ]), which can take in any EventFilter object (e.g. a string name, "ValueChanged"; a topic array [ topic0, [ topic1a, topic1b ] ]; a full filter { address: someAddress, topics: topics }).

I'll be putting it up soon for feedback. :)

@ricmoo ricmoo changed the title Feedback Feature: better historical event API Mar 20, 2019
@rstormsf
Copy link
Author

rstormsf commented Mar 21, 2019

@ricmoo
would you be able to provide a full example, please?
I've tried to follow the one from official documentation and it didn't seem to work :-(
I really think I'm doing something wrong, if you could perhaps show the full example, I'd really appreciate it.

I tried just using

let contractEnsName = '0x123....123';

let topic = ethers.utils.id("nameRegistered(bytes32,address,uint256)");

let filter = {
    address: contractEnsName,
    fromBlock: 7400416,
    toBlock: 'latest',
    topics: [ topic ]
}

provider.getLogs(filter).then((result) => {
    console.log(result);
})

a few questions come up:

  1. if I already have a contract instance variable, why I can't access the topic hash from it?
  2. If I search for events in the official documentation url, I get a "different" events section, not related to eth-events(aka logs)
  3. is there a way to do allEvents like in web3.js library

Thank you

I'm still confused by both answers above because they don't provide actual examples.
It's great to have an explanation, but the actual code is self-explanatory like in web3.js docs.

The reason why I want to migrate off web3.js - is because they have been inconsistent with their stability (like if you switch from npm to yarn - things will break) and I really loved ethers.js from day1(it was yesterday) because you allow doing things like nonce overwriting in the tx construction, non-hardcoded 50 blocks wait to throw and bunch of other cool stuff.

We still need truffle team to convince to generate ethers.js-type of readable abi type, so we can just copy/paste from it.

@ricmoo
Copy link
Member

ricmoo commented Mar 21, 2019

So, for a few examples (which I agree, the documentation could certainly use more of):

let abi = [
    "event Transfer(bytes32 indexed node, address owner)"
];
let ens = new Contract("0x314159265dD8dbb310642f98f50C066173C1259b", abi, provider)

// Get the filter event
filter = ens.filters.Transfer()
// {
//   address: "0x314159265dD8dbb310642f98f50C066173C1259b",
//   topics: [ "0xd4735d920b0f87494915f556dd9b54c8f309026070caea5c737245152564d266" ]
// }

// Oryou could pass in a parameter to the above call to filter by node, sine it is indexed,
// let filter = ens.filters.Transfer(ethers.utils.namehash("ricmoo.firefly.eth"));

// Now you can specify fromBlock and toBlock (you may pass in promises; no need to await)
filter.fromBlock = provider.getBlockNumber().then((b) => b - 10000);
filter.toBlock = "latest";

// And query:
provider.getLogs(filter).then((logs) => {
    console.log(logs);
});

// Of course, you can get all events, just by not including topics (again, you can use promises):
let filterAll = {
    address: provider.getNetwork().then((n) => n.ensAddress),
    fromBlock: provider.getBlockNumber().then((b) => b - 10000),
    toBlock: "latest"
}

// Or if you want an event listener, you can do something like:
contract.on("Transfer", function(node, owner, eventObject) {
    console.log(node, owner, eventObject);
});

// You could also use a filter object:
// contract.on(ens.filters.Transfer(), function(node, owner, eventObject) { ... }

// Or listen for all events:
contract.on("*", function(eventObject) {
    // If the event matches anything in the ABI, it will add the parsed parameters for you,
    // otherwise, you only get the raw data and topics.
    console.log(eventObject);
});

I will focus on getting more examples together in the next round of documentation, since I'll be sprucing it up for v5, but hopefully that will get you started. Here is an overview on the eventObject too, which is quite useful: https://docs.ethers.io/ethers.js/html/api-contract.html#event-emitter

Let me know if you have any more questions.

@rstormsf
Copy link
Author

rstormsf commented Mar 23, 2019

@ricmoo thank you so much! Now I'm ready to say good bye to web3.js
Things like this is killing me. Hopefully ether.js can also do dry-run on state changing methods

@rstormsf
Copy link
Author

@ricmoo
Here is what I ended up doing:

const ethers = require('ethers')
const fs = require('fs')

let abi = [
    "event Transfer(address indexed from, address indexed to, uint tokens)"
];

let infuraProvider = new ethers.providers.InfuraProvider('mainnet');
let ens = new ethers.Contract("0xB8c77482e45F1F44dE1745F52C74426C631bDD52", abi, infuraProvider)

filter = ens.filters.Transfer()
filter.fromBlock = infuraProvider.getBlockNumber().then((b) => b - 10000);
filter.toBlock = "latest";

infuraProvider.getLogs(filter).then((logs) => {
    logs.forEach((log) => {
        const data = ens.interface.parseLog(log)
        fs.appendFileSync('addressesBNB.csv', data.values.to +'\n')
    })
})

is there a way to do getLogs without additional parseLog func? just to optimize things up?

@ricmoo
Copy link
Member

ricmoo commented Mar 25, 2019

I think that is as good as you are going to get for now. In v5, you will be able to do:

ens.queryFilter(ens.filters.Transfer(), infuraProvider.getBlockNumber().then((b) => b - 10000), "latest")

which should be coming out soon, as a public beta. :)

@rstormsf
Copy link
Author

rstormsf commented Mar 25, 2019

@ricmoo I was able to record a video and tell the world about ethers.js
https://twitter.com/rstormsf/status/1110250992111476737
Keep up the good work!
Human Readable ABI - is the killer feature. I was able to quickly make a call to the contract function during the video tutorial

ricmoo added a commit that referenced this issue May 24, 2019
@ricmoo
Copy link
Member

ricmoo commented May 24, 2019

The contract.queryFilter ( filter [ , fromBlock [ , toBlock ] ] ) should be available now in the latest version of the v5 beta. Try it out and let me know if you have any problems.

The dry-run can be done using the new contract.callStatic.transfer(); callStatic will always use a call. But please keep in mind that blockchains are eventually-consistent, and a success/failure to a function using staticCall can still not jive with actual results for myriad reasons...

Thanks! :)

@ricmoo ricmoo closed this as completed May 24, 2019
@RobertoSnap
Copy link

RobertoSnap commented Mar 13, 2020

This works very well inn in v5, thanks Ricmoo!

For those still on v4, here is another simple example:

                const topicTransfer= ethers.utils.id("Transfer(address,address,uint256)") //This is the interface for your event
                console.log("topic ID for your event", topicTransfer);
                const web3 = await getWeb3(); // Your provider
                const logs = await web3.getLogs({
                    fromBlock: 0,
                    address: "0xC841dCc53D20560c77F0C6d858383568CDf96182", // Address of contract
                    toBlock: 'latest',
                    topics: [topicTransfer]
                }).catch(() => [])

                console.log("logs qued", logs);

@Etienereum
Copy link

Etienereum commented May 13, 2020

The transfer Event on the Smart Contract: event Transfer(address indexed from, address indexed to, uint256 value);

// Get the Users Account Transfer Events.
var txn = await tokenInstance.getPastEvents("Transfer", { filter: { from: userAccount }, fromBlock: 0, toBloack: "lastest", }); console.log(txn);

The above code is using web3. How can I replicate this simple code with ethers. Getting an array of the 'Transfer' event, so that I can get the address indexed _to and the uint256 _value

@ricmoo
Copy link
Member

ricmoo commented May 14, 2020

If you are using v5 (which I recommend) this has been made much easier.

// Get the filter (the second null could be omitted)
const filter = tokenInstance.filters.Transfer(userAccount, null);

// Query the filter (the latest could be omitted)
const logs = tokenInstance.queryFilter(filter, 0, "latest");

// Print out all the values:
logs.forEach((log) => {
  // The log object contains lots of useful things, but the args are what you prolly want)
  console.log(log.args._to, log.args._value);
});

@imaksp
Copy link

imaksp commented Jun 22, 2020

@ricmoo is it posible to filter by multiple addresses like in web3?
{ filter: { from: [userAccount1, userAccount2] }, fromBlock: 0, toBlock: 'lastest' })

@Albert-Gao
Copy link

@ricmoo

The code above works beautifully, I can now get all the transactions with
image

Just 2 questions:

  1. how could I get the from and to history with one filter? So I do not need to merge them later?
  2. how to get the time stamp of each transaction? invoking getTransaction for each log? better way?

image

Thanks :) Super time saver, i was looking into thegraph since the price plan of Etherscan is too pricey, but this API solves the getting transaction history problem.

@zemse
Copy link
Collaborator

zemse commented Oct 22, 2021

@Albert-Gao

how could I get the from and to history with one filter? So I do not need to merge them later?

Right now there isn't a way to get the union directly from the rpc. Maybe you can try parallelizing both requests.

const fromLogsPromise = contract.queryFilter(...);
const toLogsPromise = contract.queryFilter(...);
const [fromLogs, toLogs] =  await Promise.all([fromLogsPromise, toLogsPromise]);

how to get the time stamp of each transaction? invoking getTransaction for each log? better way?

Rpc doesn't include timestamp in the logs. So you have to call getTransaction. If you are doing this in frontend, then you may use an indexing service like TheGraph.

@kimiro34 Is it possible for you to deploy this contract to any public testnet, so that we can try to reproduce the problem?

@annguyen97dev
Copy link

annguyen97dev commented Jun 25, 2022

If you are using v5 (which I recommend) this has been made much easier.

// Get the filter (the second null could be omitted)
const filter = tokenInstance.filters.Transfer(userAccount, null);

// Query the filter (the latest could be omitted)
const logs = tokenInstance.queryFilter(filter, 0, "latest");

// Print out all the values:
logs.forEach((log) => {
  // The log object contains lots of useful things, but the args are what you prolly want)
  console.log(log.args._to, log.args._value);
});

when I use contract.filter.event(), it's have an error: myContract.filter.myEvent() is not the function. How can I handle this issue? please help me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Questions, feedback and general information. enhancement New feature or improvement.
Projects
None yet
Development

No branches or pull requests

10 participants