"use client"; import { getContract, type ThirdwebContract, } from "../../../../../../contract/contract.js"; import { getContractMetadata } from "../../../../../../extensions/common/read/getContractMetadata.js"; import { getNFT } from "../../../../../../extensions/erc1155/read/getNFT.js"; import type { PreparedTransaction } from "../../../../../../transaction/prepare-transaction.js"; import type { BaseTransactionOptions } from "../../../../../../transaction/types.js"; import { isString } from "../../../../../../utils/type-guards.js"; import type { Account } from "../../../../../../wallets/interfaces/wallet.js"; import { useReadContract } from "../../../../../core/hooks/contract/useReadContract.js"; import { useActiveAccount } from "../../../../../core/hooks/wallets/useActiveAccount.js"; import { useSendAndConfirmTransaction } from "../../../../hooks/transaction/useSendAndConfirmTransaction.js"; import { TransactionButton } from "../../../TransactionButton/index.js"; import type { ClaimButtonProps, ClaimParams, Erc20ClaimParams, Erc721ClaimParams, Erc1155ClaimParams, } from "./types.js"; /** * This button is used to claim tokens (NFT or ERC20) from a given thirdweb Drop contract. * * there are 3 type of Drop contract: NFT Drop (DropERC721), Edition Drop (DropERC1155) and Token Drop (DropERC20) * * Learn more: https://thirdweb.com/explore/drops * * * Note: This button only works with thirdweb Drop contracts. * For custom contract, please use [`TransactionButton`](https://portal.thirdweb.com/references/typescript/v5/TransactionButton) * @param props * @returns A wrapper for TransactionButton * * @component * @example * * Example for claiming NFT from an NFT Drop contract * ```tsx * import { ClaimButton } from "thirdweb/react"; * import { ethereum } from "thirdweb/chains"; * * * Claim now * * ``` * * For Edition Drop (ERC1155) * ```tsx * * Claim now * * ``` * * For Token Drop (ERC20) * ```tsx * * Claim now * * ``` * * Attach custom Pay metadata * ```tsx * ... * * ``` * * Since this button uses the `TransactionButton`, it can take in any props that can be passed * to the [`TransactionButton`](https://portal.thirdweb.com/references/typescript/v5/TransactionButton) * * * For error handling & callbacks on transaction-sent and transaction-confirmed, * please refer to the TransactionButton docs. * @transaction */ export function ClaimButton(props: ClaimButtonProps) { const { children, contractAddress, client, chain, claimParams, payModal } = props; const defaultPayModalMetadata = payModal ? payModal.metadata : undefined; const contract = getContract({ address: contractAddress, chain, client, }); const { data: payMetadata } = useReadContract(getPayMetadata, { contract, queryOptions: { enabled: !defaultPayModalMetadata, }, tokenId: claimParams.type === "ERC1155" ? claimParams.tokenId : undefined, }); const account = useActiveAccount(); const { mutateAsync } = useSendAndConfirmTransaction({ payModal: typeof payModal === "object" ? { ...payModal, metadata: payModal.metadata || payMetadata, } : payModal, }); return ( { if (!account) { throw new Error("No account detected"); } const [claimTx, { getApprovalForTransaction }] = await Promise.all([ getClaimTransaction({ account, claimParams, contract, }), import( "../../../../../../extensions/erc20/write/getApprovalForTransaction.js" ), ]); const approveTx = await getApprovalForTransaction({ account, transaction: claimTx, }); if (approveTx) { await mutateAsync(approveTx); } return claimTx; }} {...props} > {children} ); } /** * We can only get the image and name for Edition Drop * For NFT Drop and Token Drop we fall back to the name & image of the contract * @internal */ async function getPayMetadata( options: BaseTransactionOptions<{ tokenId?: bigint }>, ): Promise<{ name?: string; image?: string }> { const { contract, tokenId } = options; const [contractMetadata, nft] = await Promise.all([ getContractMetadata(options), tokenId ? getNFT({ contract, tokenId }) : undefined, ]); if (tokenId) { return { image: nft?.metadata?.image, name: nft?.metadata?.name, }; } return { image: isString(contractMetadata?.image) ? contractMetadata.image : undefined, name: isString(contractMetadata?.name) ? contractMetadata.name : undefined, }; } /** * @internal Export for test */ async function getClaimTransaction({ contract, account, claimParams, }: { contract: ThirdwebContract; account: Account | undefined; claimParams: ClaimParams; }): Promise { switch (claimParams.type) { case "ERC721": return await getERC721ClaimTo({ account, claimParams, contract }); case "ERC1155": return await getERC1155ClaimTo({ account, claimParams, contract }); case "ERC20": { return await getERC20ClaimTo({ account, claimParams, contract }); } default: throw new Error( "Invalid contract type. Must be either NFT Drop (ERC721), Edition Drop (ERC1155) or Token Drop (ERC20)", ); } } /** * @internal */ export async function getERC721ClaimTo({ contract, account, claimParams, }: { contract: ThirdwebContract; account: Account | undefined; claimParams: Erc721ClaimParams; }) { const { claimTo } = await import( "../../../../../../extensions/erc721/drops/write/claimTo.js" ); return claimTo({ contract, from: claimParams.from, quantity: claimParams.quantity, to: claimParams.to || account?.address || "", }); } /** * @internal */ export async function getERC1155ClaimTo({ contract, account, claimParams, }: { contract: ThirdwebContract; account: Account | undefined; claimParams: Erc1155ClaimParams; }) { const { claimTo } = await import( "../../../../../../extensions/erc1155/drops/write/claimTo.js" ); return claimTo({ contract, from: claimParams.from, quantity: claimParams.quantity, to: claimParams.to || account?.address || "", tokenId: claimParams.tokenId, }); } /** * @internal */ export async function getERC20ClaimTo({ contract, account, claimParams, }: { contract: ThirdwebContract; account: Account | undefined; claimParams: Erc20ClaimParams; }) { // Ideally we should check if the contract is ERC20 using `isERC20` // however TokenDrop doesn't have `supportsInterface` so it doesn't work const { claimTo } = await import( "../../../../../../extensions/erc20/drops/write/claimTo.js" ); if ("quantity" in claimParams) { return claimTo({ contract, from: claimParams.from, quantity: claimParams.quantity, to: claimParams.to || account?.address || "", }); } if ("quantityInWei" in claimParams) { return claimTo({ contract, from: claimParams.from, quantityInWei: claimParams.quantityInWei, to: claimParams.to || account?.address || "", }); } throw new Error("Missing quantity or quantityInWei"); }