thirdweb 5.89.0 → 5.90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/dist/cjs/adapters/ethers5.js +2 -0
  2. package/dist/cjs/adapters/ethers5.js.map +1 -1
  3. package/dist/cjs/chains/constants.js +17 -2
  4. package/dist/cjs/chains/constants.js.map +1 -1
  5. package/dist/cjs/contract/actions/resolve-abi.js +9 -11
  6. package/dist/cjs/contract/actions/resolve-abi.js.map +1 -1
  7. package/dist/cjs/exports/extensions/erc721.js +4 -2
  8. package/dist/cjs/exports/extensions/erc721.js.map +1 -1
  9. package/dist/cjs/exports/wallets/smart.js +2 -1
  10. package/dist/cjs/exports/wallets/smart.js.map +1 -1
  11. package/dist/cjs/extensions/erc721/read/getNFT.js +30 -7
  12. package/dist/cjs/extensions/erc721/read/getNFT.js.map +1 -1
  13. package/dist/cjs/extensions/erc721/read/getNFTs.js.map +1 -1
  14. package/dist/cjs/gas/fee-data.js +22 -15
  15. package/dist/cjs/gas/fee-data.js.map +1 -1
  16. package/dist/cjs/storage/download.js +16 -0
  17. package/dist/cjs/storage/download.js.map +1 -1
  18. package/dist/cjs/storage/mock.js +46 -0
  19. package/dist/cjs/storage/mock.js.map +1 -0
  20. package/dist/cjs/storage/upload/web-node.js +6 -1
  21. package/dist/cjs/storage/upload/web-node.js.map +1 -1
  22. package/dist/cjs/transaction/actions/estimate-gas-cost.js +1 -1
  23. package/dist/cjs/transaction/actions/estimate-gas-cost.js.map +1 -1
  24. package/dist/cjs/transaction/actions/to-serializable-transaction.js +3 -1
  25. package/dist/cjs/transaction/actions/to-serializable-transaction.js.map +1 -1
  26. package/dist/cjs/transaction/prepare-transaction.js +8 -0
  27. package/dist/cjs/transaction/prepare-transaction.js.map +1 -1
  28. package/dist/cjs/utils/process.js +4 -1
  29. package/dist/cjs/utils/process.js.map +1 -1
  30. package/dist/cjs/version.js +1 -1
  31. package/dist/cjs/wallets/manager/index.js +1 -1
  32. package/dist/cjs/wallets/manager/index.js.map +1 -1
  33. package/dist/cjs/wallets/smart/index.js +1 -0
  34. package/dist/cjs/wallets/smart/index.js.map +1 -1
  35. package/dist/cjs/wallets/smart/lib/bundler.js +75 -0
  36. package/dist/cjs/wallets/smart/lib/bundler.js.map +1 -1
  37. package/dist/cjs/wallets/smart/lib/calls.js +13 -7
  38. package/dist/cjs/wallets/smart/lib/calls.js.map +1 -1
  39. package/dist/cjs/wallets/smart/lib/userop.js +42 -17
  40. package/dist/cjs/wallets/smart/lib/userop.js.map +1 -1
  41. package/dist/esm/adapters/ethers5.js +2 -0
  42. package/dist/esm/adapters/ethers5.js.map +1 -1
  43. package/dist/esm/chains/constants.js +17 -2
  44. package/dist/esm/chains/constants.js.map +1 -1
  45. package/dist/esm/contract/actions/resolve-abi.js +9 -11
  46. package/dist/esm/contract/actions/resolve-abi.js.map +1 -1
  47. package/dist/esm/exports/extensions/erc721.js +1 -0
  48. package/dist/esm/exports/extensions/erc721.js.map +1 -1
  49. package/dist/esm/exports/wallets/smart.js +1 -1
  50. package/dist/esm/exports/wallets/smart.js.map +1 -1
  51. package/dist/esm/extensions/erc721/read/getNFT.js +30 -7
  52. package/dist/esm/extensions/erc721/read/getNFT.js.map +1 -1
  53. package/dist/esm/extensions/erc721/read/getNFTs.js.map +1 -1
  54. package/dist/esm/gas/fee-data.js +22 -15
  55. package/dist/esm/gas/fee-data.js.map +1 -1
  56. package/dist/esm/storage/download.js +16 -0
  57. package/dist/esm/storage/download.js.map +1 -1
  58. package/dist/esm/storage/mock.js +42 -0
  59. package/dist/esm/storage/mock.js.map +1 -0
  60. package/dist/esm/storage/upload/web-node.js +6 -1
  61. package/dist/esm/storage/upload/web-node.js.map +1 -1
  62. package/dist/esm/transaction/actions/estimate-gas-cost.js +1 -1
  63. package/dist/esm/transaction/actions/estimate-gas-cost.js.map +1 -1
  64. package/dist/esm/transaction/actions/to-serializable-transaction.js +3 -1
  65. package/dist/esm/transaction/actions/to-serializable-transaction.js.map +1 -1
  66. package/dist/esm/transaction/prepare-transaction.js +7 -0
  67. package/dist/esm/transaction/prepare-transaction.js.map +1 -1
  68. package/dist/esm/utils/process.js +3 -0
  69. package/dist/esm/utils/process.js.map +1 -1
  70. package/dist/esm/version.js +1 -1
  71. package/dist/esm/wallets/manager/index.js +1 -1
  72. package/dist/esm/wallets/manager/index.js.map +1 -1
  73. package/dist/esm/wallets/smart/index.js +1 -1
  74. package/dist/esm/wallets/smart/index.js.map +1 -1
  75. package/dist/esm/wallets/smart/lib/bundler.js +74 -0
  76. package/dist/esm/wallets/smart/lib/bundler.js.map +1 -1
  77. package/dist/esm/wallets/smart/lib/calls.js +13 -7
  78. package/dist/esm/wallets/smart/lib/calls.js.map +1 -1
  79. package/dist/esm/wallets/smart/lib/userop.js +41 -17
  80. package/dist/esm/wallets/smart/lib/userop.js.map +1 -1
  81. package/dist/types/adapters/ethers5.d.ts.map +1 -1
  82. package/dist/types/chains/constants.d.ts +1 -1
  83. package/dist/types/chains/constants.d.ts.map +1 -1
  84. package/dist/types/chains/types.d.ts +2 -0
  85. package/dist/types/chains/types.d.ts.map +1 -1
  86. package/dist/types/contract/actions/resolve-abi.d.ts.map +1 -1
  87. package/dist/types/exports/extensions/erc721.d.ts +1 -0
  88. package/dist/types/exports/extensions/erc721.d.ts.map +1 -1
  89. package/dist/types/exports/wallets/smart.d.ts +1 -1
  90. package/dist/types/exports/wallets/smart.d.ts.map +1 -1
  91. package/dist/types/extensions/erc721/read/getNFT.d.ts +19 -0
  92. package/dist/types/extensions/erc721/read/getNFT.d.ts.map +1 -1
  93. package/dist/types/extensions/erc721/read/getNFTs.d.ts +7 -0
  94. package/dist/types/extensions/erc721/read/getNFTs.d.ts.map +1 -1
  95. package/dist/types/gas/fee-data.d.ts +6 -5
  96. package/dist/types/gas/fee-data.d.ts.map +1 -1
  97. package/dist/types/storage/download.d.ts.map +1 -1
  98. package/dist/types/storage/mock.d.ts +11 -0
  99. package/dist/types/storage/mock.d.ts.map +1 -0
  100. package/dist/types/storage/upload/web-node.d.ts.map +1 -1
  101. package/dist/types/transaction/actions/to-serializable-transaction.d.ts +2 -0
  102. package/dist/types/transaction/actions/to-serializable-transaction.d.ts.map +1 -1
  103. package/dist/types/transaction/prepare-transaction.d.ts +3 -0
  104. package/dist/types/transaction/prepare-transaction.d.ts.map +1 -1
  105. package/dist/types/utils/process.d.ts +1 -0
  106. package/dist/types/utils/process.d.ts.map +1 -1
  107. package/dist/types/version.d.ts +1 -1
  108. package/dist/types/wallets/manager/index.d.ts +1 -1
  109. package/dist/types/wallets/manager/index.d.ts.map +1 -1
  110. package/dist/types/wallets/smart/index.d.ts +2 -0
  111. package/dist/types/wallets/smart/index.d.ts.map +1 -1
  112. package/dist/types/wallets/smart/lib/bundler.d.ts +30 -1
  113. package/dist/types/wallets/smart/lib/bundler.d.ts.map +1 -1
  114. package/dist/types/wallets/smart/lib/calls.d.ts.map +1 -1
  115. package/dist/types/wallets/smart/lib/userop.d.ts +10 -0
  116. package/dist/types/wallets/smart/lib/userop.d.ts.map +1 -1
  117. package/package.json +33 -33
  118. package/src/adapters/ethers5.ts +5 -1
  119. package/src/adapters/viem-legacy.test.ts +1 -1
  120. package/src/chains/constants.ts +17 -2
  121. package/src/chains/types.ts +3 -0
  122. package/src/contract/actions/resolve-abi.test.ts +3 -3
  123. package/src/contract/actions/resolve-abi.ts +27 -25
  124. package/src/exports/extensions/erc721.ts +1 -0
  125. package/src/exports/wallets/smart.ts +1 -0
  126. package/src/extensions/erc1155/customDrop1155.test.ts +3 -14
  127. package/src/extensions/erc1155/drop1155.test.ts +3 -14
  128. package/src/extensions/erc1155/token1155.test.ts +16 -109
  129. package/src/extensions/erc20/read/getCurrencyMetadata.test.ts +1 -1
  130. package/src/extensions/erc721/customDrop721.test.ts +3 -13
  131. package/src/extensions/erc721/drop721.test.ts +3 -13
  132. package/src/extensions/erc721/read/getNFT.ts +37 -7
  133. package/src/extensions/erc721/read/getNFTs.test.ts +1 -1
  134. package/src/extensions/erc721/read/getNFTs.ts +7 -0
  135. package/src/extensions/erc721/read/getOwnedTokenIds.test.ts +1 -1
  136. package/src/extensions/lens/read/resolveAddress.test.ts +1 -1
  137. package/src/extensions/prebuilts/deploy-vote.test.ts +1 -1
  138. package/src/extensions/unstoppable-domains/read/resolveAddress.test.ts +1 -1
  139. package/src/extensions/unstoppable-domains/read/resolveName.test.ts +1 -1
  140. package/src/gas/fee-data.ts +32 -19
  141. package/src/pay/convert/cryptoToFiat.test.ts +4 -4
  142. package/src/pay/convert/fiatToCrypto.test.ts +4 -4
  143. package/src/react/core/hooks/wallets/useConnect.test.tsx +3 -3
  144. package/src/react/web/ui/prebuilt/Account/balance.test.tsx +7 -13
  145. package/src/react/web/ui/prebuilt/Chain/icon.test.tsx +1 -1
  146. package/src/react/web/ui/prebuilt/NFT/description.test.tsx +1 -1
  147. package/src/react/web/ui/prebuilt/NFT/media.test.tsx +1 -1
  148. package/src/react/web/ui/prebuilt/NFT/name.test.tsx +1 -1
  149. package/src/react/web/ui/prebuilt/NFT/utils.test.ts +1 -1
  150. package/src/react/web/ui/prebuilt/Token/name.test.tsx +1 -1
  151. package/src/react/web/ui/prebuilt/Token/symbol.test.tsx +1 -1
  152. package/src/react/web/ui/prebuilt/Wallet/icon.test.tsx +1 -1
  153. package/src/react/web/ui/prebuilt/Wallet/name.test.tsx +1 -1
  154. package/src/react/web/ui/prebuilt/thirdweb/ClaimButton/ClaimButton.test.tsx +1 -1
  155. package/src/storage/download.test.ts +1 -1
  156. package/src/storage/download.ts +17 -0
  157. package/src/storage/mock.ts +52 -0
  158. package/src/storage/upload/web-node.test.ts +2 -1
  159. package/src/storage/upload/web-node.ts +8 -1
  160. package/src/transaction/actions/encode.test.ts +1 -1
  161. package/src/transaction/actions/estimate-gas-cost.ts +1 -1
  162. package/src/transaction/actions/to-serializable-transaction.ts +46 -35
  163. package/src/transaction/prepare-transaction.ts +11 -0
  164. package/src/utils/nft/fetch-token-metadata.test.ts +1 -1
  165. package/src/utils/process.ts +4 -0
  166. package/src/version.ts +1 -1
  167. package/src/wallets/manager/index.ts +6 -2
  168. package/src/wallets/smart/index.ts +1 -1
  169. package/src/wallets/smart/lib/bundler.ts +95 -0
  170. package/src/wallets/smart/lib/calls.ts +18 -9
  171. package/src/wallets/smart/lib/userop.ts +53 -18
  172. package/src/wallets/smart/smart-wallet-integration-v07.test.ts +23 -16
  173. package/src/wallets/smart/smart-wallet-integration.test.ts +21 -1
  174. package/src/wallets/smart/smart-wallet-modular.test.ts +1 -1
@@ -1,10 +1,9 @@
1
1
  import { type Abi, formatAbi, parseAbi } from "abitype";
2
2
  import { download } from "../../storage/download.js";
3
3
  import { getClientFetch } from "../../utils/fetch.js";
4
+ import { withCache } from "../../utils/promise/withCache.js";
4
5
  import { type ThirdwebContract, getContract } from "../contract.js";
5
6
 
6
- const ABI_RESOLUTION_CACHE = new WeakMap<ThirdwebContract<Abi>, Promise<Abi>>();
7
-
8
7
  /**
9
8
  * Resolves the ABI (Application Binary Interface) for a given contract.
10
9
  * If the ABI is already cached, it returns the cached value.
@@ -32,31 +31,34 @@ export function resolveContractAbi<abi extends Abi>(
32
31
  contract: ThirdwebContract<abi>,
33
32
  contractApiBaseUrl = "https://contract.thirdweb.com/abi",
34
33
  ): Promise<abi> {
35
- if (ABI_RESOLUTION_CACHE.has(contract)) {
36
- return ABI_RESOLUTION_CACHE.get(contract) as Promise<abi>;
37
- }
38
-
39
- const prom = (async () => {
40
- // if the contract already HAS a user defined we always use that!
41
- if (contract.abi) {
42
- return contract.abi as abi;
43
- }
34
+ return withCache(
35
+ async () => {
36
+ // if the contract already HAS a user defined we always use that!
37
+ if (contract.abi) {
38
+ return contract.abi as abi;
39
+ }
44
40
 
45
- // for local chains, we need to resolve the composite abi from bytecode
46
- if (contract.chain.id === 31337 || contract.chain.id === 1337) {
47
- return await resolveCompositeAbi(contract as ThirdwebContract);
48
- }
41
+ // for local chains, we need to resolve the composite abi from bytecode
42
+ if (contract.chain.id === 31337 || contract.chain.id === 1337) {
43
+ return (await resolveCompositeAbi(contract as ThirdwebContract)) as abi;
44
+ }
49
45
 
50
- // try to get it from the api
51
- try {
52
- return await resolveAbiFromContractApi(contract, contractApiBaseUrl);
53
- } catch {
54
- // if that fails, try to resolve it from the bytecode
55
- return await resolveCompositeAbi(contract as ThirdwebContract);
56
- }
57
- })();
58
- ABI_RESOLUTION_CACHE.set(contract, prom);
59
- return prom as Promise<abi>;
46
+ // try to get it from the api
47
+ try {
48
+ return (await resolveAbiFromContractApi(
49
+ contract,
50
+ contractApiBaseUrl,
51
+ )) as abi;
52
+ } catch {
53
+ // if that fails, try to resolve it from the bytecode
54
+ return (await resolveCompositeAbi(contract as ThirdwebContract)) as abi;
55
+ }
56
+ },
57
+ {
58
+ cacheKey: `${contract.chain.id}-${contract.address}`,
59
+ cacheTime: 1000 * 60 * 60 * 1, // 1 hour
60
+ },
61
+ );
60
62
  }
61
63
 
62
64
  /**
@@ -16,6 +16,7 @@ export {
16
16
  nextTokenIdToMint,
17
17
  isNextTokenIdToMintSupported,
18
18
  } from "../../extensions/erc721/__generated__/IERC721Enumerable/read/nextTokenIdToMint.js";
19
+ export { isTokenByIndexSupported } from "../../extensions/erc721/__generated__/IERC721Enumerable/read/tokenByIndex.js";
19
20
  export {
20
21
  ownerOf,
21
22
  type OwnerOfParams,
@@ -13,6 +13,7 @@ export {
13
13
  bundleUserOp,
14
14
  getUserOpGasFees,
15
15
  estimateUserOpGas,
16
+ estimateUserOpGasCost,
16
17
  } from "../../wallets/smart/lib/bundler.js";
17
18
 
18
19
  export {
@@ -69,20 +69,9 @@ describe.runIf(process.env.TW_SECRET_KEY)(
69
69
  });
70
70
 
71
71
  await expect(nextTokenIdToMint({ contract })).resolves.toBe(6n);
72
- await expect(
73
- getNFT({ contract, tokenId: 0n }),
74
- ).resolves.toMatchInlineSnapshot(`
75
- {
76
- "id": 0n,
77
- "metadata": {
78
- "name": "Test NFT",
79
- },
80
- "owner": null,
81
- "supply": 0n,
82
- "tokenURI": "ipfs://QmTo68Dm1ntSp2BHLmE9gesS6ELuXosRz5mAgFCK6tfsRk/0",
83
- "type": "ERC1155",
84
- }
85
- `);
72
+ expect((await getNFT({ contract, tokenId: 0n })).metadata.name).toBe(
73
+ "Test NFT",
74
+ );
86
75
  });
87
76
 
88
77
  it("should allow to claim tokens", async () => {
@@ -89,20 +89,9 @@ describe.runIf(process.env.TW_SECRET_KEY)(
89
89
  });
90
90
 
91
91
  await expect(nextTokenIdToMint({ contract })).resolves.toBe(6n);
92
- await expect(
93
- getNFT({ contract, tokenId: 0n }),
94
- ).resolves.toMatchInlineSnapshot(`
95
- {
96
- "id": 0n,
97
- "metadata": {
98
- "name": "Test NFT",
99
- },
100
- "owner": null,
101
- "supply": 0n,
102
- "tokenURI": "ipfs://QmTo68Dm1ntSp2BHLmE9gesS6ELuXosRz5mAgFCK6tfsRk/0",
103
- "type": "ERC1155",
104
- }
105
- `);
92
+ expect((await getNFT({ contract, tokenId: 0n })).metadata.name).toBe(
93
+ "Test NFT",
94
+ );
106
95
  });
107
96
 
108
97
  it("should update metadata", async () => {
@@ -81,20 +81,9 @@ describe.runIf(process.env.TW_SECRET_KEY)("TokenERC1155", () => {
81
81
  // now 1 token minted
82
82
  await expect(nextTokenIdToMint({ contract })).resolves.toBe(1n);
83
83
  // tokenId 0 is minted
84
- await expect(
85
- getNFT({ contract, tokenId: 0n }),
86
- ).resolves.toMatchInlineSnapshot(`
87
- {
88
- "id": 0n,
89
- "metadata": {
90
- "name": "Test NFT",
91
- },
92
- "owner": null,
93
- "supply": 10n,
94
- "tokenURI": "ipfs://QmUut8sypH8NaPUeksnirut7MgggMeQa9RvJ37sV513sw3/0",
95
- "type": "ERC1155",
96
- }
97
- `);
84
+ expect((await getNFT({ contract, tokenId: 0n })).metadata.name).toBe(
85
+ "Test NFT",
86
+ );
98
87
  // account should have a balance of 10
99
88
  await expect(
100
89
  balanceOf({ contract, owner: TEST_ACCOUNT_A.address, tokenId: 0n }),
@@ -119,20 +108,8 @@ describe.runIf(process.env.TW_SECRET_KEY)("TokenERC1155", () => {
119
108
  await expect(nextTokenIdToMint({ contract })).resolves.toBe(1n);
120
109
  // tokenId 0 is minted
121
110
  // supply should be 15
122
- await expect(
123
- getNFT({ contract, tokenId: 0n }),
124
- ).resolves.toMatchInlineSnapshot(`
125
- {
126
- "id": 0n,
127
- "metadata": {
128
- "name": "Test NFT",
129
- },
130
- "owner": null,
131
- "supply": 15n,
132
- "tokenURI": "ipfs://QmUut8sypH8NaPUeksnirut7MgggMeQa9RvJ37sV513sw3/0",
133
- "type": "ERC1155",
134
- }
135
- `);
111
+ // @ts-expect-error - supply is there
112
+ expect((await getNFT({ contract, tokenId: 0n })).supply).toBe(15n);
136
113
  // account should have a balance of 15
137
114
  await expect(
138
115
  balanceOf({ contract, owner: TEST_ACCOUNT_A.address, tokenId: 0n }),
@@ -155,30 +132,13 @@ describe.runIf(process.env.TW_SECRET_KEY)("TokenERC1155", () => {
155
132
 
156
133
  // now 2 tokens minted
157
134
  await expect(nextTokenIdToMint({ contract })).resolves.toBe(2n);
158
- await expect(getNFTs({ contract })).resolves.toMatchInlineSnapshot(`
159
- [
160
- {
161
- "id": 0n,
162
- "metadata": {
163
- "name": "Test NFT",
164
- },
165
- "owner": null,
166
- "supply": 15n,
167
- "tokenURI": "ipfs://QmUut8sypH8NaPUeksnirut7MgggMeQa9RvJ37sV513sw3/0",
168
- "type": "ERC1155",
169
- },
170
- {
171
- "id": 1n,
172
- "metadata": {
173
- "name": "Test NFT 2",
174
- },
175
- "owner": null,
176
- "supply": 5n,
177
- "tokenURI": "ipfs://QmV6gsfzdiMRtpnh8ay3CgutStVbes7qoF4DKpYE64h8hT/0",
178
- "type": "ERC1155",
179
- },
180
- ]
181
- `);
135
+ await expect(getNFTs({ contract })).resolves.length(2);
136
+ expect((await getNFT({ contract, tokenId: 0n })).metadata.name).toBe(
137
+ "Test NFT",
138
+ );
139
+ expect((await getNFT({ contract, tokenId: 1n })).metadata.name).toBe(
140
+ "Test NFT 2",
141
+ );
182
142
  });
183
143
 
184
144
  it("isGetNFTsSupported should work with our Edition contracts", async () => {
@@ -241,63 +201,10 @@ describe.runIf(process.env.TW_SECRET_KEY)("TokenERC1155", () => {
241
201
  });
242
202
 
243
203
  const nfts = await getNFTs({ contract });
244
- expect(nfts).toStrictEqual([
245
- {
246
- // Updated tokenURI from the test above
247
- metadata: { name: "Test1 Updated" },
248
- owner: null,
249
- id: 0n,
250
- tokenURI: "ipfs://QmTSyt6YgoFtH8yhWgevC22BxjyrkCKuzdk86nqQJCwrV9/0",
251
- type: "ERC1155",
252
- supply: 15n,
253
- },
254
- {
255
- metadata: { name: "Test NFT 2" },
256
- owner: null,
257
- id: 1n,
258
- tokenURI: "ipfs://QmV6gsfzdiMRtpnh8ay3CgutStVbes7qoF4DKpYE64h8hT/0",
259
- type: "ERC1155",
260
- supply: 5n,
261
- },
262
- {
263
- metadata: {
264
- name: "tw-contract-name",
265
- symbol: "tw-contract-symbol",
266
- description: "tw-contract-description",
267
- },
268
- owner: null,
269
- id: 2n,
270
- // Minted using URI from the test above
271
- tokenURI:
272
- "ipfs://bafybeiewg2e3vehsb5pb34xwk25eyyfwlvajzmgz3rtdrvn3upsxnsbhzi/contractUri.json",
273
- type: "ERC1155",
274
- supply: 1n,
275
- },
276
- {
277
- metadata: { name: "batch token 0" },
278
- owner: null,
279
- id: 3n,
280
- tokenURI: "ipfs://QmPSQhC2J6Wig4pH1Lt5th19FM58J5oukhfLfpc9L1i39Q/0",
281
- type: "ERC1155",
282
- supply: 1n,
283
- },
284
- {
285
- metadata: { name: "batch token 1" },
286
- owner: null,
287
- id: 4n,
288
- tokenURI: "ipfs://QmWRQwLBAeq6Wr65Yj7A4aeYqMD1C7Hs1moz15ncS6EhGu/0",
289
- type: "ERC1155",
290
- supply: 2n,
291
- },
292
- {
293
- metadata: { name: "batch token 2" },
294
- owner: null,
295
- id: 5n,
296
- tokenURI: "ipfs://QmTg1wxKGvdZR4NrdkYZGcfB5YYaBz75DH83td5RwprMRP/0",
297
- type: "ERC1155",
298
- supply: 3n,
299
- },
300
- ]);
204
+ const names = nfts.map((nft) => nft.metadata.name);
205
+ expect(names).toContain("batch token 0");
206
+ expect(names).toContain("batch token 1");
207
+ expect(names).toContain("batch token 2");
301
208
  });
302
209
 
303
210
  it("getOwnedTokenIds should work", async () => {
@@ -4,7 +4,7 @@ import { getCurrencyMetadata } from "./getCurrencyMetadata.js";
4
4
 
5
5
  describe("getCurrencyMetadata", () => {
6
6
  it("should throw if not a valid ERC20 contract", async () => {
7
- await expect(() =>
7
+ await expect(
8
8
  getCurrencyMetadata({ contract: DOODLES_CONTRACT }),
9
9
  ).rejects.toThrowError("Invalid currency token");
10
10
  });
@@ -65,19 +65,9 @@ describe.runIf(process.env.TW_SECRET_KEY)(
65
65
  });
66
66
 
67
67
  await expect(nextTokenIdToMint({ contract })).resolves.toBe(6n);
68
- await expect(
69
- getNFT({ contract, tokenId: 0n }),
70
- ).resolves.toMatchInlineSnapshot(`
71
- {
72
- "id": 0n,
73
- "metadata": {
74
- "name": "Test NFT",
75
- },
76
- "owner": null,
77
- "tokenURI": "ipfs://QmTo68Dm1ntSp2BHLmE9gesS6ELuXosRz5mAgFCK6tfsRk/0",
78
- "type": "ERC721",
79
- }
80
- `);
68
+ expect((await getNFT({ contract, tokenId: 0n })).metadata.name).toBe(
69
+ "Test NFT",
70
+ );
81
71
  });
82
72
 
83
73
  it("should allow to claim tokens", async () => {
@@ -96,19 +96,9 @@ describe.runIf(process.env.TW_SECRET_KEY)(
96
96
  });
97
97
 
98
98
  await expect(nextTokenIdToMint({ contract })).resolves.toBe(10n);
99
- await expect(
100
- getNFT({ contract, tokenId: 0n }),
101
- ).resolves.toMatchInlineSnapshot(`
102
- {
103
- "id": 0n,
104
- "metadata": {
105
- "name": "Test NFT",
106
- },
107
- "owner": null,
108
- "tokenURI": "ipfs://QmY1Rr4C7cYVPAaXykMMxg3AVbatDZ6Rd7u3gzt79CiDSB/0",
109
- "type": "ERC721",
110
- }
111
- `);
99
+ expect((await getNFT({ contract, tokenId: 0n })).metadata.name).toBe(
100
+ "Test NFT",
101
+ );
112
102
  });
113
103
 
114
104
  it("should allow to claim tokens", async () => {
@@ -6,6 +6,7 @@ import {
6
6
  type TokenURIParams,
7
7
  tokenURI,
8
8
  } from "../__generated__/IERC721A/read/tokenURI.js";
9
+ import { tokenByIndex } from "../__generated__/IERC721Enumerable/read/tokenByIndex.js";
9
10
 
10
11
  export { isTokenURISupported as isGetNFTSupported } from "../__generated__/IERC721A/read/tokenURI.js";
11
12
 
@@ -19,6 +20,13 @@ export type GetNFTParams = Prettify<
19
20
  * Whether to include the owner of the NFT.
20
21
  */
21
22
  includeOwner?: boolean;
23
+ /**
24
+ * Whether to check and fetch tokenID by index, in case of non-sequential IDs.
25
+ *
26
+ * It should be set to true if it's an ERC721Enumerable contract, and has `tokenByIndex` function.
27
+ * In this case, the provided tokenId will be considered as token-index and actual tokenId will be fetched from the contract.
28
+ */
29
+ tokenByIndex?: boolean;
22
30
  }
23
31
  >;
24
32
 
@@ -35,15 +43,37 @@ export type GetNFTParams = Prettify<
35
43
  * tokenId: 1n,
36
44
  * });
37
45
  * ```
46
+ *
47
+ * * @example
48
+ * ```ts
49
+ * import { getNFT } from "thirdweb/extensions/erc721";
50
+ *
51
+ *
52
+ * const nft = await getNFT({
53
+ * contract,
54
+ * tokenId: 1n,
55
+ * tokenByIndex: true // use this flag if the contract supports `tokenByIndex` and the above tokenId should be treated as an index.
56
+ * });
57
+ * ```
38
58
  */
39
59
  export async function getNFT(
40
60
  options: BaseTransactionOptions<GetNFTParams>,
41
61
  ): Promise<NFT> {
62
+ let tokenId = options.tokenId;
63
+ if (options.tokenByIndex) {
64
+ try {
65
+ tokenId = await tokenByIndex({
66
+ contract: options.contract,
67
+ index: options.tokenId,
68
+ });
69
+ } catch {}
70
+ }
71
+
42
72
  const [uri, owner] = await Promise.all([
43
- tokenURI(options).catch(() => null),
73
+ tokenURI({ contract: options.contract, tokenId }).catch(() => null),
44
74
  options.includeOwner
45
75
  ? import("../__generated__/IERC721A/read/ownerOf.js")
46
- .then((m) => m.ownerOf(options))
76
+ .then((m) => m.ownerOf({ contract: options.contract, tokenId }))
47
77
  .catch(() => null)
48
78
  : null,
49
79
  ]);
@@ -51,12 +81,12 @@ export async function getNFT(
51
81
  if (!uri?.trim()) {
52
82
  return parseNFT(
53
83
  {
54
- id: options.tokenId,
84
+ id: tokenId,
55
85
  type: "ERC721",
56
86
  uri: "",
57
87
  },
58
88
  {
59
- tokenId: options.tokenId,
89
+ tokenId,
60
90
  tokenUri: "",
61
91
  type: "ERC721",
62
92
  owner,
@@ -67,15 +97,15 @@ export async function getNFT(
67
97
  return parseNFT(
68
98
  await fetchTokenMetadata({
69
99
  client: options.contract.client,
70
- tokenId: options.tokenId,
100
+ tokenId,
71
101
  tokenUri: uri,
72
102
  }).catch(() => ({
73
- id: options.tokenId,
103
+ id: tokenId,
74
104
  type: "ERC721",
75
105
  uri,
76
106
  })),
77
107
  {
78
- tokenId: options.tokenId,
108
+ tokenId: tokenId,
79
109
  tokenUri: uri,
80
110
  type: "ERC721",
81
111
  owner,
@@ -189,7 +189,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFTs", () => {
189
189
  });
190
190
 
191
191
  it("should throw error if totalSupply and nextTokenIdToMint are not supported", async () => {
192
- await expect(() =>
192
+ await expect(
193
193
  getNFTs({ contract: UNISWAPV3_FACTORY_CONTRACT }),
194
194
  ).rejects.toThrowError(
195
195
  "Contract requires either `nextTokenIdToMint` or `totalSupply` function available to determine the next token ID to mint",
@@ -34,6 +34,13 @@ export type GetNFTsParams = {
34
34
  * @default false
35
35
  */
36
36
  includeOwners?: boolean;
37
+ /**
38
+ * Whether to check and fetch tokenID by index, in case of non-sequential IDs.
39
+ *
40
+ * It should be set to true if it's an ERC721Enumerable contract, and has `tokenByIndex` function.
41
+ * In this case, the provided tokenId will be considered as token-index and actual tokenId will be fetched from the contract.
42
+ */
43
+ tokenByIndex?: boolean;
37
44
  };
38
45
 
39
46
  /**
@@ -22,7 +22,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc721.getOwnedTokenIds", () => {
22
22
  it("should throw if tokenOfOwnerByIndex or tokensOfOwner not supported", async () => {
23
23
  // We know current Lens contract on Polygon doesn't have this.
24
24
  const contract = UNISWAPV3_FACTORY_CONTRACT;
25
- await expect(() =>
25
+ await expect(
26
26
  getOwnedTokenIds({ contract, owner: TEST_ACCOUNT_B.address }),
27
27
  ).rejects.toThrowError(
28
28
  `The contract at ${contract.address} on chain ${contract.chain.id} does not support the tokenOfOwnerByIndex or tokensOfOwner interface`,
@@ -14,7 +14,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("resolve lens address", () => {
14
14
  });
15
15
 
16
16
  it("should throw if passed an invalid lens handle", async () => {
17
- await expect(() =>
17
+ await expect(
18
18
  resolveAddress({ client: TEST_CLIENT, name: "vitalik.eth" }),
19
19
  ).rejects.toThrowError(
20
20
  "Could not fetch the wallet address for lens handle: vitalik.eth",
@@ -9,7 +9,7 @@ const account = TEST_ACCOUNT_B;
9
9
 
10
10
  describe.runIf(process.env.TW_SECRET_KEY)("deploy-voteERC20 contract", () => {
11
11
  it("should throw if passed an non-integer-like value to minVoteQuorumRequiredPercent", async () => {
12
- await expect(() =>
12
+ await expect(
13
13
  deployVoteContract({
14
14
  account,
15
15
  client: TEST_CLIENT,
@@ -19,7 +19,7 @@ describe.runIf(process.env.TW_SECRET_KEY)(
19
19
  });
20
20
 
21
21
  it("should throw an error with a non-existent domain name", async () => {
22
- await expect(() =>
22
+ await expect(
23
23
  resolveAddress({
24
24
  name: "thirdwebsdk.thissuredoesnotexist",
25
25
  client: TEST_CLIENT,
@@ -18,7 +18,7 @@ describe.runIf(process.env.TW_SECRET_KEY)(
18
18
  });
19
19
 
20
20
  it("should throw error on addresses that dont own any UD", async () => {
21
- await expect(() =>
21
+ await expect(
22
22
  resolveName({ client: TEST_CLIENT, address: TEST_ACCOUNT_D.address }),
23
23
  ).rejects.toThrowError(
24
24
  `Failed to retrieve domain for address: ${TEST_ACCOUNT_D.address}. Make sure you have set the Reverse Resolution address for your domain at https://unstoppabledomains.com/manage?page=reverseResolution&domain=your-domain`,
@@ -52,11 +52,13 @@ export async function getGasOverridesForTransaction(
52
52
  transaction: PreparedTransaction,
53
53
  ): Promise<FeeDataParams> {
54
54
  // first check for explicit values
55
- const [maxFeePerGas, maxPriorityFeePerGas, gasPrice] = await Promise.all([
56
- resolvePromisedValue(transaction.maxFeePerGas),
57
- resolvePromisedValue(transaction.maxPriorityFeePerGas),
58
- resolvePromisedValue(transaction.gasPrice),
59
- ]);
55
+ const [maxFeePerGas, maxPriorityFeePerGas, gasPrice, type] =
56
+ await Promise.all([
57
+ resolvePromisedValue(transaction.maxFeePerGas),
58
+ resolvePromisedValue(transaction.maxPriorityFeePerGas),
59
+ resolvePromisedValue(transaction.gasPrice),
60
+ resolvePromisedValue(transaction.type),
61
+ ]);
60
62
 
61
63
  // Exit early if the user explicitly provided enough options
62
64
  if (maxFeePerGas !== undefined && maxPriorityFeePerGas !== undefined) {
@@ -65,6 +67,7 @@ export async function getGasOverridesForTransaction(
65
67
  maxPriorityFeePerGas,
66
68
  };
67
69
  }
70
+
68
71
  if (typeof gasPrice === "bigint") {
69
72
  return { gasPrice };
70
73
  }
@@ -73,6 +76,7 @@ export async function getGasOverridesForTransaction(
73
76
  const defaultGasOverrides = await getDefaultGasOverrides(
74
77
  transaction.client,
75
78
  transaction.chain,
79
+ type === "legacy" ? "legacy" : "eip1559", // 7702, 2930, and eip1559 all qualify as "eip1559" fee type
76
80
  );
77
81
 
78
82
  if (transaction.chain.experimental?.increaseZeroByteCount) {
@@ -103,6 +107,8 @@ export async function getGasOverridesForTransaction(
103
107
  };
104
108
  }
105
109
 
110
+ export type FeeType = "legacy" | "eip1559";
111
+
106
112
  /**
107
113
  * Retrieves the default gas overrides for a given client and chain ID.
108
114
  * If the fee data contains both maxFeePerGas and maxPriorityFeePerGas, it returns an object with those values.
@@ -115,20 +121,27 @@ export async function getGasOverridesForTransaction(
115
121
  export async function getDefaultGasOverrides(
116
122
  client: ThirdwebClient,
117
123
  chain: Chain,
124
+ feeType?: FeeType,
118
125
  ) {
119
- // if chain is in the force gas price list, always use gas price
120
- if (!FORCE_GAS_PRICE_CHAIN_IDS.includes(chain.id)) {
121
- const feeData = await getDynamicFeeData(client, chain);
122
- if (
123
- feeData.maxFeePerGas !== null &&
124
- feeData.maxPriorityFeePerGas !== null
125
- ) {
126
- return {
127
- maxFeePerGas: feeData.maxFeePerGas,
128
- maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
129
- };
130
- }
126
+ // give priority to the transaction fee type over the chain fee type
127
+ const resolvedFeeType = feeType ?? chain.feeType;
128
+ // if chain is configured to force legacy transactions or is in the legacy chain list
129
+ if (
130
+ resolvedFeeType === "legacy" ||
131
+ FORCE_GAS_PRICE_CHAIN_IDS.includes(chain.id)
132
+ ) {
133
+ return {
134
+ gasPrice: await getGasPrice({ client, chain, percentMultiplier: 10 }),
135
+ };
136
+ }
137
+ const feeData = await getDynamicFeeData(client, chain);
138
+ if (feeData.maxFeePerGas !== null && feeData.maxPriorityFeePerGas !== null) {
139
+ return {
140
+ maxFeePerGas: feeData.maxFeePerGas,
141
+ maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
142
+ };
131
143
  }
144
+ // TODO: resolvedFeeType here could be "EIP1559", but we could not get EIP1559 fee data. should we throw?
132
145
  return {
133
146
  gasPrice: await getGasPrice({ client, chain, percentMultiplier: 10 }),
134
147
  };
@@ -156,7 +169,7 @@ async function getDynamicFeeData(
156
169
  eth_maxPriorityFeePerGas(rpcRequest).catch(() => null),
157
170
  ]);
158
171
 
159
- const baseBlockFee = block?.baseFeePerGas ?? 0n;
172
+ const baseBlockFee = block?.baseFeePerGas;
160
173
 
161
174
  const chainId = chain.id;
162
175
  // flag chain testnet & flag chain
@@ -174,7 +187,7 @@ async function getDynamicFeeData(
174
187
  maxPriorityFeePerGas_ = maxPriorityFeePerGas;
175
188
  }
176
189
 
177
- if (maxPriorityFeePerGas_ == null) {
190
+ if (maxPriorityFeePerGas_ == null || baseBlockFee == null) {
178
191
  // chain does not support eip-1559, return null for both
179
192
  return { maxFeePerGas: null, maxPriorityFeePerGas: null };
180
193
  }
@@ -52,7 +52,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Pay: crypto-to-fiat", () => {
52
52
  });
53
53
 
54
54
  it("should throw error for testnet chain (because testnets are not supported", async () => {
55
- await expect(() =>
55
+ await expect(
56
56
  convertCryptoToFiat({
57
57
  chain: sepolia,
58
58
  fromTokenAddress: NATIVE_TOKEN_ADDRESS,
@@ -66,7 +66,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Pay: crypto-to-fiat", () => {
66
66
  });
67
67
 
68
68
  it("should throw error if fromTokenAddress is set to an invalid EVM address", async () => {
69
- await expect(() =>
69
+ await expect(
70
70
  convertCryptoToFiat({
71
71
  chain: ethereum,
72
72
  fromTokenAddress: "haha",
@@ -80,7 +80,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Pay: crypto-to-fiat", () => {
80
80
  });
81
81
 
82
82
  it("should throw error if fromTokenAddress is set to a wallet address", async () => {
83
- await expect(() =>
83
+ await expect(
84
84
  convertCryptoToFiat({
85
85
  chain: base,
86
86
  fromTokenAddress: TEST_ACCOUNT_A.address,
@@ -99,7 +99,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Pay: crypto-to-fiat", () => {
99
99
  status: 400,
100
100
  statusText: "Bad Request",
101
101
  });
102
- await expect(() =>
102
+ await expect(
103
103
  convertCryptoToFiat({
104
104
  chain: base,
105
105
  fromTokenAddress: NATIVE_TOKEN_ADDRESS,