diff --git a/.changeset/breezy-dodos-hunt.md b/.changeset/breezy-dodos-hunt.md new file mode 100644 index 00000000000..a8d463accf7 --- /dev/null +++ b/.changeset/breezy-dodos-hunt.md @@ -0,0 +1,5 @@ +--- +"@thirdweb-dev/engine": patch +--- + +client id optional diff --git a/.changeset/five-sheep-crash.md b/.changeset/five-sheep-crash.md new file mode 100644 index 00000000000..b11c77983e5 --- /dev/null +++ b/.changeset/five-sheep-crash.md @@ -0,0 +1,5 @@ +--- +"@thirdweb-dev/insight": patch +--- + +client id optional diff --git a/.changeset/green-olives-unite.md b/.changeset/green-olives-unite.md new file mode 100644 index 00000000000..ee1e09e3d82 --- /dev/null +++ b/.changeset/green-olives-unite.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Handle large NFT colletions when updating metadata diff --git a/packages/engine/src/configure.ts b/packages/engine/src/configure.ts index 4906cbe85b1..ea14537a5fc 100644 --- a/packages/engine/src/configure.ts +++ b/packages/engine/src/configure.ts @@ -2,7 +2,7 @@ import type { Config } from "@hey-api/client-fetch"; import { client } from "./client/client.gen.js"; export type EngineClientOptions = { - readonly clientId: string; + readonly clientId?: string; readonly secretKey?: string; }; diff --git a/packages/insight/src/configure.ts b/packages/insight/src/configure.ts index 0e90879dff7..a41253ccfe9 100644 --- a/packages/insight/src/configure.ts +++ b/packages/insight/src/configure.ts @@ -2,7 +2,7 @@ import type { Config } from "@hey-api/client-fetch"; import { client } from "./client/client.gen.js"; export type InsightClientOptions = { - readonly clientId: string; + readonly clientId?: string; readonly secretKey?: string; }; diff --git a/packages/thirdweb/src/extensions/erc721/drops/write/updateMetadata.test.ts b/packages/thirdweb/src/extensions/erc721/drops/write/updateMetadata.test.ts index bb1a00d0f8c..9dbe0c15424 100644 --- a/packages/thirdweb/src/extensions/erc721/drops/write/updateMetadata.test.ts +++ b/packages/thirdweb/src/extensions/erc721/drops/write/updateMetadata.test.ts @@ -80,4 +80,47 @@ describe.runIf(process.env.TW_SECRET_KEY)("updateMetadata ERC721", () => { "No base URI set. Please set a base URI before updating metadata", ); }); + + it( + "should handle very large batch", + { + timeout: 600000, + }, + async () => { + const address = await deployERC721Contract({ + client, + chain, + account, + type: "DropERC721", + params: { + name: "NFT Drop", + contractURI: TEST_CONTRACT_URI, + }, + }); + const contract = getContract({ + address, + client, + chain, + }); + const lazyMintTx = lazyMint({ + contract, + nfts: Array.from({ length: 1000 }, (_, i) => ({ + name: `token ${i}`, + })), + }); + await sendAndConfirmTransaction({ transaction: lazyMintTx, account }); + const updateTx = updateMetadata({ + contract, + targetTokenId: 1n, + newMetadata: { name: "token 1 - updated" }, + }); + await sendAndConfirmTransaction({ transaction: updateTx, account }); + const nfts = await getNFTs({ contract }); + + expect(nfts.length).toBe(100); // first page + expect(nfts[0]?.metadata.name).toBe("token 0"); + expect(nfts[1]?.metadata.name).toBe("token 1 - updated"); + expect(nfts[2]?.metadata.name).toBe("token 2"); + }, + ); }); diff --git a/packages/thirdweb/src/extensions/erc721/drops/write/updateMetadata.ts b/packages/thirdweb/src/extensions/erc721/drops/write/updateMetadata.ts index daf0a7425d7..e223ecfe1fa 100644 --- a/packages/thirdweb/src/extensions/erc721/drops/write/updateMetadata.ts +++ b/packages/thirdweb/src/extensions/erc721/drops/write/updateMetadata.ts @@ -48,17 +48,30 @@ async function getUpdateMetadataParams( (_, k) => BigInt(k) + startTokenId, ); - const currentMetadatas = await Promise.all( - range.map((id) => - GetNFT.getNFT({ contract, tokenId: id, includeOwner: false }), - ), - ); - - // Abort if any of the items failed to load - if (currentMetadatas.some((item) => item === undefined || !item.tokenURI)) { - throw new Error( - `Failed to load all ${range.length} items from batchIndex: ${batchIndex}`, + const BATCH_SIZE = 50; + const currentMetadatas = []; + for (let i = 0; i < range.length; i += BATCH_SIZE) { + const chunk = range.slice(i, i + BATCH_SIZE); + const chunkResults = await Promise.all( + chunk.map((id) => + GetNFT.getNFT({ + contract, + tokenId: id, + includeOwner: false, + useIndexer: false, + }), + ), ); + currentMetadatas.push(...chunkResults); + if (i + BATCH_SIZE < range.length) { + await new Promise((resolve) => setTimeout(resolve, 500)); + } + // Abort if any of the items failed to load + if (currentMetadatas.some((item) => item === undefined || !item.tokenURI)) { + throw new Error( + `Failed to load all ${range.length} items from batchIndex: ${batchIndex}`, + ); + } } const newMetadatas: NFTInput[] = []; diff --git a/packages/thirdweb/src/wallets/in-app/core/actions/get-enclave-user-status.ts b/packages/thirdweb/src/wallets/in-app/core/actions/get-enclave-user-status.ts index 52b3bf9e89d..55fec4a5c93 100644 --- a/packages/thirdweb/src/wallets/in-app/core/actions/get-enclave-user-status.ts +++ b/packages/thirdweb/src/wallets/in-app/core/actions/get-enclave-user-status.ts @@ -25,7 +25,6 @@ export async function getUserStatus({ method: "GET", headers: { "Content-Type": "application/json", - "x-thirdweb-client-id": client.clientId, Authorization: `Bearer embedded-wallet-token:${authToken}`, }, }, diff --git a/packages/thirdweb/src/wallets/in-app/core/authentication/guest.ts b/packages/thirdweb/src/wallets/in-app/core/authentication/guest.ts index 2884d9e52af..0cfea932e23 100644 --- a/packages/thirdweb/src/wallets/in-app/core/authentication/guest.ts +++ b/packages/thirdweb/src/wallets/in-app/core/authentication/guest.ts @@ -45,7 +45,12 @@ export async function guestAuthenticate(args: { }), }); - if (!res.ok) throw new Error("Failed to generate guest account"); + if (!res.ok) { + const error = await res.text(); + throw new Error( + `Failed to generate guest account: ${res.status} ${res.statusText} ${error}`, + ); + } return (await res.json()) satisfies AuthStoredTokenWithCookieReturnType; } diff --git a/packages/thirdweb/src/wallets/in-app/core/eip7702/minimal-account.test.ts b/packages/thirdweb/src/wallets/in-app/core/eip7702/minimal-account.test.ts new file mode 100644 index 00000000000..919b9887960 --- /dev/null +++ b/packages/thirdweb/src/wallets/in-app/core/eip7702/minimal-account.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "vitest"; +import { TEST_CLIENT } from "../../../../../test/src/test-clients.js"; +import { baseSepolia } from "../../../../chains/chain-definitions/base-sepolia.js"; +import { sendBatchTransaction } from "../../../../transaction/actions/send-batch-transaction.js"; +import { prepareTransaction } from "../../../../transaction/prepare-transaction.js"; +import { generateAccount } from "../../../utils/generateAccount.js"; +import { inAppWallet } from "../../web/in-app.js"; + +const client = TEST_CLIENT; +const chain = baseSepolia; + +describe.runIf(process.env.TW_SECRET_KEY)("7702 Minimal Account", () => { + it("should batch transactions", async () => { + const iaw = inAppWallet({ + executionMode: { + mode: "EIP7702", + sponsorGas: true, + }, + }); + const account = await iaw.connect({ + client, + strategy: "guest", + chain, + }); + const tx1 = prepareTransaction({ + client, + chain, + to: (await generateAccount({ client })).address, + value: 0n, + }); + const tx2 = prepareTransaction({ + client, + chain, + to: (await generateAccount({ client })).address, + value: 0n, + }); + const result = await sendBatchTransaction({ + account, + transactions: [tx1, tx2], + }); + expect(result.transactionHash).toBeDefined(); + }); +});