1

Firstly, I’ll say that I prefer to do this with Foundry, but if it turns out to be too much of a pain, I’m open to doing this with ethers.js as well.

Take this block, for instance:

https://etherscan.io/txs?block=18141814

In Etherscan, you can see that there was two different NFTs that were minted on it:

https://etherscan.io/tx/0x590e73f62628542a418f523c03471dcd42ff373ac662b244c0e087bfbfa5363d

https://etherscan.io/tx/0xaefa21f43c75ab5860e1ecfeb50ffa687d9efad473517a98287df6308d875832

I used Etherscan’s advanced filter feature to find them, but you can tell they’re newly minted NFTs because the transfer() event was emitted with 0x00.. from parameter.

Now with Foundry, you can get details for the same block using cast block 18141812 --rpc-url https://eth-mainnet.alchemyapi.io/<api_key>

And then you can store an array locally with all the tx hashes it spits out. Technically you can then loop through each tx using cast run to get the event logs and find newly minted NFTs from there, but this takes forever, literally in the order of several minutes for each tx in any given block, so this doesn’t seem to be particularly feasible.

  1. Is there a more sane way to do this with Foundry, for any arbitrarily given block?
  2. If not to the above, how can this be done with ethers.js?
  3. If the filtering technique will rely on looking for transfer() events, how do you differentiate with other token standards that also use transfer() such as ERC-20?

Edit:

After looking into things a bit more on the Foundry side, I realised that you can just do cast receipt <tx> logs --rpc-url <url>. Then you can filter based on the transfer event signature (0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef) and the number of topics, per the approach in the ethers.js solution provided by @DaveDevAdvocateChainstack. I'm still keen to see a working example with cast though. At this point, I'm half-way there, just need some gaps filled in.

Given the sheer number of blocks I will be iterating through (barring API constraints), a Foundry-based solution will still be very worthwhile because of the increased speed it affords over ethers.js.

3
  • Hi just a quick note here, be careful when posting code as you left your Alchemy API key in your example. I edited the post and removed it but I recommend you delete that node:) Sep 15, 2023 at 18:48
  • @DaveDevAdvocateChainstack The reason I left the API key in my example is because it's the same one from Foundry's own docs. Good idea to keep out anyway just as a matter of practice, so appreciate the edit nevertheless.
    – user72364
    Sep 15, 2023 at 19:27
  • Interesting, maybe they use it like a public endpoint. But yeah overall, it's good practice to keep secrets, secret :) Sep 16, 2023 at 20:02

2 Answers 2

1

Dev Advocate at Chainstack here.

Unfortunately, I don't have enough experience with Foundry yet, but I can help you with ethers.

First, to answer this question:

If the filtering technique will rely on looking for transfer() events, how do you differentiate from other token standards that also use transfer(), such as ERC-20?

A Transfer event in ERC721 looks like this:

event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

Note how all of the parameters are indexed, this means that all those values will appear in the topics array when fetching the logs.

This is the Transfer event for ERC20:

event Transfer(address indexed from, address indexed to, uint256 value);

The value parameter is not indexed and will appear in the data field when fetching logs.

You can use this logic to separate ERC721 events from ERC20.

Now to get the events in a more efficient way, you can use the eth_getLogs method. This allows to specify what kind of logs you want to get, and even the block range so you can fetch logs for many blocks at once.

Then you can count how many topics each log has, if it has 4 it's an ERC721 transfer, then you can check for the zero address and you got your mints!

Here is how you can do it with ethers:

const ethers = require("ethers");

// Initialize connection to the node.
const nodeUrl = "YOUR_CHAINSTACK_ENDPOINT"

const provider = new ethers.JsonRpcProvider(nodeUrl);

// This is how many blocks from the latest you will check
const blockRange = 50;

const getLogs = async () => {
  const latestBlock = await provider.getBlockNumber();
  console.log(`Latest block: ${latestBlock}`);
  const filter = {
    fromBlock: latestBlock - blockRange,
    toBlock: "latest",
    topics: [
      "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
    ],
  };
  const logs = await provider.getLogs(filter);

  // Filter logs to ensure they are from ERC-721
  const erc721Logs = logs.filter((log) => log.topics.length === 4);

  const ZERO_ADDRESS = ethers.ZeroAddress;
  erc721Logs.forEach((log) => {
    const transactionHash = log.transactionHash;
    const blockNumber = log.blockNumber;
    const address = log.address;
    const filteredTopics = log.topics.slice(1); // Remove the first topic

    const fromAddress = "0x" + filteredTopics[0].slice(-40);
    const toAddress = "0x" + filteredTopics[1].slice(-40);
    const value = BigInt(filteredTopics[2]).toString();

    if (fromAddress === ZERO_ADDRESS) {
      console.log("Transaction Hash:", transactionHash);
      console.log("Block Number:", blockNumber);
      console.log("NFT address:", address);
      console.log("From Address:", fromAddress);
      console.log("To Address:", toAddress);
      console.log("Token ID:", value);
      console.log("-----------------------------"); 
    }
  });
};

getLogs();

With this method, you can run loops to get mint logs from a few thousand blocks at the time.

This is based on ethers V6 and will give you a result like this:

-----------------------------
Transaction Hash: 0xef48329ed53b2a8b80ce50c241990cbfc98d325672bddd66dc2a1ec6e13601b6
Block Number: 18143435
NFT address: 0xC36442b4a4522E871399CD717aBDD847Ab11FE88
From Address: 0x0000000000000000000000000000000000000000
To Address: 0xfc61049029239f9e71bbd948df5bb287aa2fa956
Token ID: 564705
-----------------------------
Transaction Hash: 0xb80e5bf8171b8f692f78dfa2d78cdfef398c53e6e3cfe82ee853987af8ebb1c9
Block Number: 18143437
NFT address: 0x835A6e20348b89831F2d23493F06f9E03a6Ce3a3
From Address: 0x0000000000000000000000000000000000000000
To Address: 0x3812649ff0b9dbaea594982b99c72810ffdf2e85
Token ID: 495
-----------------------------
Transaction Hash: 0xb80e5bf8171b8f692f78dfa2d78cdfef398c53e6e3cfe82ee853987af8ebb1c9
Block Number: 18143437
NFT address: 0x835A6e20348b89831F2d23493F06f9E03a6Ce3a3
From Address: 0x0000000000000000000000000000000000000000
To Address: 0x3812649ff0b9dbaea594982b99c72810ffdf2e85
Token ID: 496
-----------------------------

Note that the eth_getLogs method is very heavy on the node, and many providers have strict limitations.

I recommend reading this guide about Understanding eth_getLogs limitations

There are also other methods to index this kind of data. If you want to keep it current or index very old data, I'd suggest a Subgraph. Chainstack has many guides on how to develop Subgraphs.

This one specifically is for indexing ERC20 transfers, but it can be easily adapted to ERC721.

Once again, for full disclosure, I'm a developer advocate at Chainstack and I am just trying to give you options :) Feel free to reach out if you have questions!

2
  • 1
    This is working as expected. Will still keep the question open for the time being in case anyone else wants to come along and provide a Foundry-based solution as well.
    – user72364
    Sep 17, 2023 at 13:02
  • That sounds good; I'm glad it's useful for you! And try to take a look at subgraphs; it's a bit more complex, but it might be a better system if you plan to index a lot of data. I'll try to find out about a Foundry solution! Sep 18, 2023 at 12:48
0

In terms of Foundry, you could do something like:

cast rpc --raw eth_getLogs '[{"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000", null, null]}]' --rpc-url <api_key>`

This filters eth_getLogs by the transfer() event signature (0xddf252ad), a 0x000 from address and four topics to differentiate it from other standards.

You could then write a bash script utilising this with some additional logic to index NFTs.

Will still keep the question open for now because it would be interesting to see if something similar could be done in a solidity script with cheatcodes or some other abstraction that can then be run with forge.

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.