Skip to content

Commit dce20fe

Browse files
[SDK] Use insight for erc721/getNFTs and erc721/getOwnedNFTs
1 parent e29f749 commit dce20fe

File tree

28 files changed

+1006
-178
lines changed

28 files changed

+1006
-178
lines changed

.changeset/purple-bats-march.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Use insight for erc821/getNFT, erc721/getNFTs and erc721/getOwnedNFTs
6+
7+
Standard ERC721 getNFT, getNFTs and getOwnedNFTs now use insight, our in house indexer by default. If indexer is not availbale, will fallback to RPC.
8+
9+
You can also use the indexer directly using the Insight API:
10+
11+
for an entire collection
12+
13+
```ts
14+
import { Insight } from "thirdweb";
15+
16+
const events = await Insight.getContractNFTs({
17+
client,
18+
chains: [sepolia],
19+
contractAddress: "0x1234567890123456789012345678901234567890",
20+
});
21+
```
22+
23+
or for a single NFT
24+
25+
```ts
26+
import { Insight } from "thirdweb";
27+
28+
const events = await Insight.getNFT({
29+
client,
30+
chains: [sepolia],
31+
contractAddress: "0x1234567890123456789012345678901234567890",
32+
tokenId: 1n,
33+
});
34+
```

apps/dashboard/src/@/actions/getWalletNFTs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ async function getWalletNFTsFromInsight(params: {
194194
tokenURI: nft.metadata_url,
195195
type: nft.token_type === "erc721" ? "ERC721" : "ERC1155",
196196
supply: nft.balance,
197+
tokenAddress: nft.contract.address,
198+
chainId: nft.contract.chain_id,
197199
};
198200

199201
return walletNFT;

packages/thirdweb/src/event/actions/get-events.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type {
55
ExtractAbiEventNames,
66
} from "abitype";
77
import { type Log, formatLog } from "viem";
8-
import { getChainServices } from "../../chains/utils.js";
98
import { resolveContractAbi } from "../../contract/actions/resolve-abi.js";
109
import type { ThirdwebContract } from "../../contract/contract.js";
1110
import { getContractEvents as getContractEventsInsight } from "../../insight/get-events.js";
@@ -197,7 +196,7 @@ export async function getContractEvents<
197196
),
198197
);
199198
} catch (e) {
200-
console.warn("Error fetching from insight", e);
199+
console.warn("Error fetching from insight, falling back to rpc", e);
201200
// fetch from rpc
202201
logs = await Promise.all(
203202
logsParams.map((ethLogParams) => eth_getLogs(rpcRequest, ethLogParams)),
@@ -225,17 +224,6 @@ async function getLogsFromInsight(options: {
225224
}): Promise<Log[]> {
226225
const { params, contract } = options;
227226

228-
const chainServices = await getChainServices(contract.chain);
229-
const insightEnabled = chainServices.some(
230-
(c) => c.service === "insight" && c.enabled,
231-
);
232-
233-
if (!insightEnabled) {
234-
throw new Error(
235-
`Insight is not available for chainId ${contract.chain.id}`,
236-
);
237-
}
238-
239227
const fromBlock =
240228
typeof params.fromBlock === "bigint" ? Number(params.fromBlock) : undefined;
241229

packages/thirdweb/src/extensions/erc1155/read/getNFT.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc1155.getNFT", () => {
1111
});
1212
expect(nft).toMatchInlineSnapshot(`
1313
{
14+
"chainId": 1,
1415
"id": 2n,
1516
"metadata": {
1617
"animation_url": "ipfs://QmYoM63qaumQznBRx38tQjkY4ewbymeFb2KWBhkfMqNHax/3.mp4",
@@ -36,6 +37,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc1155.getNFT", () => {
3637
},
3738
"owner": null,
3839
"supply": 2519n,
40+
"tokenAddress": "0x42d3641255C946CC451474295d29D3505173F22A",
3941
"tokenURI": "ipfs://QmbMXdbnNUAuGRoY6c6G792c6T9utfaBGqRUaMaRUf52Cb/2",
4042
"type": "ERC1155",
4143
}

packages/thirdweb/src/extensions/erc1155/read/getNFT.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ export async function getNFT(
5858
type: "ERC1155",
5959
owner: null,
6060
supply,
61+
tokenAddress: options.contract.address,
62+
chainId: options.contract.chain.id,
6163
},
6264
);
6365
}

packages/thirdweb/src/extensions/erc20/read/getBalance.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export type GetBalanceResult = {
2323
displayValue: string;
2424
symbol: string;
2525
name: string;
26+
tokenAddress: string;
27+
chainId: number;
2628
};
2729

2830
/**
@@ -48,5 +50,7 @@ export async function getBalance(
4850
...currencyMetadata,
4951
value: balanceWei,
5052
displayValue: toTokens(balanceWei, currencyMetadata.decimals),
53+
tokenAddress: options.contract.address,
54+
chainId: options.contract.chain.id,
5155
};
5256
}

packages/thirdweb/src/extensions/erc721/read/getNFT.test.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,117 @@
11
import { describe, expect, it } from "vitest";
22
import { DOODLES_CONTRACT } from "~test/test-contracts.js";
3+
import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
34
import { getNFT } from "./getNFT.js";
45

56
describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFT", () => {
7+
it("without owner using indexer", async () => {
8+
const clientId = TEST_CLIENT.clientId;
9+
const nft = await getNFT({
10+
contract: { ...DOODLES_CONTRACT },
11+
tokenId: 1n,
12+
includeOwner: false,
13+
});
14+
expect(nft).toMatchInlineSnapshot(`
15+
{
16+
"chainId": 1,
17+
"id": 1n,
18+
"metadata": {
19+
"attributes": [
20+
{
21+
"trait_type": "face",
22+
"value": "holographic beard",
23+
},
24+
{
25+
"trait_type": "hair",
26+
"value": "white bucket cap",
27+
},
28+
{
29+
"trait_type": "body",
30+
"value": "purple sweater with satchel",
31+
},
32+
{
33+
"trait_type": "background",
34+
"value": "grey",
35+
},
36+
{
37+
"trait_type": "head",
38+
"value": "gradient 2",
39+
},
40+
],
41+
"description": "A community-driven collectibles project featuring art by Burnt Toast. Doodles come in a joyful range of colors, traits and sizes with a collection size of 10,000. Each Doodle allows its owner to vote for experiences and activations paid for by the Doodles Community Treasury. Burnt Toast is the working alias for Scott Martin, a Canadian–based illustrator, designer, animator and muralist.",
42+
"image": "https://${clientId}.ipfscdn.io/ipfs/QmTDxnzcvj2p3xBrKcGv1wxoyhAn2yzCQnZZ9LmFjReuH9",
43+
"image_url": "https://${clientId}.ipfscdn.io/ipfs/QmTDxnzcvj2p3xBrKcGv1wxoyhAn2yzCQnZZ9LmFjReuH9",
44+
"name": "Doodle #1",
45+
"uri": "https://${clientId}.ipfscdn.io/ipfs/QmPMc4tcBsMqLRuCQtPmPe84bpSjrC3Ky7t3JWuHXYB4aS/1",
46+
},
47+
"owner": null,
48+
"tokenAddress": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e",
49+
"tokenURI": "https://${clientId}.ipfscdn.io/ipfs/QmPMc4tcBsMqLRuCQtPmPe84bpSjrC3Ky7t3JWuHXYB4aS/1",
50+
"type": "ERC721",
51+
}
52+
`);
53+
});
54+
55+
it("with owner using indexer", async () => {
56+
const nft = await getNFT({
57+
contract: { ...DOODLES_CONTRACT },
58+
tokenId: 1n,
59+
includeOwner: true,
60+
});
61+
expect(nft).toMatchInlineSnapshot(`
62+
{
63+
"chainId": 1,
64+
"id": 1n,
65+
"metadata": {
66+
"attributes": [
67+
{
68+
"trait_type": "face",
69+
"value": "holographic beard",
70+
},
71+
{
72+
"trait_type": "hair",
73+
"value": "white bucket cap",
74+
},
75+
{
76+
"trait_type": "body",
77+
"value": "purple sweater with satchel",
78+
},
79+
{
80+
"trait_type": "background",
81+
"value": "grey",
82+
},
83+
{
84+
"trait_type": "head",
85+
"value": "gradient 2",
86+
},
87+
],
88+
"description": "A community-driven collectibles project featuring art by Burnt Toast. Doodles come in a joyful range of colors, traits and sizes with a collection size of 10,000. Each Doodle allows its owner to vote for experiences and activations paid for by the Doodles Community Treasury. Burnt Toast is the working alias for Scott Martin, a Canadian–based illustrator, designer, animator and muralist.",
89+
"image": "https://${clientId}.ipfscdn.io/ipfs/QmTDxnzcvj2p3xBrKcGv1wxoyhAn2yzCQnZZ9LmFjReuH9",
90+
"image_url": "https://${clientId}.ipfscdn.io/ipfs/QmTDxnzcvj2p3xBrKcGv1wxoyhAn2yzCQnZZ9LmFjReuH9",
91+
"name": "Doodle #1",
92+
"owner_addresses": [
93+
"0xbe9936fcfc50666f5425fde4a9decc59cef73b24",
94+
],
95+
"uri": "https://${clientId}.ipfscdn.io/ipfs/QmPMc4tcBsMqLRuCQtPmPe84bpSjrC3Ky7t3JWuHXYB4aS/1",
96+
},
97+
"owner": "0xbe9936fcfc50666f5425fde4a9decc59cef73b24",
98+
"tokenAddress": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e",
99+
"tokenURI": "https://${clientId}.ipfscdn.io/ipfs/QmPMc4tcBsMqLRuCQtPmPe84bpSjrC3Ky7t3JWuHXYB4aS/1",
100+
"type": "ERC721",
101+
}
102+
`);
103+
});
104+
6105
it("without owner", async () => {
7106
const nft = await getNFT({
8107
contract: { ...DOODLES_CONTRACT },
9108
tokenId: 1n,
10109
includeOwner: false,
110+
useIndexer: false,
11111
});
12112
expect(nft).toMatchInlineSnapshot(`
13113
{
114+
"chainId": 1,
14115
"id": 1n,
15116
"metadata": {
16117
"attributes": [
@@ -40,6 +141,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFT", () => {
40141
"name": "Doodle #1",
41142
},
42143
"owner": null,
144+
"tokenAddress": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e",
43145
"tokenURI": "ipfs://QmPMc4tcBsMqLRuCQtPmPe84bpSjrC3Ky7t3JWuHXYB4aS/1",
44146
"type": "ERC721",
45147
}
@@ -51,9 +153,11 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFT", () => {
51153
contract: { ...DOODLES_CONTRACT },
52154
tokenId: 1n,
53155
includeOwner: true,
156+
useIndexer: false,
54157
});
55158
expect(nft).toMatchInlineSnapshot(`
56159
{
160+
"chainId": 1,
57161
"id": 1n,
58162
"metadata": {
59163
"attributes": [
@@ -83,6 +187,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFT", () => {
83187
"name": "Doodle #1",
84188
},
85189
"owner": "0xbE9936FCFC50666f5425FDE4A9decC59cEF73b24",
190+
"tokenAddress": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e",
86191
"tokenURI": "ipfs://QmPMc4tcBsMqLRuCQtPmPe84bpSjrC3Ky7t3JWuHXYB4aS/1",
87192
"type": "ERC721",
88193
}

packages/thirdweb/src/extensions/erc721/read/getNFT.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import {
77
tokenURI,
88
} from "../__generated__/IERC721A/read/tokenURI.js";
99
import { tokenByIndex } from "../__generated__/IERC721Enumerable/read/tokenByIndex.js";
10-
1110
export { isTokenURISupported as isGetNFTSupported } from "../__generated__/IERC721A/read/tokenURI.js";
11+
import { getNFT as getNFTInsight } from "../../../insight/index.js";
1212

1313
/**
1414
* Parameters for getting an NFT.
@@ -27,6 +27,11 @@ export type GetNFTParams = Prettify<
2727
* In this case, the provided tokenId will be considered as token-index and actual tokenId will be fetched from the contract.
2828
*/
2929
tokenByIndex?: boolean;
30+
/**
31+
* Whether to use the insight API to fetch the NFT.
32+
* @default true
33+
*/
34+
useIndexer?: boolean;
3035
}
3136
>;
3237

@@ -58,6 +63,51 @@ export type GetNFTParams = Prettify<
5863
*/
5964
export async function getNFT(
6065
options: BaseTransactionOptions<GetNFTParams>,
66+
): Promise<NFT> {
67+
const { useIndexer = true } = options;
68+
if (useIndexer) {
69+
try {
70+
return getNFTFromInsight(options);
71+
} catch {
72+
return getNFTFromRPC(options);
73+
}
74+
}
75+
return getNFTFromRPC(options);
76+
}
77+
78+
async function getNFTFromInsight(
79+
options: BaseTransactionOptions<GetNFTParams>,
80+
): Promise<NFT> {
81+
const tokenId = options.tokenId;
82+
const nft = await getNFTInsight({
83+
client: options.contract.client,
84+
chain: options.contract.chain,
85+
contractAddress: options.contract.address,
86+
tokenId: options.tokenId,
87+
includeOwners: options.includeOwner,
88+
});
89+
if (!nft) {
90+
return parseNFT(
91+
{
92+
id: tokenId,
93+
type: "ERC721",
94+
uri: "",
95+
},
96+
{
97+
tokenId,
98+
tokenUri: "",
99+
type: "ERC721",
100+
owner: null,
101+
tokenAddress: options.contract.address,
102+
chainId: options.contract.chain.id,
103+
},
104+
);
105+
}
106+
return nft;
107+
}
108+
109+
async function getNFTFromRPC(
110+
options: BaseTransactionOptions<GetNFTParams>,
61111
): Promise<NFT> {
62112
let tokenId = options.tokenId;
63113
if (options.tokenByIndex) {
@@ -90,6 +140,8 @@ export async function getNFT(
90140
tokenUri: "",
91141
type: "ERC721",
92142
owner,
143+
tokenAddress: options.contract.address,
144+
chainId: options.contract.chain.id,
93145
},
94146
);
95147
}
@@ -109,6 +161,8 @@ export async function getNFT(
109161
tokenUri: uri,
110162
type: "ERC721",
111163
owner,
164+
tokenAddress: options.contract.address,
165+
chainId: options.contract.chain.id,
112166
},
113167
);
114168
}

0 commit comments

Comments
 (0)