diff --git a/.changeset/six-drinks-joke.md b/.changeset/six-drinks-joke.md new file mode 100644 index 00000000000..bd6e5b484bb --- /dev/null +++ b/.changeset/six-drinks-joke.md @@ -0,0 +1,5 @@ +--- +"thirdweb": minor +--- + +Add ERC1155 extension: mintToBatch diff --git a/packages/thirdweb/src/exports/extensions/erc1155.ts b/packages/thirdweb/src/exports/extensions/erc1155.ts index 81d142ac10c..88e4c40f5ec 100644 --- a/packages/thirdweb/src/exports/extensions/erc1155.ts +++ b/packages/thirdweb/src/exports/extensions/erc1155.ts @@ -198,3 +198,8 @@ export { // Zora 1155 contract export { nextTokenId } from "../../extensions/erc1155/__generated__/Zora1155/read/nextTokenId.js"; + +export { + mintToBatch, + type MintToBatchParams, +} from "../../extensions/erc1155/write/mintToBatch.js"; diff --git a/packages/thirdweb/src/extensions/erc1155/write/mintToBatch.test.ts b/packages/thirdweb/src/extensions/erc1155/write/mintToBatch.test.ts new file mode 100644 index 00000000000..b18808b7c59 --- /dev/null +++ b/packages/thirdweb/src/extensions/erc1155/write/mintToBatch.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, it } from "vitest"; +import { ANVIL_CHAIN } from "~test/chains.js"; +import { TEST_CONTRACT_URI } from "~test/ipfs-uris.js"; +import { TEST_CLIENT } from "~test/test-clients.js"; +import { TEST_ACCOUNT_C } from "~test/test-wallets.js"; +import { getContract } from "../../../contract/contract.js"; +import { deployERC1155Contract } from "../../../extensions/prebuilts/deploy-erc1155.js"; +import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js"; +import { getNFTs } from "../read/getNFTs.js"; +import { mintToBatch } from "./mintToBatch.js"; + +const chain = ANVIL_CHAIN; +const client = TEST_CLIENT; +const account = TEST_ACCOUNT_C; + +describe("ERC1155 Edition: mintToBatch", () => { + it("should mint multiple tokens in one tx", async () => { + const contract = getContract({ + chain, + client, + address: await deployERC1155Contract({ + chain, + client, + account, + type: "TokenERC1155", + params: { + name: "edition", + contractURI: TEST_CONTRACT_URI, + }, + }), + }); + + await sendAndConfirmTransaction({ + account, + transaction: mintToBatch({ + contract, + to: account.address, + nfts: [ + { metadata: { name: "token 0" }, supply: 1n }, + { metadata: { name: "token 1" }, supply: 2n }, + { metadata: { name: "token 2" }, supply: 3n }, + ], + }), + }); + + const nfts = await getNFTs({ contract }); + expect(nfts).toStrictEqual([ + { + metadata: { name: "token 0" }, + owner: null, + id: 0n, + tokenURI: "ipfs://QmPZ6LpGqMuFbHKTXrNW1NRNLHf1nrxS4dtoFqdZZTKvPX/0", + type: "ERC1155", + supply: 1n, + }, + { + metadata: { name: "token 1" }, + owner: null, + id: 1n, + tokenURI: "ipfs://QmRFPyc3yEYxR4pQxwyTQWTine51TxWCoD6nzJWR3eX45b/0", + type: "ERC1155", + supply: 2n, + }, + { + metadata: { name: "token 2" }, + owner: null, + id: 2n, + tokenURI: "ipfs://QmesQiRLHCgqWZM2GFCs7Nb7rr2S72hU1BVQc7xiTyKZtT/0", + type: "ERC1155", + supply: 3n, + }, + ]); + }); +}); diff --git a/packages/thirdweb/src/extensions/erc1155/write/mintToBatch.ts b/packages/thirdweb/src/extensions/erc1155/write/mintToBatch.ts new file mode 100644 index 00000000000..4aae5da2b26 --- /dev/null +++ b/packages/thirdweb/src/extensions/erc1155/write/mintToBatch.ts @@ -0,0 +1,117 @@ +import { maxUint256 } from "viem"; +import { multicall } from "../../../extensions/common/__generated__/IMulticall/write/multicall.js"; +import { upload } from "../../../storage/upload.js"; +import type { + BaseTransactionOptions, + WithOverrides, +} from "../../../transaction/types.js"; +import type { NFTInput } from "../../../utils/nft/parseNft.js"; +import { encodeMintTo } from "../__generated__/IMintableERC1155/write/mintTo.js"; + +/** + * @extension ERC1155 + */ +export type MintToBatchParams = WithOverrides<{ + /** + * The wallet that the NFTs will be minted to + */ + to: string; + /** + * An array of NFT metadata & supply to mint + * @example + * ```ts + * const nfts = [ + * { + * metadata: { name: "token 0" }, + * supply: 1n, + * }, + * { + * metadata: { name: "token 1" }, + * supply: 10n, + * }, + * ] + * ``` + */ + nfts: Array<{ + supply: bigint; + metadata: NFTInput | string; + }>; +}>; + +/** + * This extension batches multiple `mintTo` extensions into one single multicall. + * Keep in mind that there is a limit of how many NFTs you can mint per transaction. + * This limit varies depends on the network that you are transacting on. + * + * You are recommended to experiment with the number to figure out the best number for your chain of choice. + * @param options - the transaction options + * @returns A promise that resolves to the transaction result. + * @extension ERC1155 + * @example + * ```ts + * import { mintBatchTo } from "thirdweb/extension/erc1155"; + * + * const transaction = mintToBatch({ + * contract: editionContract, + * to: "0x...", + * nfts: [ + * { + * metadata: { + * name: "Token #0", + * image: "...", + * attributes: [], + * }, + * supply: 100n, + * }, + * { + * metadata: { + * name: "Token #1", + * image: "...", + * attributes: [], + * }, + * supply: 111n, + * }, + * ], + * }); + * + * await sendTransaction({ transaction, account }); + * ``` + */ +export function mintToBatch( + options: BaseTransactionOptions, +) { + return multicall({ + contract: options.contract, + asyncParams: async () => { + const uris = await Promise.all( + options.nfts.map((item) => { + if (typeof item.metadata === "string") { + return item.metadata; + } + return upload({ + client: options.contract.client, + files: [item.metadata], + }); + }), + ); + + const data = uris.map((uri, index) => { + const item = options.nfts[index]; + if (!item) { + // Should not happen + throw new Error("Index mismatch"); + } + return encodeMintTo({ + to: options.to, + // maxUint256 is used to indicate that this is a NEW token! + tokenId: maxUint256, + uri, + amount: item.supply, + }); + }); + + return { data }; + }, + overrides: options.overrides, + }); +}