[SDK] Add ERC1155 getOwnedTokenIds (#4345)
## Problem solved
Let's say I need a list of owned tokenids for rendering a list of <NFT.Media />. Using getOwnedNFTs is a waste of requests, so I split the code from the current getOwnedNFTs to make a new function
<!-- start pr-codex -->
---
## PR-Codex overview
This PR adds a new function `getOwnedTokenIds` to retrieve ERC1155 tokenIds and their balances for a wallet address, along with related tests.
### Detailed summary
- Added `getOwnedTokenIds` function to retrieve ERC1155 tokenIds and balances
- Updated `getOwnedNFTs` to use `getOwnedTokenIds`
- Added tests for `getOwnedTokenIds`
> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`
<!-- end pr-codex -->
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"thirdweb": patch
|
||||
---
|
||||
|
||||
Add ERC1155 getOwnedTokenIds + more tests
|
||||
@@ -21,6 +21,10 @@ export {
|
||||
nextTokenIdToMint,
|
||||
isNextTokenIdToMintSupported,
|
||||
} from "../../extensions/erc1155/__generated__/IERC1155Enumerable/read/nextTokenIdToMint.js";
|
||||
export {
|
||||
getOwnedTokenIds,
|
||||
type GetOwnedTokenIdsParams,
|
||||
} from "../../extensions/erc1155/read/getOwnedTokenIds.js";
|
||||
export {
|
||||
uri,
|
||||
/**
|
||||
|
||||
@@ -1,31 +1,16 @@
|
||||
import type { Address } from "abitype";
|
||||
import type { BaseTransactionOptions } from "../../../transaction/types.js";
|
||||
import type { NFT } from "../../../utils/nft/parseNft.js";
|
||||
import { balanceOfBatch } from "../__generated__/IERC1155/read/balanceOfBatch.js";
|
||||
import { nextTokenIdToMint } from "../__generated__/IERC1155Enumerable/read/nextTokenIdToMint.js";
|
||||
import { nextTokenId } from "../__generated__/Zora1155/read/nextTokenId.js";
|
||||
import { getNFT } from "./getNFT.js";
|
||||
|
||||
const DEFAULT_QUERY_ALL_COUNT = 100;
|
||||
import {
|
||||
type GetOwnedTokenIdsParams,
|
||||
getOwnedTokenIds,
|
||||
} from "./getOwnedTokenIds.js";
|
||||
|
||||
/**
|
||||
* Parameters for retrieving NFTs.
|
||||
* @extension ERC1155
|
||||
*/
|
||||
export type GetOwnedNFTsParams = {
|
||||
/**
|
||||
* Which tokenId to start at.
|
||||
*/
|
||||
start?: number;
|
||||
/**
|
||||
* The number of NFTs to retrieve.
|
||||
*/
|
||||
count?: number;
|
||||
/**
|
||||
* The address of the wallet to get the NFTs of.
|
||||
*/
|
||||
address: string;
|
||||
};
|
||||
export type GetOwnedNFTsParams = GetOwnedTokenIdsParams;
|
||||
|
||||
/**
|
||||
* Retrieves the owned ERC1155 NFTs for a given wallet address.
|
||||
@@ -46,52 +31,10 @@ export type GetOwnedNFTsParams = {
|
||||
export async function getOwnedNFTs(
|
||||
options: BaseTransactionOptions<GetOwnedNFTsParams>,
|
||||
): Promise<(NFT & { quantityOwned: bigint })[]> {
|
||||
const maxId = await Promise.allSettled([
|
||||
nextTokenIdToMint(options),
|
||||
nextTokenId(options),
|
||||
]).then(([_nextToMint, _next]) => {
|
||||
if (_nextToMint.status === "fulfilled") {
|
||||
return _nextToMint.value;
|
||||
}
|
||||
if (_next.status === "fulfilled") {
|
||||
return _next.value;
|
||||
}
|
||||
throw Error("Contract doesn't have required extension");
|
||||
});
|
||||
|
||||
// approach is naieve, likely can be improved
|
||||
const owners: Address[] = [];
|
||||
const tokenIds: bigint[] = [];
|
||||
for (let i = 0n; i < maxId; i++) {
|
||||
owners.push(options.address);
|
||||
tokenIds.push(i);
|
||||
}
|
||||
|
||||
const balances = await balanceOfBatch({
|
||||
...options,
|
||||
owners,
|
||||
tokenIds,
|
||||
});
|
||||
|
||||
let ownedBalances = balances
|
||||
.map((b, i) => {
|
||||
return {
|
||||
tokenId: i,
|
||||
balance: b,
|
||||
};
|
||||
})
|
||||
.filter((b) => b.balance > 0);
|
||||
|
||||
if (options.start || options.count) {
|
||||
const start = options?.start || 0;
|
||||
const count = options?.count || DEFAULT_QUERY_ALL_COUNT;
|
||||
ownedBalances = ownedBalances.slice(start, start + count);
|
||||
}
|
||||
const ownedBalances = await getOwnedTokenIds(options);
|
||||
|
||||
const nfts = await Promise.all(
|
||||
ownedBalances.map((ob) =>
|
||||
getNFT({ ...options, tokenId: BigInt(ob.tokenId) }),
|
||||
),
|
||||
ownedBalances.map((ob) => getNFT({ ...options, tokenId: ob.tokenId })),
|
||||
);
|
||||
|
||||
return nfts.map((nft, index) => ({
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "~test/chains.js";
|
||||
import { TEST_CONTRACT_URI } from "~test/ipfs-uris.js";
|
||||
import { TEST_CLIENT } from "~test/test-clients.js";
|
||||
import { TEST_ACCOUNT_A } from "~test/test-wallets.js";
|
||||
import { getContract } from "../../../contract/contract.js";
|
||||
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { deployERC1155Contract } from "../../prebuilts/deploy-erc1155.js";
|
||||
import { mintTo } from "../write/mintTo.js";
|
||||
import { getOwnedTokenIds } from "./getOwnedTokenIds.js";
|
||||
|
||||
const account = TEST_ACCOUNT_A;
|
||||
const client = TEST_CLIENT;
|
||||
const chain = ANVIL_CHAIN;
|
||||
|
||||
describe.runIf(process.env.TW_SECRET_KEY)("ERC1155 getOwnedTokenIds", () => {
|
||||
it("should fetch owned tokenIds", async () => {
|
||||
const address = await deployERC1155Contract({
|
||||
type: "TokenERC1155",
|
||||
chain,
|
||||
client,
|
||||
account,
|
||||
params: {
|
||||
name: "edition",
|
||||
contractURI: TEST_CONTRACT_URI,
|
||||
},
|
||||
});
|
||||
|
||||
const contract = getContract({
|
||||
address,
|
||||
chain,
|
||||
client,
|
||||
});
|
||||
|
||||
const transaction = mintTo({
|
||||
contract,
|
||||
nft: { name: "token 0" },
|
||||
to: account.address,
|
||||
supply: 20n,
|
||||
});
|
||||
await sendAndConfirmTransaction({ transaction, account });
|
||||
|
||||
const ownedTokenIds = await getOwnedTokenIds({
|
||||
contract,
|
||||
address: account.address,
|
||||
});
|
||||
|
||||
expect(ownedTokenIds).toStrictEqual([{ tokenId: 0n, balance: 20n }]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import type { Address } from "abitype";
|
||||
import type { BaseTransactionOptions } from "../../../transaction/types.js";
|
||||
import { balanceOfBatch } from "../__generated__/IERC1155/read/balanceOfBatch.js";
|
||||
import { nextTokenIdToMint } from "../__generated__/IERC1155Enumerable/read/nextTokenIdToMint.js";
|
||||
import { nextTokenId } from "../__generated__/Zora1155/read/nextTokenId.js";
|
||||
|
||||
const DEFAULT_QUERY_ALL_COUNT = 100;
|
||||
|
||||
/**
|
||||
* Parameters for retrieving owned tokenIds of an ERC1155 contract.
|
||||
* @extension ERC1155
|
||||
*/
|
||||
export type GetOwnedTokenIdsParams = {
|
||||
/**
|
||||
* Which tokenId to start at.
|
||||
*/
|
||||
start?: number;
|
||||
/**
|
||||
* The number of NFTs to retrieve.
|
||||
*/
|
||||
count?: number;
|
||||
/**
|
||||
* The address of the wallet to get the NFTs of.
|
||||
*/
|
||||
address: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the owned ERC1155 tokenIds & the owned balance of each tokenId for a given wallet address.
|
||||
* @param options - The transaction options and parameters.
|
||||
* @returns A promise that resolves to an array of ERC1155 NFTs owned by the wallet address, along with the quantity owned.
|
||||
* @extension ERC1155
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getOwnedNFTs } from "thirdweb/extensions/erc1155";
|
||||
* const ownedTokenIds = await getOwnedTokenIds({
|
||||
* contract,
|
||||
* start: 0,
|
||||
* count: 10,
|
||||
* address: "0x123...",
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function getOwnedTokenIds(
|
||||
options: BaseTransactionOptions<GetOwnedTokenIdsParams>,
|
||||
): Promise<{ tokenId: bigint; balance: bigint }[]> {
|
||||
const maxId = await Promise.allSettled([
|
||||
nextTokenIdToMint(options),
|
||||
nextTokenId(options),
|
||||
]).then(([_nextToMint, _next]) => {
|
||||
if (_nextToMint.status === "fulfilled") {
|
||||
return _nextToMint.value;
|
||||
}
|
||||
if (_next.status === "fulfilled") {
|
||||
return _next.value;
|
||||
}
|
||||
throw Error("Contract doesn't have required extension");
|
||||
});
|
||||
|
||||
// approach is naieve, likely can be improved
|
||||
const owners: Address[] = [];
|
||||
const tokenIds: bigint[] = [];
|
||||
for (let i = 0n; i < maxId; i++) {
|
||||
owners.push(options.address);
|
||||
tokenIds.push(i);
|
||||
}
|
||||
|
||||
const balances = await balanceOfBatch({
|
||||
...options,
|
||||
owners,
|
||||
tokenIds,
|
||||
});
|
||||
|
||||
let ownedBalances = balances
|
||||
.map((b, i) => {
|
||||
return {
|
||||
tokenId: BigInt(i),
|
||||
balance: b,
|
||||
};
|
||||
})
|
||||
.filter((b) => b.balance > 0);
|
||||
|
||||
if (options.start || options.count) {
|
||||
const start = options?.start || 0;
|
||||
const count = options?.count || DEFAULT_QUERY_ALL_COUNT;
|
||||
ownedBalances = ownedBalances.slice(start, start + count);
|
||||
}
|
||||
|
||||
return ownedBalances;
|
||||
}
|
||||
Reference in New Issue
Block a user