"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,
};
}