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