From aa541d89b9381402548a7047f726305042a109b2 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 26 Feb 2025 05:24:19 +0530 Subject: [PATCH 1/3] Handle token by index --- .../nfts/[tokenId]/token-id.tsx | 7 +++- .../nfts/components/table.tsx | 5 ++- .../src/extensions/erc721/read/getNFT.ts | 38 +++++++++++++++---- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx index d54062e6386..50f309dad27 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx @@ -85,6 +85,7 @@ export const TokenIdPage: React.FC = ({ contract, tokenId: BigInt(tokenId || 0), includeOwner: true, + ignoreTokenIndex: true, }, ); @@ -222,7 +223,11 @@ export const TokenIdPage: React.FC = ({ 8 + ? `${nft.id.toString().slice(0, 4)}...${nft.id.toString().slice(-4)}` + : nft.id?.toString() + } tooltip="Token ID" copyIconPosition="right" /> diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/table.tsx index 3454ab07bf3..b4a9a0a9012 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/table.tsx @@ -59,7 +59,10 @@ export const NFTGetAllTable: React.FC = ({ const cols: Column[] = [ { Header: "Token Id", - accessor: (row) => row.id?.toString(), + accessor: (row) => + row.id?.toString().length > 8 + ? `${row.id.toString().slice(0, 4)}...${row.id.toString().slice(-4)}` + : row.id?.toString(), Cell: (cell: CellProps) =>

{cell.value}

, }, { diff --git a/packages/thirdweb/src/extensions/erc721/read/getNFT.ts b/packages/thirdweb/src/extensions/erc721/read/getNFT.ts index 6d8e1903833..d7f049ad351 100644 --- a/packages/thirdweb/src/extensions/erc721/read/getNFT.ts +++ b/packages/thirdweb/src/extensions/erc721/read/getNFT.ts @@ -1,3 +1,5 @@ +import { type Abi, toFunctionSelector } from "viem"; +import { resolveContractAbi } from "../../../contract/actions/resolve-abi.js"; import type { BaseTransactionOptions } from "../../../transaction/types.js"; import { fetchTokenMetadata } from "../../../utils/nft/fetchTokenMetadata.js"; import { type NFT, parseNFT } from "../../../utils/nft/parseNft.js"; @@ -6,6 +8,10 @@ import { type TokenURIParams, tokenURI, } from "../__generated__/IERC721A/read/tokenURI.js"; +import { + isTokenByIndexSupported, + tokenByIndex, +} from "../__generated__/IERC721Enumerable/read/tokenByIndex.js"; export { isTokenURISupported as isGetNFTSupported } from "../__generated__/IERC721A/read/tokenURI.js"; @@ -19,6 +25,10 @@ export type GetNFTParams = Prettify< * Whether to include the owner of the NFT. */ includeOwner?: boolean; + /** + * Whether to check tokenId by index. + */ + ignoreTokenIndex?: boolean; } >; @@ -39,11 +49,25 @@ export type GetNFTParams = Prettify< export async function getNFT( options: BaseTransactionOptions, ): Promise { + const abi = await resolveContractAbi(options.contract); + const selectors = abi + .filter((f) => f.type === "function") + .map((f) => toFunctionSelector(f)); + const hasTokenByIndex = isTokenByIndexSupported(selectors); + + let tokenId = options.tokenId; + if (!options.ignoreTokenIndex && hasTokenByIndex) { + tokenId = await tokenByIndex({ + contract: options.contract, + index: options.tokenId, + }); + } + const [uri, owner] = await Promise.all([ - tokenURI(options).catch(() => null), + tokenURI({ contract: options.contract, tokenId }).catch(() => null), options.includeOwner ? import("../__generated__/IERC721A/read/ownerOf.js") - .then((m) => m.ownerOf(options)) + .then((m) => m.ownerOf({ contract: options.contract, tokenId })) .catch(() => null) : null, ]); @@ -51,12 +75,12 @@ export async function getNFT( if (!uri?.trim()) { return parseNFT( { - id: options.tokenId, + id: tokenId, type: "ERC721", uri: "", }, { - tokenId: options.tokenId, + tokenId, tokenUri: "", type: "ERC721", owner, @@ -67,15 +91,15 @@ export async function getNFT( return parseNFT( await fetchTokenMetadata({ client: options.contract.client, - tokenId: options.tokenId, + tokenId, tokenUri: uri, }).catch(() => ({ - id: options.tokenId, + id: tokenId, type: "ERC721", uri, })), { - tokenId: options.tokenId, + tokenId: tokenId, tokenUri: uri, type: "ERC721", owner, From f306a43a6bec951357bb441fcf94f7918d9f61b1 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 26 Feb 2025 06:01:06 +0530 Subject: [PATCH 2/3] move detection to dashboard --- .../nfts/ContractNFTPage.tsx | 9 +++- .../nfts/[tokenId]/token-id.tsx | 2 +- .../nfts/components/table.tsx | 3 ++ .../thirdweb/src/exports/extensions/erc721.ts | 1 + .../src/extensions/erc721/read/getNFT.ts | 44 +++++++++++-------- .../src/extensions/erc721/read/getNFTs.ts | 7 +++ 6 files changed, 45 insertions(+), 21 deletions(-) diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/ContractNFTPage.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/ContractNFTPage.tsx index f7bfe9e33af..9ba7ca862d0 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/ContractNFTPage.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/ContractNFTPage.tsx @@ -42,6 +42,9 @@ export const ContractNFTPage: React.FC = ({ const isSetSharedMetadataSupported = ERC721Ext.isSetSharedMetadataSupported(functionSelectors); + const isTokenByIndexSupported = + ERC721Ext.isTokenByIndexSupported(functionSelectors); + const canRenderNFTTable = (() => { if (isErc721) { return ERC721Ext.isGetNFTsSupported(functionSelectors); @@ -101,7 +104,11 @@ export const ContractNFTPage: React.FC = ({ {canShowSupplyCards && } {canRenderNFTTable && ( - + )} ); diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx index 50f309dad27..578a2a69f0c 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx @@ -85,7 +85,7 @@ export const TokenIdPage: React.FC = ({ contract, tokenId: BigInt(tokenId || 0), includeOwner: true, - ignoreTokenIndex: true, + tokenByIndex: false, }, ); diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/table.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/table.tsx index b4a9a0a9012..85d9d4dc7af 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/table.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/table.tsx @@ -45,10 +45,12 @@ import { useReadContract } from "thirdweb/react"; interface ContractOverviewNFTGetAllProps { contract: ThirdwebContract; isErc721: boolean; + tokenByIndex: boolean; } export const NFTGetAllTable: React.FC = ({ contract, isErc721, + tokenByIndex, }) => { // if it's not erc721, it's erc1155 const isErc1155 = !isErc721; @@ -158,6 +160,7 @@ export const NFTGetAllTable: React.FC = ({ start: queryParams.start, count: queryParams.count, includeOwners: true, + tokenByIndex, }, ); diff --git a/packages/thirdweb/src/exports/extensions/erc721.ts b/packages/thirdweb/src/exports/extensions/erc721.ts index eda654f7b45..00efc090ca7 100644 --- a/packages/thirdweb/src/exports/extensions/erc721.ts +++ b/packages/thirdweb/src/exports/extensions/erc721.ts @@ -16,6 +16,7 @@ export { nextTokenIdToMint, isNextTokenIdToMintSupported, } from "../../extensions/erc721/__generated__/IERC721Enumerable/read/nextTokenIdToMint.js"; +export { isTokenByIndexSupported } from "../../extensions/erc721/__generated__/IERC721Enumerable/read/tokenByIndex.js"; export { ownerOf, type OwnerOfParams, diff --git a/packages/thirdweb/src/extensions/erc721/read/getNFT.ts b/packages/thirdweb/src/extensions/erc721/read/getNFT.ts index d7f049ad351..eaa12fe2e55 100644 --- a/packages/thirdweb/src/extensions/erc721/read/getNFT.ts +++ b/packages/thirdweb/src/extensions/erc721/read/getNFT.ts @@ -1,5 +1,3 @@ -import { type Abi, toFunctionSelector } from "viem"; -import { resolveContractAbi } from "../../../contract/actions/resolve-abi.js"; import type { BaseTransactionOptions } from "../../../transaction/types.js"; import { fetchTokenMetadata } from "../../../utils/nft/fetchTokenMetadata.js"; import { type NFT, parseNFT } from "../../../utils/nft/parseNft.js"; @@ -8,10 +6,7 @@ import { type TokenURIParams, tokenURI, } from "../__generated__/IERC721A/read/tokenURI.js"; -import { - isTokenByIndexSupported, - tokenByIndex, -} from "../__generated__/IERC721Enumerable/read/tokenByIndex.js"; +import { tokenByIndex } from "../__generated__/IERC721Enumerable/read/tokenByIndex.js"; export { isTokenURISupported as isGetNFTSupported } from "../__generated__/IERC721A/read/tokenURI.js"; @@ -26,9 +21,12 @@ export type GetNFTParams = Prettify< */ includeOwner?: boolean; /** - * Whether to check tokenId by index. + * Whether to check and fetch tokenID by index, in case of non-sequential IDs. + * + * It should be set to true if it's an ERC721Enumerable contract, and has `tokenByIndex` function. + * In this case, the provided tokenId will be considered as token-index and actual tokenId will be fetched from the contract. */ - ignoreTokenIndex?: boolean; + tokenByIndex?: boolean; } >; @@ -45,22 +43,30 @@ export type GetNFTParams = Prettify< * tokenId: 1n, * }); * ``` + * + * * @example + * ```ts + * import { getNFT } from "thirdweb/extensions/erc721"; + * + * + * const nft = await getNFT({ + * contract, + * tokenId: 1n, + * nonSequential: true // use this flag if the contract supports `tokenByIndex` and the above tokenId should be treated as an index. + * }); + * ``` */ export async function getNFT( options: BaseTransactionOptions, ): Promise { - const abi = await resolveContractAbi(options.contract); - const selectors = abi - .filter((f) => f.type === "function") - .map((f) => toFunctionSelector(f)); - const hasTokenByIndex = isTokenByIndexSupported(selectors); - let tokenId = options.tokenId; - if (!options.ignoreTokenIndex && hasTokenByIndex) { - tokenId = await tokenByIndex({ - contract: options.contract, - index: options.tokenId, - }); + if (options.tokenByIndex) { + try { + tokenId = await tokenByIndex({ + contract: options.contract, + index: options.tokenId, + }); + } catch {} } const [uri, owner] = await Promise.all([ diff --git a/packages/thirdweb/src/extensions/erc721/read/getNFTs.ts b/packages/thirdweb/src/extensions/erc721/read/getNFTs.ts index 11f26d42845..e25f44488d8 100644 --- a/packages/thirdweb/src/extensions/erc721/read/getNFTs.ts +++ b/packages/thirdweb/src/extensions/erc721/read/getNFTs.ts @@ -34,6 +34,13 @@ export type GetNFTsParams = { * @default false */ includeOwners?: boolean; + /** + * Whether to check and fetch tokenID by index, in case of non-sequential IDs. + * + * It should be set to true if it's an ERC721Enumerable contract, and has `tokenByIndex` function. + * In this case, the provided tokenId will be considered as token-index and actual tokenId will be fetched from the contract. + */ + tokenByIndex?: boolean; }; /** From c6c6b1baebfff6f178a0dc8f5b685836ae91fdf2 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 26 Feb 2025 06:04:50 +0530 Subject: [PATCH 3/3] fix comment --- packages/thirdweb/src/extensions/erc721/read/getNFT.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/thirdweb/src/extensions/erc721/read/getNFT.ts b/packages/thirdweb/src/extensions/erc721/read/getNFT.ts index eaa12fe2e55..22bcb7231f7 100644 --- a/packages/thirdweb/src/extensions/erc721/read/getNFT.ts +++ b/packages/thirdweb/src/extensions/erc721/read/getNFT.ts @@ -52,7 +52,7 @@ export type GetNFTParams = Prettify< * const nft = await getNFT({ * contract, * tokenId: 1n, - * nonSequential: true // use this flag if the contract supports `tokenByIndex` and the above tokenId should be treated as an index. + * tokenByIndex: true // use this flag if the contract supports `tokenByIndex` and the above tokenId should be treated as an index. * }); * ``` */