"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 { isERC721 } from "../../../../../../extensions/erc721/read/isERC721.js"; import { isERC1155 } from "../../../../../../extensions/erc1155/read/isERC1155.js"; import { type CreateListingParams, createListing, } from "../../../../../../extensions/marketplace/direct-listings/write/createListing.js"; import type { BaseTransactionOptions } from "../../../../../../transaction/types.js"; import { isString } from "../../../../../../utils/type-guards.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 CreateDirectListingButtonProps = Omit< TransactionButtonProps, "transaction" > & CreateListingParams & { contractAddress: string; chain: Chain; client: ThirdwebClient; }; /** * This button is used to create Direct listings for the thirdweb Marketplace v3 contract * * It uses the [`TransactionButton`](https://portal.thirdweb.com/references/typescript/v5/TransactionButton) * and the [`createListing` extension](https://portal.thirdweb.com/references/typescript/v5/marketplace/createListing) under the hood * which means it inherits all the props of those 2 components. * * @example * ```tsx * import { CreateDirectListingButton } from "thirdweb/react"; * * * > * Sell NFT * * ``` * * For error handling & callbacks on transaction-sent and transaction-confirmed, * please refer to the TransactionButton docs. * @component * @transaction */ export function CreateDirectListingButton( props: CreateDirectListingButtonProps, ) { const { contractAddress, chain, client, children, payModal, assetContractAddress, tokenId, } = props; const marketplaceContract = getContract({ address: contractAddress, chain, client, }); const account = useActiveAccount(); const defaultPayModalMetadata = payModal ? payModal.metadata : undefined; const nftContract = getContract({ address: assetContractAddress, chain, client, }); const { data: payMetadata } = useReadContract(getPayMetadata, { contract: nftContract, queryOptions: { enabled: !defaultPayModalMetadata, }, tokenId, }); const { mutateAsync } = useSendAndConfirmTransaction({ payModal: typeof payModal === "object" ? { ...payModal, metadata: payModal.metadata || payMetadata, } : payModal, }); const prepareTransaction = useCallback(async () => { if (!account) { throw new Error("No account detected"); } const [is721, is1155] = await Promise.all([ isERC721({ contract: nftContract }), isERC1155({ contract: nftContract }), ]); if (!is1155 && !is721) { throw new Error("Asset must either be ERC721 or ERC1155"); } // Check for token approval if (is1155) { const [{ isApprovedForAll }, { setApprovalForAll }] = await Promise.all([ import( "../../../../../../extensions/erc1155/__generated__/IERC1155/read/isApprovedForAll.js" ), import( "../../../../../../extensions/erc1155/__generated__/IERC1155/write/setApprovalForAll.js" ), ]); const isApproved = await isApprovedForAll({ contract: nftContract, operator: marketplaceContract.address, owner: account.address, }); if (!isApproved) { const transaction = setApprovalForAll({ approved: true, contract: nftContract, operator: marketplaceContract.address, }); await mutateAsync(transaction); } } else { const [{ isApprovedForAll }, { setApprovalForAll }, { getApproved }] = await Promise.all([ import( "../../../../../../extensions/erc721/__generated__/IERC721A/read/isApprovedForAll.js" ), import( "../../../../../../extensions/erc721/__generated__/IERC721A/write/setApprovalForAll.js" ), import( "../../../../../../extensions/erc721/__generated__/IERC721A/read/getApproved.js" ), ]); const [isApproved, tokenApproved] = await Promise.all([ isApprovedForAll({ contract: nftContract, operator: marketplaceContract.address, owner: account.address, }), getApproved({ contract: nftContract, tokenId: props.tokenId }), ]); if ( !isApproved && tokenApproved.toLowerCase() !== marketplaceContract.address.toLowerCase() ) { const transaction = setApprovalForAll({ approved: true, contract: nftContract, operator: marketplaceContract.address, }); await mutateAsync(transaction); } } const listingTx = createListing({ contract: marketplaceContract, ...props, }); return listingTx; }, [marketplaceContract, props, account, mutateAsync, nftContract]); return ( prepareTransaction()} {...props} > {children} ); } /** * @internal */ async function getPayMetadata( options: BaseTransactionOptions<{ tokenId: bigint; }>, ): Promise<{ name?: string; image?: string }> { const [ { getContractMetadata }, { getNFT: getERC721 }, { getNFT: getERC1155 }, ] = await Promise.all([ import("../../../../../../extensions/common/read/getContractMetadata.js"), import("../../../../../../extensions/erc721/read/getNFT.js"), import("../../../../../../extensions/erc1155/read/getNFT.js"), ]); const [is721, is1155, contractMetadata] = await Promise.all([ isERC721(options), isERC1155(options), getContractMetadata(options), ]); if (is721) { const nft = await getERC721(options); return { image: nft?.metadata?.image, name: nft?.metadata?.name, }; } if (is1155) { const nft = await getERC1155(options); return { image: nft?.metadata?.image, name: nft?.metadata?.name, }; } return { image: isString(contractMetadata?.image) ? contractMetadata.image : undefined, name: isString(contractMetadata?.name) ? contractMetadata.name : undefined, }; }