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!