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.

This tutorial uses the alchemy_getAssetTransfers endpoint.

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.

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:

shell
$6.4.1

Setup Project Environment

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

shell
$mkdir contract-transfers
>cd contract-transfers

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

shell
$npm init

Press enter and answer the project prompt as follows:

contract-transfers.json
1package name: (contract-transfers)
2version: (1.0.0)
3description:
4entry point: (index.js)
5test command:
6git repository:
7keywords:
8author:
9license: (ISC)

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


Using Native JavaScript

Using native fetch allows us to efficiently interact with Alchemy’s endpoints and make JSON-RPC requests.


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. We can 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:

FirstTransfer.js
1const getFirstTransfer = async () => {
2 const apiKey = "<-- ALCHEMY APP API KEY -->";
3
4 const requestBody = {
5 jsonrpc: "2.0",
6 id: 1,
7 method: "alchemy_getAssetTransfers",
8 params: [{
9 fromBlock: "0x0",
10 toBlock: "latest",
11 contractAddresses: ["0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"],
12 excludeZeroValue: true,
13 category: ["erc721"],
14 }]
15 };
16
17 try {
18 const response = await fetch(`https://eth-mainnet.g.alchemy.com/v2/${apiKey}`, {
19 method: "POST",
20 headers: { "Content-Type": "application/json" },
21 body: JSON.stringify(requestBody)
22 });
23
24 const data = await response.json();
25 console.log("Example response:", data.result);
26 } catch (error) {
27 console.error("Error:", error);
28 }
29};
30
31getFirstTransfer();

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:

FirstTransfer.js
1const getFirstTransfer = async () => {
2 const apiKey = "<-- ALCHEMY APP API KEY -->";
3
4 const requestBody = {
5 jsonrpc: "2.0",
6 id: 1,
7 method: "alchemy_getAssetTransfers",
8 params: [
9 {
10 fromBlock: "0x0",
11 contractAddresses: ["0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"], // You can replace with contract of your choosing
12 excludeZeroValue: true,
13 category: ["erc721"],
14 }
15 ]
16 };
17
18 try {
19 const response = await fetch(`https://eth-mainnet.g.alchemy.com/v2/${apiKey}`, {
20 method: 'POST',
21 headers: {
22 'Content-Type': 'application/json'
23 },
24 body: JSON.stringify(requestBody)
25 });
26
27 const data = await response.json();
28 // printing the first indexed transfer event to console
29 console.log("First Transfer:", data.result.transfers[0]);
30 } catch (error) {
31 console.error("Error:", error);
32 }
33};
34
35getFirstTransfer();

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:

shell
$node FirstTransfer.js

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

json
1{
2 blockNum: '0xbc61b7',
3 hash: '0xb74538f871af833485fd3e62c5b53234403628e3be5ae369385ee24bf546f0df',
4 from: '0x0000000000000000000000000000000000000000',
5 to: '0x7772881a615cd2d326ebe0475a78f9d2963074b7',
6 value: null,
7 erc721TokenId: '0x00000000000000000000000000000000000000000000000000000000000003b7',
8 erc1155Metadata: null,
9 tokenId: '0x00000000000000000000000000000000000000000000000000000000000003b7',
10 asset: 'BAYC',
11 category: 'erc721',
12 rawContract: {
13 value: null,
14 address: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
15 decimal: null
16 }
17}
18Page 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:

shell
$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:

LastTransfer.js
1const getLastTransfer = async () => {
2 const apiKey = "<-- ALCHEMY APP API KEY -->";
3
4 const requestBody = {
5 jsonrpc: "2.0",
6 id: 1,
7 method: "alchemy_getAssetTransfers",
8 params: [{
9 fromBlock: "0x0",
10 toBlock: "latest",
11 contractAddresses: ["0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"],
12 excludeZeroValue: true,
13 category: ["erc721"],
14 }]
15 };
16
17 try {
18 const response = await fetch(`https://eth-mainnet.g.alchemy.com/v2/${apiKey}`, {
19 method: "POST",
20 headers: { "Content-Type": "application/json" },
21 body: JSON.stringify(requestBody)
22 });
23
24 const firstPage = await response.json();
25 // *** remove these lines ***
26 const firstPageLength = firstPage.transfers.length;
27 console.log(firstPage.transfers[firstPageLength - 1]);
28 let pageKey = firstPage.pageKey;
29 if (pageKey) {
30 console.log("Page key: " + pageKey);
31 } else {
32 console.log("Page key: none");
33 }
34 // *** ^^^^^^^^^^^^^^^^^^ ***
35} catch (error) {
36 console.error("Error fetching transfers:", error);
37}
38};
39
40getLastTransfer();

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

LastTransfer.js
1let pageKey = firstPage.result.pageKey;
2
3 try {
4 if (pageKey) {
5 let counter = 0;
6 while (pageKey) {
7 // Request next page with pagination
8 const nextRequestBody = {
9 jsonrpc: "2.0",
10 id: 1,
11 method: "alchemy_getAssetTransfers",
12 params: [{
13 fromBlock: "0x0",
14 toBlock: "latest",
15 contractAddresses: ["0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"],
16 excludeZeroValue: true,
17 category: ["erc721"],
18 pageKey: pageKey.toString(),
19 }]
20 };
21
22 const nextResponse = await fetch(`https://eth-mainnet.g.alchemy.com/v2/${apiKey}`, {
23 method: "POST",
24 headers: { "Content-Type": "application/json" },
25 body: JSON.stringify(nextRequestBody)
26 });
27
28 const nextPage = await nextResponse.json();
29 pageKey = nextPage.result.pageKey;
30
31 if (pageKey) {
32 counter += 1;
33 console.log("Request #" + counter + " made!");
34 continue;
35 } else {
36 const nextPageLength = nextPage.result.transfers.length;
37 const transferCount = counter * 1000 + nextPageLength;
38 console.log("Last BAYC token transfer(#" + transferCount + "):");
39 console.log(nextPage.result.transfers[nextPageLength - 1]);
40 break;
41 }
42 }
43 } else if (pageKey === undefined) {
44 const firstPageLength = firstPage.result.transfers.length;
45 console.log(firstPage.result.transfers[firstPageLength - 1]);
46 }
47 } catch (err) {
48 console.log("Something went wrong with your request: " + err);
49 }

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

Your entire LastTransfer.js script should look like this:

LastTransfer.js
1const getLastTransfer = async () => {
2 const apiKey = "<-- ALCHEMY APP API KEY -->";
3
4 // First request to get initial page
5 const firstRequestBody = {
6 jsonrpc: "2.0",
7 id: 1,
8 method: "alchemy_getAssetTransfers",
9 params: [{
10 fromBlock: "0x0",
11 toBlock: "latest",
12 contractAddresses: ["0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"],
13 excludeZeroValue: true,
14 category: ["erc721"],
15 }]
16 };
17
18 try {
19 const firstResponse = await fetch(`https://eth-mainnet.g.alchemy.com/v2/${apiKey}`, {
20 method: "POST",
21 headers: { "Content-Type": "application/json" },
22 body: JSON.stringify(firstRequestBody)
23 });
24
25 const firstPage = await firstResponse.json();
26 let pageKey = firstPage.result.pageKey;
27
28 if (pageKey) {
29 let counter = 0;
30 while (pageKey) {
31 // Request next page using pageKey
32 const nextRequestBody = {
33 jsonrpc: "2.0",
34 id: 1,
35 method: "alchemy_getAssetTransfers",
36 params: [{
37 fromBlock: "0x0",
38 toBlock: "latest",
39 contractAddresses: ["0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"],
40 excludeZeroValue: true,
41 category: ["erc721"],
42 pageKey: pageKey.toString(),
43 }]
44 };
45
46 const nextResponse = await fetch(`https://eth-mainnet.g.alchemy.com/v2/${apiKey}`, {
47 method: "POST",
48 headers: { "Content-Type": "application/json" },
49 body: JSON.stringify(nextRequestBody)
50 });
51
52 const nextPage = await nextResponse.json();
53 pageKey = nextPage.result.pageKey;
54
55 if (pageKey) {
56 counter += 1;
57 console.log("Request #" + counter + " made!");
58 continue;
59 } else {
60 const nextPageLength = nextPage.result.transfers.length;
61 const transferCount = counter * 1000 + nextPageLength;
62 console.log("Last BAYC token transfer(#" + transferCount + "):");
63 console.log(nextPage.result.transfers[nextPageLength - 1]);
64 break;
65 }
66 }
67 } else if (pageKey === undefined) {
68 const firstPageLength = firstPage.result.transfers.length;
69 console.log("Last transfer from first page:");
70 console.log(firstPage.result.transfers[firstPageLength - 1]);
71 }
72 } catch (err) {
73 console.log("Something went wrong with your request: " + err);
74 }
75};
76
77getLastTransfer();

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

shell
$node LastTransfer.js

If successful, your script should log each request:

shell
$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:

ouput
1...
2Request #73 made!
3Request #74 made!
4Request #75 made!
5Last BAYC token transfer(#75016):
6{
7 blockNum: '0xe4f531',
8 hash: '0x9363b1b2fa0808183e713c49fd9e2720e8a1592aeae13ecb899cac4a67b8d2c0',
9 from: '0x86018f67180375751fd42c26c560da2928e2b8d2',
10 to: '0x3bad83b5e9a026774f3928d1f27d9d6c0590da85',
11 value: null,
12 erc721TokenId: '0x00000000000000000000000000000000000000000000000000000000000000c0',
13 erc1155Metadata: null,
14 tokenId: '0x00000000000000000000000000000000000000000000000000000000000000c0',
15 asset: 'BAYC',
16 category: 'erc721',
17 rawContract: {
18 value: null,
19 address: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
20 decimal: null
21 }
22}

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 @Alchemy! And don’t forget to join our Discord server to meet other blockchain devs, builders, and entrepreneurs!