Skip to content

Commit d03cbf0

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

File tree

31 files changed

+1052
-199
lines changed

31 files changed

+1052
-199
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+
```

.github/workflows/CI.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ env:
1616
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
1717
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
1818
TW_SECRET_KEY: ${{ secrets.TW_SECRET_KEY }}
19+
TW_CLIENT_ID: ${{ secrets.TW_CLIENT_ID }}
1920
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
2021

2122
jobs:

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;

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-form.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
205205
owner: nft.owner,
206206
type: "ERC721",
207207
tokenURI: nft.tokenURI,
208+
chainId: nft.chainId,
209+
tokenAddress: nft.tokenAddress,
208210
};
209211
}
210212
return {
@@ -216,6 +218,8 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
216218
owner: nft.owner,
217219
type: "ERC1155",
218220
tokenURI: nft.tokenURI,
221+
chainId: nft.chainId,
222+
tokenAddress: nft.tokenAddress,
219223
};
220224
}) as WalletNFT[];
221225
}, [ownedNFTs, form]);

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)