From 1be993c8d9e4fca324f14fdfd7a5e743aba62fab Mon Sep 17 00:00:00 2001 From: Kien Ngo Date: Wed, 1 Nov 2023 16:58:52 -0400 Subject: [PATCH 01/11] Add pagination to erc721 getAllOwners --- .../src/evm/core/classes/delayed-reveal.ts | 23 +++++++++---------- .../src/evm/core/classes/erc-721-supply.ts | 9 ++++++-- packages/sdk/src/evm/core/classes/erc-721.ts | 6 ++--- packages/sdk/test/evm/nft.test.ts | 20 ++++++++++++++++ 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/packages/sdk/src/evm/core/classes/delayed-reveal.ts b/packages/sdk/src/evm/core/classes/delayed-reveal.ts index 8dfbe511506..4005a70fbe1 100644 --- a/packages/sdk/src/evm/core/classes/delayed-reveal.ts +++ b/packages/sdk/src/evm/core/classes/delayed-reveal.ts @@ -109,18 +109,19 @@ export class DelayedReveal< throw new Error("Password is required"); } - const placeholderUris = await this.storage.uploadBatch( - [CommonNFTInput.parse(placeholder)], - { - rewriteFileNames: { - fileStartNumber: 0, - }, - }, - ); + const [placeholderUris, startFileNumber, baseUriId, legacyContract] = + await Promise.all([ + this.storage.uploadBatch([CommonNFTInput.parse(placeholder)], { + rewriteFileNames: { + fileStartNumber: 0, + }, + }), + this.nextTokenIdToMintFn(), + this.contractWrapper.read("getBaseURICount", []), + this.isLegacyContract(), + ]); const placeholderUri = getBaseUriFromBatch(placeholderUris); - const startFileNumber = await this.nextTokenIdToMintFn(); - const uris = await this.storage.uploadBatch( metadatas.map((m) => CommonNFTInput.parse(m)), { @@ -132,7 +133,6 @@ export class DelayedReveal< ); const baseUri = getBaseUriFromBatch(uris); - const baseUriId = await this.contractWrapper.read("getBaseURICount", []); const hashedPassword = await this.hashDelayRevealPassword( baseUriId, password, @@ -143,7 +143,6 @@ export class DelayedReveal< ); let data: string; - const legacyContract = await this.isLegacyContract(); if (legacyContract) { data = encryptedBaseUri; } else { diff --git a/packages/sdk/src/evm/core/classes/erc-721-supply.ts b/packages/sdk/src/evm/core/classes/erc-721-supply.ts index aed10a9dff4..a4ad1cb581e 100644 --- a/packages/sdk/src/evm/core/classes/erc-721-supply.ts +++ b/packages/sdk/src/evm/core/classes/erc-721-supply.ts @@ -87,7 +87,7 @@ export class Erc721Supply implements DetectableFeature { * Return all the owners of each token id in this contract * @returns */ - public async allOwners() { + public async allOwners(queryParams?: QueryAllParams) { let totalCount: BigNumber; let startTokenId = BigNumber.from(0); if (hasFunction("startTokenId", this.contractWrapper)) { @@ -103,7 +103,12 @@ export class Erc721Supply implements DetectableFeature { // TODO use multicall3 if available // TODO can't call toNumber() here, this can be a very large number - const arr = [...new Array(totalCount.toNumber()).keys()]; + let arr = [...new Array(totalCount.toNumber()).keys()]; + if (queryParams) { + const start = queryParams?.start || 0; + const count = queryParams?.count || DEFAULT_QUERY_ALL_COUNT; + arr = arr.slice(start, start + count); + } const owners = await Promise.all( arr.map((i) => this.erc721.ownerOf(i).catch(() => constants.AddressZero)), ); diff --git a/packages/sdk/src/evm/core/classes/erc-721.ts b/packages/sdk/src/evm/core/classes/erc-721.ts index 40e619fafce..9cca418ec3f 100644 --- a/packages/sdk/src/evm/core/classes/erc-721.ts +++ b/packages/sdk/src/evm/core/classes/erc-721.ts @@ -372,8 +372,8 @@ export class Erc721< * @returns an array of token ids and owners * @twfeature ERC721Supply | ERC721Enumerable */ - public async getAllOwners() { - return assertEnabled(this.query, FEATURE_NFT_SUPPLY).allOwners(); + public async getAllOwners(queryParams?: QueryAllParams) { + return assertEnabled(this.query, FEATURE_NFT_SUPPLY).allOwners(queryParams); } /** @@ -435,7 +435,7 @@ export class Erc721< } else { const [address, allOwners] = await Promise.all([ walletAddress || this.contractWrapper.getSignerAddress(), - this.getAllOwners(), + this.getAllOwners(queryParams), ]); let ownedTokens = (allOwners || []).filter( (i) => address?.toLowerCase() === i.owner?.toLowerCase(), diff --git a/packages/sdk/test/evm/nft.test.ts b/packages/sdk/test/evm/nft.test.ts index ec6813073c3..827f19c1d3b 100644 --- a/packages/sdk/test/evm/nft.test.ts +++ b/packages/sdk/test/evm/nft.test.ts @@ -330,6 +330,26 @@ describe("NFT Contract", async () => { expect(nftPage2[2].metadata.id).to.eq("4"); }); + it("should respect pagination for getAllOwners", async () => { + const _tokenIds: number[] = Array.from( + { length: 101 }, + (_, index) => index, + ); // [0, 1, ... 100] + const metadata = _tokenIds.map((num) => ({ name: `Test${num}` })); + await nftContract.mintBatch(metadata); + const [page1, page2] = await Promise.all([ + nftContract.erc721.getAllOwners({ start: 0, count: 10 }), + nftContract.erc721.getAllOwners({ start: 5, count: 8 }), + ]); + expect(page1).to.be.an("array").length(10); + expect(page1[0].tokenId).to.eq(0); + expect(page1[9].tokenId).to.eq(9); + + expect(page2).to.be.an("array").length(8); + expect(page2[0].tokenId).to.eq(5); + expect(page2[9].tokenId).to.eq(12); + }); + it("getOwned should return all item when queryParams.count is greater than the total supply (erc-721-standard.ts)", async () => { const _tokenIds: number[] = Array.from({ length: 11 }, (_, index) => index); // [0, 1, ... 10] const metadata = _tokenIds.map((num) => ({ name: `Test${num}` })); From 10d9b43dda83ff93796d6ab4c273f917def9f16b Mon Sep 17 00:00:00 2001 From: Kien Ngo Date: Wed, 1 Nov 2023 16:59:36 -0400 Subject: [PATCH 02/11] Add changeset --- .changeset/tiny-scissors-raise.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tiny-scissors-raise.md diff --git a/.changeset/tiny-scissors-raise.md b/.changeset/tiny-scissors-raise.md new file mode 100644 index 00000000000..ca268c02a44 --- /dev/null +++ b/.changeset/tiny-scissors-raise.md @@ -0,0 +1,5 @@ +--- +"@thirdweb-dev/sdk": patch +--- + +Add pagination to ERC721's getAllOwners method From 913f5a99db30b37ff1d38ad2f8a9a184f88b3ba1 Mon Sep 17 00:00:00 2001 From: Kien Ngo Date: Wed, 1 Nov 2023 17:01:53 -0400 Subject: [PATCH 03/11] Revert unwanted changes --- .../src/evm/core/classes/delayed-reveal.ts | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/sdk/src/evm/core/classes/delayed-reveal.ts b/packages/sdk/src/evm/core/classes/delayed-reveal.ts index 4005a70fbe1..8dfbe511506 100644 --- a/packages/sdk/src/evm/core/classes/delayed-reveal.ts +++ b/packages/sdk/src/evm/core/classes/delayed-reveal.ts @@ -109,19 +109,18 @@ export class DelayedReveal< throw new Error("Password is required"); } - const [placeholderUris, startFileNumber, baseUriId, legacyContract] = - await Promise.all([ - this.storage.uploadBatch([CommonNFTInput.parse(placeholder)], { - rewriteFileNames: { - fileStartNumber: 0, - }, - }), - this.nextTokenIdToMintFn(), - this.contractWrapper.read("getBaseURICount", []), - this.isLegacyContract(), - ]); + const placeholderUris = await this.storage.uploadBatch( + [CommonNFTInput.parse(placeholder)], + { + rewriteFileNames: { + fileStartNumber: 0, + }, + }, + ); const placeholderUri = getBaseUriFromBatch(placeholderUris); + const startFileNumber = await this.nextTokenIdToMintFn(); + const uris = await this.storage.uploadBatch( metadatas.map((m) => CommonNFTInput.parse(m)), { @@ -133,6 +132,7 @@ export class DelayedReveal< ); const baseUri = getBaseUriFromBatch(uris); + const baseUriId = await this.contractWrapper.read("getBaseURICount", []); const hashedPassword = await this.hashDelayRevealPassword( baseUriId, password, @@ -143,6 +143,7 @@ export class DelayedReveal< ); let data: string; + const legacyContract = await this.isLegacyContract(); if (legacyContract) { data = encryptedBaseUri; } else { From 237995f5042dadab7b11de4e8061897f7869aace Mon Sep 17 00:00:00 2001 From: Kien Ngo Date: Wed, 1 Nov 2023 17:24:42 -0400 Subject: [PATCH 04/11] Fix test' ' --- packages/sdk/test/evm/nft.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/test/evm/nft.test.ts b/packages/sdk/test/evm/nft.test.ts index 827f19c1d3b..45a181b5942 100644 --- a/packages/sdk/test/evm/nft.test.ts +++ b/packages/sdk/test/evm/nft.test.ts @@ -347,7 +347,7 @@ describe("NFT Contract", async () => { expect(page2).to.be.an("array").length(8); expect(page2[0].tokenId).to.eq(5); - expect(page2[9].tokenId).to.eq(12); + expect(page2[7].tokenId).to.eq(12); }); it("getOwned should return all item when queryParams.count is greater than the total supply (erc-721-standard.ts)", async () => { From 3763ae26028e634419c660a789de60b9b0248b4b Mon Sep 17 00:00:00 2001 From: Kien Ngo Date: Wed, 1 Nov 2023 17:31:30 -0400 Subject: [PATCH 05/11] Reduce nft mint amount in test --- packages/sdk/test/evm/nft.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sdk/test/evm/nft.test.ts b/packages/sdk/test/evm/nft.test.ts index 45a181b5942..0cd94301ee4 100644 --- a/packages/sdk/test/evm/nft.test.ts +++ b/packages/sdk/test/evm/nft.test.ts @@ -332,9 +332,9 @@ describe("NFT Contract", async () => { it("should respect pagination for getAllOwners", async () => { const _tokenIds: number[] = Array.from( - { length: 101 }, + { length: 31 }, (_, index) => index, - ); // [0, 1, ... 100] + ); // [0, 1, ... 30] const metadata = _tokenIds.map((num) => ({ name: `Test${num}` })); await nftContract.mintBatch(metadata); const [page1, page2] = await Promise.all([ From 2716132d538c06e8611ed399bf381e795ed363ab Mon Sep 17 00:00:00 2001 From: Kien Ngo Date: Wed, 1 Nov 2023 18:01:37 -0400 Subject: [PATCH 06/11] Update test --- packages/sdk/test/evm/nft.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/sdk/test/evm/nft.test.ts b/packages/sdk/test/evm/nft.test.ts index 0cd94301ee4..143c09f6e19 100644 --- a/packages/sdk/test/evm/nft.test.ts +++ b/packages/sdk/test/evm/nft.test.ts @@ -331,10 +331,7 @@ describe("NFT Contract", async () => { }); it("should respect pagination for getAllOwners", async () => { - const _tokenIds: number[] = Array.from( - { length: 31 }, - (_, index) => index, - ); // [0, 1, ... 30] + const _tokenIds: number[] = Array.from({ length: 31 }, (_, index) => index); // [0, 1, ... 30] const metadata = _tokenIds.map((num) => ({ name: `Test${num}` })); await nftContract.mintBatch(metadata); const [page1, page2] = await Promise.all([ @@ -343,11 +340,14 @@ describe("NFT Contract", async () => { ]); expect(page1).to.be.an("array").length(10); expect(page1[0].tokenId).to.eq(0); + expect(page1[4].owner).to.eq(adminWallet); expect(page1[9].tokenId).to.eq(9); expect(page2).to.be.an("array").length(8); expect(page2[0].tokenId).to.eq(5); + expect(page2[0].owner).to.eq(adminWallet); expect(page2[7].tokenId).to.eq(12); + expect(page2[7].owner).to.eq(adminWallet); }); it("getOwned should return all item when queryParams.count is greater than the total supply (erc-721-standard.ts)", async () => { From eaac6d9e4874fbde347e7d65cccb287d46265b48 Mon Sep 17 00:00:00 2001 From: Kien Ngo Date: Wed, 1 Nov 2023 18:20:11 -0400 Subject: [PATCH 07/11] Update --- packages/sdk/test/evm/nft.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/test/evm/nft.test.ts b/packages/sdk/test/evm/nft.test.ts index 143c09f6e19..db64bcf85dc 100644 --- a/packages/sdk/test/evm/nft.test.ts +++ b/packages/sdk/test/evm/nft.test.ts @@ -331,7 +331,7 @@ describe("NFT Contract", async () => { }); it("should respect pagination for getAllOwners", async () => { - const _tokenIds: number[] = Array.from({ length: 31 }, (_, index) => index); // [0, 1, ... 30] + const _tokenIds: number[] = Array.from({ length: 31 }, (_, index) => index); const metadata = _tokenIds.map((num) => ({ name: `Test${num}` })); await nftContract.mintBatch(metadata); const [page1, page2] = await Promise.all([ From 4e016721edc291f75168d3e0d8584a4255921767 Mon Sep 17 00:00:00 2001 From: Kien Ngo Date: Wed, 1 Nov 2023 19:00:02 -0400 Subject: [PATCH 08/11] Update test --- packages/sdk/test/evm/nft.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/sdk/test/evm/nft.test.ts b/packages/sdk/test/evm/nft.test.ts index db64bcf85dc..06fdb3e0d57 100644 --- a/packages/sdk/test/evm/nft.test.ts +++ b/packages/sdk/test/evm/nft.test.ts @@ -340,14 +340,14 @@ describe("NFT Contract", async () => { ]); expect(page1).to.be.an("array").length(10); expect(page1[0].tokenId).to.eq(0); - expect(page1[4].owner).to.eq(adminWallet); + expect(page1[4].owner).to.eq(nftContract.getAddress()); expect(page1[9].tokenId).to.eq(9); expect(page2).to.be.an("array").length(8); expect(page2[0].tokenId).to.eq(5); - expect(page2[0].owner).to.eq(adminWallet); + expect(page2[0].owner).to.eq(nftContract.getAddress()); expect(page2[7].tokenId).to.eq(12); - expect(page2[7].owner).to.eq(adminWallet); + expect(page2[7].owner).to.eq(nftContract.getAddress()); }); it("getOwned should return all item when queryParams.count is greater than the total supply (erc-721-standard.ts)", async () => { From cb3fdc4755664f813f476167d6d8d553ba607ee3 Mon Sep 17 00:00:00 2001 From: Kien Ngo Date: Wed, 1 Nov 2023 19:18:27 -0400 Subject: [PATCH 09/11] Update test --- packages/sdk/test/evm/nft.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/sdk/test/evm/nft.test.ts b/packages/sdk/test/evm/nft.test.ts index 06fdb3e0d57..ae4fdf0821a 100644 --- a/packages/sdk/test/evm/nft.test.ts +++ b/packages/sdk/test/evm/nft.test.ts @@ -340,14 +340,11 @@ describe("NFT Contract", async () => { ]); expect(page1).to.be.an("array").length(10); expect(page1[0].tokenId).to.eq(0); - expect(page1[4].owner).to.eq(nftContract.getAddress()); expect(page1[9].tokenId).to.eq(9); expect(page2).to.be.an("array").length(8); expect(page2[0].tokenId).to.eq(5); - expect(page2[0].owner).to.eq(nftContract.getAddress()); expect(page2[7].tokenId).to.eq(12); - expect(page2[7].owner).to.eq(nftContract.getAddress()); }); it("getOwned should return all item when queryParams.count is greater than the total supply (erc-721-standard.ts)", async () => { From 2cb322eb3cb0e96d8332b990abb1f61301c102fb Mon Sep 17 00:00:00 2001 From: Kien Ngo Date: Tue, 28 Nov 2023 18:16:22 -0500 Subject: [PATCH 10/11] Update --- packages/sdk/src/evm/core/classes/erc-721.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/sdk/src/evm/core/classes/erc-721.ts b/packages/sdk/src/evm/core/classes/erc-721.ts index 9cca418ec3f..325b414d30d 100644 --- a/packages/sdk/src/evm/core/classes/erc-721.ts +++ b/packages/sdk/src/evm/core/classes/erc-721.ts @@ -437,14 +437,9 @@ export class Erc721< walletAddress || this.contractWrapper.getSignerAddress(), this.getAllOwners(queryParams), ]); - let ownedTokens = (allOwners || []).filter( + const ownedTokens = (allOwners || []).filter( (i) => address?.toLowerCase() === i.owner?.toLowerCase(), ); - if (queryParams) { - const start = queryParams?.start || 0; - const count = queryParams?.count || DEFAULT_QUERY_ALL_COUNT; - ownedTokens = ownedTokens.slice(start, start + count); - } return await Promise.all( ownedTokens.map(async (i) => this.get(i.tokenId)), ); From ee121cdcbbc548a6d1f644a07b5d24600fcba778 Mon Sep 17 00:00:00 2001 From: Kien Ngo Date: Tue, 28 Nov 2023 18:20:38 -0500 Subject: [PATCH 11/11] Fix lint --- packages/sdk/src/evm/core/classes/erc-721.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/sdk/src/evm/core/classes/erc-721.ts b/packages/sdk/src/evm/core/classes/erc-721.ts index 34efbc6c354..868cb580558 100644 --- a/packages/sdk/src/evm/core/classes/erc-721.ts +++ b/packages/sdk/src/evm/core/classes/erc-721.ts @@ -17,10 +17,7 @@ import type { } from "@thirdweb-dev/contracts-js"; import type { ThirdwebStorage } from "@thirdweb-dev/storage"; import { BigNumber, BigNumberish, constants } from "ethers"; -import { - DEFAULT_QUERY_ALL_COUNT, - type QueryAllParams, -} from "../../../core/schema/QueryParams"; +import { type QueryAllParams } from "../../../core/schema/QueryParams"; import type { NFT, NFTMetadata,