How does OpenSea know the IDs of my NFTs?

Checking how easily this famous platform detects the NFTs that I have associated to my account I wonder how it does it.

I know that the only way to detect the ids of my NFTs in an ERC721 contract is using the ownerOf function and looping the total supply or using an "ownerNFTids" style function using the ERC721Enumerable mappings.

But Opensea detects all NFTs you have in all contracts, regardless of whether they have a function created inherited from ERC721Enumerable.

How do they do it?

Each collection contract emits the Transfer() event log when a token is transferred, minted or burned, passing it three params:

  • from the sender address (or the zero address if it's a token mint)
  • to the recipient address (or the zero address if it's a token burn)
  • and the last param is the token ID

Each event log also contains information which contract address (NFT collection) has emitted the log.

OpenSea most likely listens to ERC721 Transfer events in each transaction, stores them in a searchable database, and aggregates their list of token holders. Then they're able to simply filter by your address and retrieve the list of tokens from this aggregated database.

1 Like

How does it know what contracts to listen to?

They can be listening to to all Transfer events (by all contracts) by specifying the topics[0] filter value as keccak256 hash of the event signature - Transfer(address,address,uint256).

More high-level example of the same principle is creating a handler in web3:

const options = {
    topics: [
        web3.utils.keccak256("Transfer(address,address,uint256)")
    ]
};
web3.eth.subscribe("logs", options, (err, data) => {
    // handle the event log
});

The same event is also emitted by the ERC20 token contracts, however the ERC721 implements the supportsInterface() method. So to distinguish between those two types of token transfers, they can call supportsInterface(0x80ac58cd) (the param is the interface ID of ERC721) on the emitting contract and see if it returns true or not.

Result of the call be cached or stored permanently (but note that contract implementations can be changed if they're behind a proxy contract), so they might also have a list of already confirmed ERC721 collections that they validate against, not having to call the supportsInterface() method each time.

2 Likes