Skip to content

Commit

Permalink
Fix getTransfersForX() to include NFT if it's transferred multiple ti…
Browse files Browse the repository at this point in the history
…mes in the same batch call (#311)
  • Loading branch information
thebrianchen authored Apr 25, 2023
1 parent 558f1de commit b074957
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 63 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Minor Changes

- Fixed a bug where the SDK was not compatible with `moduleResolution: bundler` when using `typescript` at `v5.0`(#302). Thanks @florrdv!
- Fixed a bug with `getTransfersForOwner()` and `getTransfersForContract()` methods in the `NftNamespace`, where some NFTs would not be returned if the NFT was transferred multiple times.

## 2.8.0

Expand Down
25 changes: 21 additions & 4 deletions src/internal/nft-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -798,10 +798,27 @@ async function getNftsForTransfers(
config,
metadataTransfers.map(transfer => transfer.token)
);
const transferredNfts = nfts.map((nft, i) => ({
...nft,
...metadataTransfers[i].metadata
}));

// The same NFT can be transferred multiple times in the same transfers response.
// We want to return one NFT for each transfer, so we create a mapping for
// each NFT to pair with the transfer metadata.
const nftsByTokenId = new Map<string, Nft>();
nfts.forEach(nft => {
const key = `${nft.contract.address}-${BigNumber.from(
nft.tokenId
).toString()}`;
nftsByTokenId.set(key, nft);
});

const transferredNfts = metadataTransfers.map(t => {
const key = `${t.token.contractAddress}-${BigNumber.from(
t.token.tokenId
).toString()}`;
return {
...nftsByTokenId.get(key)!,
...t.metadata
};
});

return {
nfts: transferredNfts,
Expand Down
76 changes: 17 additions & 59 deletions test/integration/nft.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,67 +473,25 @@ describe('E2E integration tests', () => {
expect(nftsWithAddress.length).toEqual(response5.nfts.length);
});

it('getTransfersForOwner()', async () => {
// Handles paging
const response = await alchemy.nft.getTransfersForOwner(
'vitalik.eth',
GetTransfersForOwnerTransferType.TO
);
expect(response.pageKey).toBeDefined();
expect(response.nfts.length).toBeGreaterThan(0);
const responseWithPageKey = await alchemy.nft.getTransfersForOwner(
'vitalik.eth',
GetTransfersForOwnerTransferType.TO,
{
pageKey: response.pageKey
}
);
expect(responseWithPageKey.nfts.length).toBeGreaterThan(0);
expect(response.nfts[0]).not.toEqual(responseWithPageKey.nfts[0]);

// Handles ERC1155 NFT transfers.
const response3 = await alchemy.nft.getTransfersForOwner(
'vitalik.eth',
GetTransfersForOwnerTransferType.TO,
{
tokenType: NftTokenType.ERC1155
}
);
const nfts1155 = response3.nfts.filter(
nft => nft.tokenType === NftTokenType.ERC1155
);
expect(nfts1155.length).toEqual(response3.nfts.length);
it('getTransfersForContract() with multiple transfers for same token', async () => {
// This is a sanity test since this block range contains two transfers for
// the same token that will be included in the same NFT metadata batch.
const CONTRACT = '0x0cdd3cb3bcd969c2b389488b51fb093cc0d703b1';
const START_BLOCK = 16877400;
const END_BLOCK = 16877500;

const transactions = new Set<string>();
const transfers = await alchemy.nft.getTransfersForContract(CONTRACT, {
fromBlock: START_BLOCK,
toBlock: END_BLOCK
});

// Handles ERC721 NFT transfers.
const response4 = await alchemy.nft.getTransfersForOwner(
'vitalik.eth',
GetTransfersForOwnerTransferType.FROM,
{
tokenType: NftTokenType.ERC721
}
);
const nfts721 = response4.nfts.filter(
// Some 721 transfers are ingested as NftTokenType.UNKNOWN.
nft => nft.tokenType !== NftTokenType.ERC1155
);
expect(nfts721.length).toEqual(response4.nfts.length);
transfers.nfts
.filter(t => t.tokenId === '238')
.forEach(t => transactions.add(t.transactionHash));
console.log(transfers.nfts.length);

// Handles contract address specifying.
const contractAddresses = [
'0xa1eb40c284c5b44419425c4202fa8dabff31006b',
'0x8442864d6ab62a9193be2f16580c08e0d7bcda2f'
];
const response5 = await alchemy.nft.getTransfersForOwner(
'vitalik.eth',
GetTransfersForOwnerTransferType.TO,
{
contractAddresses
}
);
const nftsWithAddress = response5.nfts.filter(nft =>
contractAddresses.includes(nft.contract.address)
);
expect(nftsWithAddress.length).toEqual(response5.nfts.length);
expect(transactions.size).toEqual(2);
});

it('getTransfersForContract()', async () => {
Expand Down

0 comments on commit b074957

Please sign in to comment.