2. Tracking ERC721 transfers

We build APIs for recent Nouns transfers and a leaderboard of most popular NFTs (by transfer counts)

Let's index ERC721s / NFTs, tracking any instance in which one transferred. In this canvas, we'll hook on all contracts that implement the ERC721 standard and track the Transfer log.

The ERC721 standard actually includes three transfer functions: transferFrom, and two variants of safeTransferFrom. The same Transfer event is typically emitted in each of these cases, as well as often for mint and burn ops.

If you want to skip to the end, here's the canvas that already has everything built.


Steps

Set up the Lambda

  1. Add an EVM Lambda to the canvas. For the hook, choose ABI > Classification > ERC721 and hit enter. Select the Transfer log hook. Leave the default hook position (Post) and callback name (postTransferLog). Toggle on the Generate schema feature as it'll give us a good template. Then hit Add.

  2. A TransferLog schema is automatically generated with a few fields. Hit the Test button at the top-right of the IDE and then hit Run. You'll see logs emitted to the console.

  3. Now let's add more details and re-arrange: click the menu next to the schema and select Edit. Add fields and rearrange:

    NameType
    txnHashbytes32
    globalCounteruint256
    blockNumberuint64
    tokenAddressaddress
    tokenIduint256
    fromAddressaddress
    toAddressaddress
  4. When you're done editing the schema, hit Save. Now we need to update the callback to emit the new data:

    function postTransferLog() public {
        postTransferLogContext storage ctx = getPostTransferLogContext();
        simEmitToSchema_TransferLog(
            SchemaTransferLogColumns({
                txnHash: ctx.txn.hash(),
                globalCounter: simGlobalCounter(),
                blockNumber: block.number,
                tokenAddress: ctx.txn.to(),
                tokenId: ctx.tokenId,
                fromAddress: ctx.from,
                toAddress: ctx.to    
            })
        );
    }
  1. Test again when you're done and you'll see the extra fields in the emitted logs.

Build the pipeline

  1. Close the IDE for the EVM Lambda component. You'll see a Schema component was automatically added to your canvas.
  2. Click on the left handle of the Lambda component to add a Data source. In the data source component, set the From in the block range to 1. While the ERC721 standard was formally accepted in early 2018, we'll run from genesis to include contracts that implemented the standard before its acceptance. Leave the To blank to let it keep running at the tip.
  3. Click on the right handle of the Schema component to add a Persistence. Name the persistence something like erc721_transfers and set the persistence.
  4. Hit the play button on the execution edge. In a few moments, we should see both tip and backfill executions in the execution edge status (mouse over the i).

Querying and building APIs

You can prototype queries using the query editor. Let's use some we've prebaked to make APIs.

  1. Add an API component to the canvas. It doesn't have to be connected to any other components. For the first API, let's do a simple one that shows the N most recent Nouns owner changes. Use the following query:
select * from @org.erc721_owner_changes
where tokenAddress = '0x9c8ff314c9bc7f6e59a9d9225fb22946427edc03'
order by globalCounter desc
limit $limit
  1. For the limit parameter, use type uint32 (a few others would work too), and set the default value to 1.
  2. Test the query using the test interaction, then set the API, activate it, and try the cURL request available in the actions menu.
  3. For our second API, let's build a leaderboard of the most popular NFT contracts over a user-defined block range, sorting by the number of transfers. Here's a query for that:
with most_recent_block as (
    select max(blockNumber) as most_recent_block from @org.erc721_transfers
)
select 
    tokenAddress,
    count(*) as cnt
from @org.erc721_transfers
where blockNumber >= (select * from most_recent_block) - $block_range
group by tokenAddress
order by cnt desc
limit $limit
  1. Both block_range and limit parameters can be typed as uint32, and 1000 and 5 would make sense as default parameters. Again, test the query and API using the test feature and the cURL.

Key takeaways

  1. You can easily hook on all contracts that implement a certain standard, like ERC721, even if they have vastly different implementations.
  2. You can query and build APIs against all of your persistences as well as sim core tables from any canvas.

Join our Telegram