Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/four-jeans-brake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@thirdweb-dev/sdk": patch
---

Add optional pagination to getOwned NFTs
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,11 @@ export class EditionDrop extends StandardErc1155<PrebuiltEditionDrop> {
*
* @returns The NFT metadata for all NFTs in the contract.
*/
public async getOwned(walletAddress?: AddressOrEns): Promise<NFT[]> {
return this.erc1155.getOwned(walletAddress);
public async getOwned(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
return this.erc1155.getOwned(walletAddress, queryParams);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,11 @@ export class Edition extends StandardErc1155<TokenERC1155> {
*
* @returns The NFT metadata for all NFTs in the contract.
*/
public async getOwned(walletAddress?: AddressOrEns): Promise<NFT[]> {
return this.erc1155.getOwned(walletAddress);
public async getOwned(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
return this.erc1155.getOwned(walletAddress, queryParams);
}

/**
Expand Down
12 changes: 10 additions & 2 deletions packages/sdk/src/evm/core/classes/erc-1155-enumerable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ export class Erc1155Enumerable implements DetectableFeature {
*
* @returns The NFT metadata for all NFTs in the contract.
*/
public async owned(walletAddress?: AddressOrEns): Promise<NFT[]> {
public async owned(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
const [address, maxId] = await Promise.all([
resolveAddress(
walletAddress || (await this.contractWrapper.getSignerAddress()),
Expand All @@ -114,14 +117,19 @@ export class Erc1155Enumerable implements DetectableFeature {
Array.from(Array(maxId.toNumber()).keys()),
]);

const ownedBalances = balances
let ownedBalances = balances
.map((b, i) => {
return {
tokenId: i,
balance: b,
};
})
.filter((b) => b.balance.gt(0));
if (queryParams) {
const start = queryParams?.start || 0;
const count = queryParams?.count || DEFAULT_QUERY_ALL_COUNT;
ownedBalances = ownedBalances.slice(start, start + count);
}
const nfts = (
await Promise.all(
ownedBalances.map((item) => this.erc1155.get(item.tokenId.toString())),
Expand Down
6 changes: 5 additions & 1 deletion packages/sdk/src/evm/core/classes/erc-1155.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,12 +494,16 @@ export class Erc1155<
* @returns The NFT metadata for all NFTs in the contract.
* @twfeature ERC1155Enumerable
*/
public async getOwned(walletAddress?: AddressOrEns): Promise<NFT[]> {
public async getOwned(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
if (walletAddress) {
walletAddress = await resolveAddress(walletAddress);
}
return assertEnabled(this.query, FEATURE_EDITION_ENUMERABLE).owned(
walletAddress,
queryParams,
);
}

Expand Down
17 changes: 15 additions & 2 deletions packages/sdk/src/evm/core/classes/erc-721-enumerable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import type { BaseERC721 } from "../../types/eips";
import { DetectableFeature } from "../interfaces/DetectableFeature";
import type { ContractWrapper } from "./contract-wrapper";
import type { Erc721 } from "./erc-721";
import {
DEFAULT_QUERY_ALL_COUNT,
QueryAllParams,
} from "../../../core/schema/QueryParams";

/**
* List owned ERC721 NFTs
Expand Down Expand Up @@ -46,10 +50,19 @@ export class Erc721Enumerable implements DetectableFeature {
* const nfts = await contract.nft.query.owned.all(address);
* ```
* @param walletAddress - the wallet address to query, defaults to the connected wallet
* @param queryParams - optional filtering to only fetch a subset of results.
* @returns The NFT metadata for all NFTs in the contract.
*/
public async all(walletAddress?: AddressOrEns): Promise<NFT[]> {
const tokenIds = await this.tokenIds(walletAddress);
public async all(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
let tokenIds = await this.tokenIds(walletAddress);
if (queryParams) {
const start = queryParams?.start || 0;
const count = queryParams?.count || DEFAULT_QUERY_ALL_COUNT;
tokenIds = tokenIds.slice(start, start + count);
}
return await Promise.all(
tokenIds.map((tokenId) => this.erc721.get(tokenId.toString())),
);
Expand Down
8 changes: 6 additions & 2 deletions packages/sdk/src/evm/core/classes/erc-721-standard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,17 @@ export class StandardErc721<
* console.log(nfts);
* ```
* @param walletAddress - the wallet address to query, defaults to the connected wallet
* @param queryParams - optional filtering to only fetch a subset of results.
* @returns The NFT metadata for all NFTs in the contract.
*/
public async getOwned(walletAddress?: AddressOrEns): Promise<NFT[]> {
public async getOwned(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
if (walletAddress) {
walletAddress = await resolveAddress(walletAddress);
}
return this.erc721.getOwned(walletAddress);
return this.erc721.getOwned(walletAddress, queryParams);
}

/**
Expand Down
25 changes: 19 additions & 6 deletions packages/sdk/src/evm/core/classes/erc-721.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import type {
} from "@thirdweb-dev/contracts-js";
import type { ThirdwebStorage } from "@thirdweb-dev/storage";
import { BigNumber, BigNumberish, constants } from "ethers";
import type { QueryAllParams } from "../../../core/schema/QueryParams";
import {
DEFAULT_QUERY_ALL_COUNT,
type QueryAllParams,
} from "../../../core/schema/QueryParams";
import type {
NFT,
NFTMetadata,
Expand Down Expand Up @@ -415,25 +418,35 @@ export class Erc721<
* console.log(nfts);
* ```
* @param walletAddress - the wallet address to query, defaults to the connected wallet
* @param queryParams - optional filtering to only fetch a subset of results.
* @returns The NFT metadata for all NFTs in the contract.
* @twfeature ERC721Supply | ERC721Enumerable
*/
public async getOwned(walletAddress?: AddressOrEns) {
public async getOwned(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
) {
if (walletAddress) {
walletAddress = await resolveAddress(walletAddress);
}

if (this.query?.owned) {
return this.query.owned.all(walletAddress);
return this.query.owned.all(walletAddress, queryParams);
} else {
const [address, allOwners] = await Promise.all([
walletAddress || this.contractWrapper.getSignerAddress(),
this.getAllOwners(),
]);
let 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(
(allOwners || [])
.filter((i) => address?.toLowerCase() === i.owner?.toLowerCase())
.map((i) => this.get(i.tokenId)),
ownedTokens.map(async (i) => this.get(i.tokenId)),
);
}
}
Expand Down
16 changes: 14 additions & 2 deletions packages/sdk/src/evm/core/classes/erc-721a-queryable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import type { BaseERC721 } from "../../types/eips";
import { DetectableFeature } from "../interfaces/DetectableFeature";
import type { ContractWrapper } from "./contract-wrapper";
import type { Erc721 } from "./erc-721";
import {
DEFAULT_QUERY_ALL_COUNT,
QueryAllParams,
} from "../../../core/schema/QueryParams";

/**
* List owned ERC721 NFTs
Expand Down Expand Up @@ -50,8 +54,16 @@ export class Erc721AQueryable implements DetectableFeature {
* @param walletAddress - the wallet address to query, defaults to the connected wallet
* @returns The NFT metadata for all NFTs in the contract.
*/
public async all(walletAddress?: AddressOrEns): Promise<NFT[]> {
const tokenIds = await this.tokenIds(walletAddress);
public async all(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
let tokenIds = await this.tokenIds(walletAddress);
if (queryParams) {
const start = queryParams?.start || 0;
const count = queryParams?.count || DEFAULT_QUERY_ALL_COUNT;
tokenIds = tokenIds.slice(start, start + count);
}
return await Promise.all(
tokenIds.map((tokenId) => this.erc721.get(tokenId.toString())),
);
Expand Down
96 changes: 96 additions & 0 deletions packages/sdk/test/evm/edition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,4 +287,100 @@ describe("Edition Contract", async () => {
expect(nfts[0].supply).to.be.equal("5");
expect(nfts[0].metadata.id).to.be.equal("1");
});

it("should respect pagination for getOwned (for edition.ts)", async () => {
const nfts = [] as { metadata: { name: string }; supply: number }[];
for (let i = 0; i < 10; i++) {
nfts.push({
metadata: { name: `Test${i}` },
supply: 10,
});
}
await bundleContract.mintBatch(nfts);
const total = await bundleContract.getTotalCount();
expect(total.toNumber()).to.eq(10);
const nftPage1 = await bundleContract.getOwned(adminWallet.address, {
count: 2,
start: 0,
});
expect(nftPage1).to.be.an("array").length(2);
expect(nftPage1[0].metadata.id).to.eq("0");
expect(nftPage1[1].metadata.id).to.eq("1");

const nftPage2 = await bundleContract.getOwned(adminWallet.address, {
count: 3,
start: 2,
});
expect(nftPage2).to.be.an("array").length(3);
expect(nftPage2[0].metadata.id).to.eq("2");
expect(nftPage2[1].metadata.id).to.eq("3");
expect(nftPage2[2].metadata.id).to.eq("4");
});

it("should respect pagination for getOwned (for erc-1155.ts)", async () => {
const nfts = [] as { metadata: { name: string }; supply: number }[];
for (let i = 0; i < 10; i++) {
nfts.push({
metadata: { name: `Test${i}` },
supply: 10,
});
}
await bundleContract.mintBatch(nfts);
const total = await bundleContract.getTotalCount();
expect(total.toNumber()).to.eq(10);
const nftPage1 = await bundleContract.erc1155.getOwned(
adminWallet.address,
{
count: 2,
start: 0,
},
);
expect(nftPage1).to.be.an("array").length(2);
expect(nftPage1[0].metadata.id).to.eq("0");
expect(nftPage1[1].metadata.id).to.eq("1");

const nftPage2 = await bundleContract.erc1155.getOwned(
adminWallet.address,
{
count: 3,
start: 2,
},
);
expect(nftPage2).to.be.an("array").length(3);
expect(nftPage2[0].metadata.id).to.eq("2");
expect(nftPage2[1].metadata.id).to.eq("3");
expect(nftPage2[2].metadata.id).to.eq("4");
});

it("getOwned should return all items when queryParams.count is greater than the total supply (edition.ts)", async () => {
const nfts = [] as { metadata: { name: string }; supply: number }[];
for (let i = 0; i < 10; i++) {
nfts.push({
metadata: { name: `Test${i}` },
supply: 10,
});
}
await bundleContract.mintBatch(nfts);
const items = await bundleContract.getOwned(undefined, {
count: 1000,
start: 0,
});
expect(items).to.be.an("array").length(nfts.length);
});

it("getOwned should return all items when queryParams.count is greater than the total supply (erc-1155.ts)", async () => {
const nfts = [] as { metadata: { name: string }; supply: number }[];
for (let i = 0; i < 10; i++) {
nfts.push({
metadata: { name: `Test${i}` },
supply: 10,
});
}
await bundleContract.mintBatch(nfts);
const items = await bundleContract.getOwned(undefined, {
count: 1000,
start: 0,
});
expect(items).to.be.an("array").length(nfts.length);
});
});
66 changes: 66 additions & 0 deletions packages/sdk/test/evm/nft.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,4 +285,70 @@ describe("NFT Contract", async () => {
expect(records[0].tokenId).to.eq(1);
expect(records[1].tokenId).to.eq(2);
});

it("should respect pagination for getOwned (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}` }));
await nftContract.mintBatch(metadata);
const nftPage1 = await nftContract.getOwned(undefined, {
count: 2,
start: 0,
});
expect(nftPage1).to.be.an("array").length(2);
expect(nftPage1[0].metadata.id).to.eq("0");
expect(nftPage1[1].metadata.id).to.eq("1");

const nftPage2 = await nftContract.getOwned(undefined, {
count: 3,
start: 2,
});
expect(nftPage2).to.be.an("array").length(3);
expect(nftPage2[0].metadata.id).to.eq("2");
expect(nftPage2[1].metadata.id).to.eq("3");
expect(nftPage2[2].metadata.id).to.eq("4");
});

it("should respect pagination for getOwned (erc-721.ts)", async () => {
const _tokenIds: number[] = Array.from({ length: 11 }, (_, index) => index); // [0, 1, ... 10]
const metadata = _tokenIds.map((num) => ({ name: `Test${num}` }));
await nftContract.mintBatch(metadata);
const nftPage1 = await nftContract.erc721.getOwned(undefined, {
count: 2,
start: 0,
});
expect(nftPage1).to.be.an("array").length(2);
expect(nftPage1[0].metadata.id).to.eq("0");
expect(nftPage1[1].metadata.id).to.eq("1");

const nftPage2 = await nftContract.erc721.getOwned(undefined, {
count: 3,
start: 2,
});
expect(nftPage2).to.be.an("array").length(3);
expect(nftPage2[0].metadata.id).to.eq("2");
expect(nftPage2[1].metadata.id).to.eq("3");
expect(nftPage2[2].metadata.id).to.eq("4");
});

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}` }));
await nftContract.mintBatch(metadata);
const nfts = await nftContract.getOwned(undefined, {
count: 1000,
start: 0,
});
expect(nfts).to.be.an("array").length(_tokenIds.length);
});

it("getOwned should return all items when queryParams.count is greater than the total supply (erc-721.ts)", async () => {
const _tokenIds: number[] = Array.from({ length: 11 }, (_, index) => index); // [0, 1, ... 10]
const metadata = _tokenIds.map((num) => ({ name: `Test${num}` }));
await nftContract.mintBatch(metadata);
const nfts = await nftContract.erc721.getOwned(undefined, {
count: 1000,
start: 0,
});
expect(nfts).to.be.an("array").length(_tokenIds.length);
});
});