"use client"; import { useCallback } from "react"; import type { Chain } from "../../../../../../chains/types.js"; import type { ThirdwebClient } from "../../../../../../client/client.js"; import { getContract } from "../../../../../../contract/contract.js"; import { getListing } from "../../../../../../extensions/marketplace/direct-listings/read/getListing.js"; import type { BaseTransactionOptions } from "../../../../../../transaction/types.js"; import { useReadContract } from "../../../../../core/hooks/contract/useReadContract.js"; import type { TransactionButtonProps } from "../../../../../core/hooks/transaction/transaction-button-utils.js"; import { useActiveAccount } from "../../../../../core/hooks/wallets/useActiveAccount.js"; import { useSendAndConfirmTransaction } from "../../../../hooks/transaction/useSendAndConfirmTransaction.js"; import { TransactionButton } from "../../../TransactionButton/index.js"; export type BuyDirectListingButtonProps = Omit< TransactionButtonProps, "transaction" > & { /** * The contract address of the Marketplace v3 contract. */ contractAddress: string; /** * The chain which the Drop contract is deployed on */ chain: Chain; /** * thirdweb Client */ client: ThirdwebClient; /** * ID of the marketplace's DirectListing */ listingId: bigint; /** * Qty to buy (optional) * * - For ERC721 listing: the `quantity` is always hard-coded to 1n - passing this props doesn't do anything * * - For ERC1155 listing: the `quantity` defaults to the quantity of the listing if not specified. * * The component will also throw an error if you pass a `quantity` and it's greater than the listing's quantity */ quantity?: bigint; }; /** * This button is used with thirdweb Marketplace v3 contract, for buying NFT(s) from a listing. * * Under the hood, it prepares a transaction using the [`buyFromListing` extension](https://portal.thirdweb.com/references/typescript/v5/marketplace/buyFromListing) * and then pass it to a * * Since it uses the TransactionButton, it can take in any props that can be passed * to the [`TransactionButton`](https://portal.thirdweb.com/references/typescript/v5/TransactionButton) * * @param props props of type [BuyDirectListingButtonProps](https://portal.thirdweb.com/references/typescript/v5/BuyDirectListingButtonProps) * @example * ```tsx * import { BuyDirectListingButton } from "thirdweb/react"; * * * Buy NFT * * ``` * * For error handling & callbacks on transaction-sent and transaction-confirmed, * please refer to the TransactionButton docs. * @component * @transaction */ export function BuyDirectListingButton(props: BuyDirectListingButtonProps) { const { contractAddress, listingId, children, chain, client, quantity, payModal, } = props; const defaultPayModalMetadata = payModal ? payModal.metadata : undefined; const account = useActiveAccount(); const contract = getContract({ address: contractAddress, chain, client, }); const { data: payMetadata } = useReadContract(getPayMetadata, { contract, listingId, queryOptions: { enabled: !defaultPayModalMetadata, }, }); const { mutateAsync } = useSendAndConfirmTransaction({ payModal: typeof payModal === "object" ? { ...payModal, metadata: payModal.metadata || payMetadata, } : payModal, }); const prepareBuyTransaction = useCallback(async () => { if (!account) { throw new Error("No account detected"); } const [listing, { getApprovalForTransaction }, { buyFromListing }] = await Promise.all([ getListing({ contract, listingId, }), import( "../../../../../../extensions/erc20/write/getApprovalForTransaction.js" ), import( "../../../../../../extensions/marketplace/direct-listings/write/buyFromListing.js" ), ]); if (!listing) { throw new Error(`Could not retrieve listing with ID: ${listingId}`); } let _quantity = 1n; // For ERC721 the quantity should always be 1n. We throw an error if user passes a different props if (listing.asset.type === "ERC721") { if (typeof quantity === "bigint" && (quantity !== 1n || quantity < 0n)) { throw new Error( "Invalid quantity. This is an ERC721 listing & quantity is always `1n`", ); } } else if (listing.asset.type === "ERC1155") { if (typeof quantity === "bigint") { if (quantity > listing.quantity) { throw new Error( `quantity exceeds available amount. Available: ${listing.quantity.toString()}`, ); } if (quantity < 0n) { throw new Error("Invalid quantity. Should be at least 1n"); } _quantity = quantity; } _quantity = listing.quantity; } const buyTx = buyFromListing({ contract, listingId, quantity: _quantity, recipient: account?.address || "", }); const approveTx = await getApprovalForTransaction({ account, transaction: buyTx, }); if (approveTx) { await mutateAsync(approveTx); } return buyTx; }, [account, contract, quantity, listingId, mutateAsync]); return ( prepareBuyTransaction()} {...props} > {children} ); } /** * @internal */ async function getPayMetadata( options: BaseTransactionOptions<{ listingId: bigint }>, ): Promise<{ name?: string; image?: string }> { const listing = await getListing(options); if (!listing) { return { image: undefined, name: undefined }; } return { image: listing.asset?.metadata?.image, name: listing.asset?.metadata?.name, }; }