How to Get a Contract's Last Transfer Event

Learn how to use Alchemy's SDK to query the transfer history of one or multiple smart contracts in a single request.

📘

API Endpoint

This tutorial uses the alchemy_getAssetTransfers endpoint.

If you just need the script for this tutorial refer to the below Recipe or continue reading for more

Have you ever wanted to know what's going on under the hood of a smart contract? One way to get insight into how a smart contract is used is to track a contract's transfer events. This allows you to examine how users/addresses interact with it.

In this guide, you will query the last transfer even of the BAYC smart contract, though you may choose another contract of your preference.

The following is a list of potential use cases:

  • Create an NFT tracker for reporting on the latest trades.
  • Create a DeFi tracker of a particular dex to get the latest transfer info or provide current information.
  • Create Crypto Whale Twitter bot.
  • Create the transfer history of a particular address or smart contract.
  • Build smart contract logic that requires the most up-to-date transfer info.

📘

Note:

If you already completed "How to get a contract's first transfer event" you may skip the setup and installation steps.


Install Node.js

Head to Node.js and download the LTS version.

You can verify your installation was successful by running npm -version in your macOS terminal or Windows command prompt. A successful installation will display a version number, such as:

6.4.1

Setup Project Environment

Open VS Code (or your preferred IDE) and enter the following in terminal:

mkdir contract-transfers
cd contract-transfers

Once inside our project directory, initialize npm (node package manager) with the following command:

npm init

Press enter and answer the project prompt as follows:

package name: (contract-transfers)
version: (1.0.0)
description: 
entry point: (index.js)
test command: 
git repository: 
keywords: 
author: 
license: (ISC)

Press enter again to complete the prompt. If successful, a package.json file will have been created in your directory.


Install Alchemy's SDK

Alchemy's SDK allows us to more efficiently interact with Alchemy's endpoints and make JSON-RPC requests.

Ensure you are inside your project folder and type the following command in the terminal:

npm install alchemy-sdk

Get Contract's Last Transfer Event

In this section, we will use Alchemy's [Transfer API]ref:transfers-api) to retrieve the contract's last transfer event. The Alchemy SDK allows us to call the alchemy_getAssetTransfers function and filter transfers by passing in the following object parameters:

PropertyDescriptionRequirementsDefault
fromBlockIndicates from which block the endpoint searches. Inclusive and can be a hex string, integer, or latest.Optional"0x0"
toBlockIndicates to which block the endpoint searches. Inclusive and can be a hex string, integer, or latest.Optionallatest
fromAddressIndicates the sending address in the transaction. Can be a hex string.OptionalWildcard - any address
toAddressIndicates the receiving address in the transaction. Can be a hex string.OptionalWildcard - any address
contractAddressesAn array of contact addresses to filter for. Note: Only applies to transfers of token, erc20, erc721, and erc1155.OptionalWildcard - any address
categoryAn array of transfer categories. Can be any of the following: external, internal, erc20, erc721, or erc1155.Required
excludeZeroValueA boolean to exclude transfers of zero value. A zero value is not the same as null.Optionaltrue
maxCountThe maximum number of results to return per call. Note: 1000 is the max per request.Optional1000 or 0x3e8
pageKeyUse for pagination. If more results are available after the response, a uuid property will return. You can use this in subsequent requests to retrieve the next 1000 results of maxCount.Optional

For reference, here is an example of how the above parameters could be passed into getAssetTransfers:

alchemy.core.getAssetTransfers({
    fromBlock: "0x0",
    toBlock: "latest",
    contractAddresses: ["0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"],
    excludeZeroValue: true,
    category: ["erc721"],
  });

To get started finding a contract's first transfer, let's create a file inside our project folder named FirstTransfer.js and add the following code to utilize the alchemy_getAssetTransfers endpoint:

// Setup: npm install alchemy-sdk
import { Alchemy, Network } from "alchemy-sdk";

const config = {
  apiKey: "<-- ALCHEMY APP API KEY -->",
  network: Network.ETH_MAINNET,
};
const alchemy = new Alchemy(config);

const getFirstTransfer = async () => {
  // Calling the getAssetTransfers function and filters using the following parameters
  const allTransfers = await alchemy.core.getAssetTransfers({
    fromBlock: "0x0",
    contractAddresses: ["0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"], // You can replace with contract of your choosing
    excludeZeroValue: true,
    category: ["erc721"],
  });

  // printing the first indexed transfer event to console
  console.log("First Transfer:", allTransfers.transfers[0]);
};

getFirstTransfer();

Above, we created an async function called getFirstTransfer.To learn more about how what it does, view the commented notes above.

To use your script, type the following command in your terminal:

node FirstTransfer.js

If successful, you should see the following transfer object in your output:

{
  blockNum: '0xbc61b7',
  hash: '0xb74538f871af833485fd3e62c5b53234403628e3be5ae369385ee24bf546f0df',
  from: '0x0000000000000000000000000000000000000000',
  to: '0x7772881a615cd2d326ebe0475a78f9d2963074b7',
  value: null,
  erc721TokenId: '0x00000000000000000000000000000000000000000000000000000000000003b7',
  erc1155Metadata: null,
  tokenId: '0x00000000000000000000000000000000000000000000000000000000000003b7',
  asset: 'BAYC',
  category: 'erc721',
  rawContract: {
    value: null,
    address: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
    decimal: null
  }
}
Page key: 915e662f-a7ca-4c4f-a5e9-0bbf2c6a53f1

If your output includes a page key, you can use the value for pagination in subsequent requests. If you've received the latest transfer event, your result will not include a page key and reads as:

Page key: none

In that scenario, congratulations, you've successfully queried some of the latest transfer events!

However, in our case, we did receive a UUID page key. This is because the BAYC contract contains more than 1000 transfer events (the maximum allowed per getAssetTransfers request). To learn more about how to use page keys, continue on to the following section.


Use Page Keys

To account for potential page keys, we will create a loop to check whether getAssetTransfer returns a page key. If it does, the loop will continuously call getAssetTransfers until a page key no longer returns.

To do this, we need to reorganize our code to make room for the while loop. Remove lines 18-25 of LastTransfer.js:

// Setup: npm install alchemy-sdk
import { Alchemy, Network } from "alchemy-sdk";

const config = {
  apiKey: "<-- ALCHEMY APP API KEY -->",
  network: Network.ETH_MAINNET,
};
const alchemy = new Alchemy(config);

const getLastTransfer = async () => {
  const getTransfers = alchemy.core.getAssetTransfers({
    fromBlock: "0x0",
    toBlock: "latest",
    contractAddresses: ["0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"],
    excludeZeroValue: true,
    category: ["erc721"],
  });
  const firstPage = await getTransfers;
  // *** remove these lines ***
  const firstPageLength = firstPage.transfers.length;
  console.log(firstPage.transfers[firstPageLength - 1]);
  let pageKey = firstPage.pageKey;
  if (pageKey) {
    console.log("Page key: " + pageKey);
  } else {
    console.log("Page key: none");
  }
  // *** ^^^^^^^^^^^^^^^^^^ ***
};

getLastTransfer();

Now, replace lines 18-25 with the following code block:

let pageKey = firstPage.pageKey;

  try {
    if (pageKey) {
      let counter = 0;
      while (pageKey) {
        const nextKey = alchemy.core.getAssetTransfers({
          fromBlock: "0x0",
          toBlock: "latest",
          contractAddresses: ["0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"],
          excludeZeroValue: true,
          category: ["erc721"],
          pageKey: pageKey.toString(),
        });

        const nextPage = await nextKey;
        pageKey = nextPage.pageKey;

        if (pageKey) {
          counter += 1;
          console.log("Request #" + counter + " made!");
          continue;
        } else {
          const nextPageLength = nextPage.transfers.length;
          const transferCount = counter * 1000 + nextPageLength;
          console.log("Last BAYC token transfer(#" + transferCount + "):");
          console.log(nextPage.transfers[nextPageLength - 1]);
          break;
        }
      }
    } else if (pageKey === undefined) {
      const firstPageLength = firstPage.transfers.length;
      console.log(firstPage.transfers[firstPageLength - 1]);
    }
  } catch (err) {
    console.log("Something went wrong with your request: " + err);
  }
};

👍

Tip

To dive into the loop's details, check out the commented code above.

Your entire LastTransfer.js script should look like this:

// Setup: npm install alchemy-sdk
import { Alchemy, Network } from "alchemy-sdk";

const config = {
  apiKey: "<-- ALCHEMY APP API KEY -->",
  network: Network.ETH_MAINNET,
};
const alchemy = new Alchemy(config);

const getLastTransfer = async () => {
  const getTransfers = alchemy.core.getAssetTransfers({
    fromBlock: "0x0",
    toBlock: "latest",
    contractAddresses: ["0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"],
    excludeZeroValue: true,
    category: ["erc721"],
  });

  const firstPage = await getTransfers;
  let pageKey = firstPage.pageKey;

  try {
    if (pageKey) {
      let counter = 0;
      while (pageKey) {
        const nextKey = alchemy.core.getAssetTransfers({
          fromBlock: "0x0",
          toBlock: "latest",
          contractAddresses: ["0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"],
          excludeZeroValue: true,
          category: ["erc721"],
          pageKey: pageKey.toString(),
        });

        const nextPage = await nextKey;
        pageKey = nextPage.pageKey;
        
        if (pageKey) {
          counter += 1;
          console.log("Request #" + counter + " made!");
          continue;
        } else {
          const nextPageLength = nextPage.transfers.length;
          const transferCount = counter * 1000 + nextPageLength;
          console.log("Last BAYC token transfer(#" + transferCount + "):");
          console.log(nextPage.transfers[nextPageLength - 1]);
          break;
        }
      }
    } else if (pageKey === undefined) {
      const firstPageLength = firstPage.transfers.length;
      console.log(firstPage.transfers[firstPageLength - 1]);
    }
  } catch (err) {
    console.log("Something went wrong with your request: " + err);
  }
};

getLastTransfer();

To test our script, run the following code in your terminal:

node LastTransfer.js

If successful, your script should log each request:

Request #1 made!
Request #2 made!
Request #3 made!

Once your script loops through the contract's entire transfer history, you should see an output similar to the following:

...
Request #73 made!
Request #74 made!
Request #75 made!
Last BAYC token transfer(#75016):
{
  blockNum: '0xe4f531',
  hash: '0x9363b1b2fa0808183e713c49fd9e2720e8a1592aeae13ecb899cac4a67b8d2c0',
  from: '0x86018f67180375751fd42c26c560da2928e2b8d2',
  to: '0x3bad83b5e9a026774f3928d1f27d9d6c0590da85',
  value: null,
  erc721TokenId: '0x00000000000000000000000000000000000000000000000000000000000000c0',
  erc1155Metadata: null,
  tokenId: '0x00000000000000000000000000000000000000000000000000000000000000c0',
  asset: 'BAYC',
  category: 'erc721',
  rawContract: {
    value: null,
    address: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
    decimal: null
  }
}

Hooray! You have successfully used pagination to get the latest transfer of the BAYC contract!

If you enjoyed this tutorial for retrieving a contract's latest transfer event, give us a tweet @AlchemyPlatform! And don't forget to join our Discord server to meet other blockchain devs, builders, and entrepreneurs!


ReadMe