UNPKG

@0xfutbol/id

Version:

React component library with shared providers for 0xFutbol ID

844 lines 153 kB
import {jsx,jsxs,Fragment}from'react/jsx-runtime';import {useQuery,useQueryClient,useMutation}from'@tanstack/react-query';import {useState,useEffect,forwardRef,useRef,useCallback,useLayoutEffect,useMemo}from'react';import {aD as sendTransaction,aL as waitForReceipt,aM as getBuyWithCryptoQuote,g as getClientFetch,aN as getPayBuyWithFiatQuoteEndpoint,s as stringify,aO as useCustomTheme,aP as useActiveWallet,aQ as useChainMetadata,aR as useEnsName,aS as isNativeToken,at as getContract,Q as toTokens,aT as LoadingScreen,aU as Container,aV as ModalHeader,aW as Spacer,aX as Img,aY as spacing,aZ as WalletImage,a_ as iconSize,a$ as ChainIcon,b0 as Text,b1 as TokenIcon,b2 as NATIVE_TOKEN_ADDRESS,b3 as formatNumber,b4 as Line,b5 as shortenAddress,b6 as Button,b7 as trackPayEvent,b8 as ConnectButton,ap as resolvePromisedValue,ao as encode,b9 as getWalletBalance,ba as getTransactionGasCost,F as getChainMetadata,bb as useActiveAccount,bc as hasSponsoredTransactionsEnabled,bd as useWalletBalance,be as NATIVE_TOKEN,bf as OutlineWalletIcon,bg as ErrorState,bh as WalletRow,bi as formatTokenBalance,bj as TokenSymbol,bk as Skeleton,bl as fontSize,bm as currencies,bn as newStyled,bo as getFiatIcon,bp as StyledDiv,bq as fadeInAnimation,br as DynamicHeight,bs as CrossContainer,bt as IconButton,bu as Cross2Icon,bv as radius,bw as keyframes,bx as ClockIcon,by as ChevronDownIcon,bz as Link,bA as PREFERRED_FIAT_PROVIDER_STORAGE_KEY,bB as Spinner,d as getAddress,bC as useConnectedWallets,bD as StepContainer,bE as StepConnectorArrow,bF as SwitchNetworkButton,h as getCachedChain,aH as isSmartWallet,bG as isInAppWallet,bH as isEcosystemWallet,bI as addPendingTx,bJ as useChainName,bK as PayTokenIcon,bL as useBuyWithFiatStatus,bM as invalidateWalletBalance,aE as allowance,aF as approve,bN as sendBatchTransaction,bO as useBuyWithCryptoStatus,bP as getBuyWithFiatStatus,bQ as useActiveWalletChain,bR as useDebouncedValue,bS as polygon,bT as usdCurrency,bU as convertCryptoToFiat,bV as getTokenAddress,bW as Input,bX as TokenRow,bY as TextDivider,bZ as CardStackIcon,b_ as useDisconnect,b$ as ChevronRightIcon,c0 as getPayBuyWithCryptoTransferEndpoint,c1 as StepBar,c2 as SwapSummary,c3 as Step,c4 as ConnectorLine,c5 as CheckCircledIcon,c6 as prepareTransaction,ac as toWei,c7 as transfer,c8 as SwapStatusScreen,c9 as useBuySupportedDestinations,ca as useBuySupportedSources,cb as WalletSwitcherConnectionScreen,cc as SwapFlow,cd as TokenSelector,ce as NetworkSelectorContent,cf as ChainButton,cg as ChainName}from'./index-DNoa140s.js';import {decimals}from'./decimals-CxnsnJ8p.js';import {getCurrencyMetadata}from'./getCurrencyMetadata-BXyjSZEI.js';import'@0xfutbol/id-sign';import'react-use';import'@0xfutbol/constants';import'thirdweb';import'@matchain/matchid-sdk-react';import'@matchain/matchid-sdk-react/index.css';import'react-dom';import'./decimals-CUdgsyo0.js';/** * Sends a transaction using the provided wallet. * @param options - The options for sending the transaction. * @returns A promise that resolves to the confirmed transaction receipt. * @throws An error if the wallet is not connected. * @transaction * @example * * ### Basic usage * ```ts * import { sendAndConfirmTransaction } from "thirdweb"; * * const transactionReceipt = await sendAndConfirmTransaction({ * account, * transaction * }); * ``` * * ### Gasless usage with [thirdweb Engine](https://portal.thirdweb.com/engine) * ```ts * const transactionReceipt = await sendAndConfirmTransaction({ * account, * transaction, * gasless: { * provider: "engine", * relayerUrl: "https://thirdweb.engine-***.thirdweb.com/relayer/***", * relayerForwarderAddress: "0x...", * } * }); * ``` * * ### Gasless usage with OpenZeppelin * ```ts * const transactionReceipt = await sendAndConfirmTransaction({ * account, * transaction, * gasless: { * provider: "openzeppelin", * relayerUrl: "https://...", * relayerForwarderAddress: "0x...", * } * }); * ``` */ async function sendAndConfirmTransaction(options) { const submittedTx = await sendTransaction(options); return waitForReceipt(submittedTx); }/** * Hook to get a price quote for performing a "Buy with crypto" transaction that allows users to buy a token with another token - aka a swap. * * The price quote is an object of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote). * This quote contains the information about the purchase such as token amounts, processing fees, estimated time etc. * * This hook is a React Query wrapper of the [`getBuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithCryptoQuote) function. * You can also use that function directly * * Once you have the quote, you can use the [`useSendTransaction`](https://portal.thirdweb.com/references/typescript/v5/useSendTransaction) function to send the purchase * and [`useBuyWithCryptoStatus`](https://portal.thirdweb.com/references/typescript/v5/useBuyWithCryptoStatus) function to get the status of the swap transaction. * @param params - object of type [`BuyWithCryptoQuoteQueryParams`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuoteQueryParams) * @param queryParams - options to configure the react query * @returns A React Query object which contains the data of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote) * @example * ```tsx * import { useBuyWithCryptoQuote, useBuyWithCryptoStatus, type BuyWithCryptoStatusQueryParams, useActiveAccount } from "thirdweb/react"; * import { sendTransaction } from 'thirdweb'; * * function Component() { * const buyWithCryptoQuoteQuery = useBuyWithCryptoQuote(swapParams); * const [buyTxHash, setBuyTxHash] = useState<BuyWithCryptoStatusQueryParams | undefined>(); * const buyWithCryptoStatusQuery = useBuyWithCryptoStatus(buyTxHash ? { * client, * transactionHash: buyTxHash, * }: undefined); * * async function handleBuyWithCrypto() { * const account = useActiveAccount(); * * // if approval is required * if (buyWithCryptoQuoteQuery.data.approval) { * const approveTx = await sendTransaction({ * transaction: swapQuote.data.approval, * account: account, * }); * await waitForApproval(approveTx); * } * * // send the transaction to buy crypto * // this promise is resolved when user confirms the transaction in the wallet and the transaction is sent to the blockchain * const buyTx = await sendTransaction({ * transaction: swapQuote.data.transactionRequest, * account: account, * }); * await waitForApproval(buyTx); * * // set buyTx.transactionHash to poll the status of the swap transaction * setBuyWithCryptoTx(buyTx.transactionHash); * } * * return <button onClick={handleBuyWithCrypto}>Swap</button> * } * ``` * @buyCrypto */ function useBuyWithCryptoQuote(params, queryParams) { return useQuery({ ...queryParams, queryKey: ["buyWithCryptoQuote", params], refetchInterval: 20_000, queryFn: () => { if (!params) { throw new Error("Swap params are required"); } return getBuyWithCryptoQuote(params); }, enabled: !!params, retry: false, }); }/** * Get a quote of type [`BuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatQuote) to buy given token with fiat currency. * This quote contains the information about the swap such as token amounts, processing fees, estimated time etc. * * ### Rendering the On-Ramp provider UI * Once you have the `quote`, you can open the `quote.onRampLink` in a new tab - This will prompt the user to buy the token with fiat currency * * ### Determining the steps required * If `quote.onRampToken.token` is same as `quote.toToken` ( same chain + same token address ) - This means that the token can be directly bought from the on-ramp provider. * But if they are different, On-ramp provider will send the `quote.onRampToken` to the user's wallet address and a swap is required to swap it to the desired token onchain. * * You can use the [`isSwapRequiredPostOnramp`](https://portal.thirdweb.com/references/typescript/v5/isSwapRequiredPostOnramp) utility function to check if a swap is required after the on-ramp is done. * * ### Polling for the status * Once you open the `quote.onRampLink` in a new tab, you can start polling for the status using [`getBuyWithFiatStatus`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatStatus) to get the status of the transaction. * * `getBuyWithFiatStatus` returns a status object of type [`BuyWithFiatStatus`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatStatus). * * - If no swap is required - the status will become `"ON_RAMP_TRANSFER_COMPLETED"` once the on-ramp provider has sent the desired token to the user's wallet address. Once you receive this status, the process is complete. * - If a swap is required - the status will become `"CRYPTO_SWAP_REQUIRED"` once the on-ramp provider has sent the tokens to the user's wallet address. Once you receive this status, you need to start the swap process. * * ### Swap Process * On receiving the `"CRYPTO_SWAP_REQUIRED"` status, you can use the [`getPostOnRampQuote`](https://portal.thirdweb.com/references/typescript/v5/getPostOnRampQuote) function to get the quote for the swap of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote). * * Once you have this quote - You can follow the same steps as mentioned in the [`getBuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithCryptoQuote) documentation to perform the swap. * * @param params - object of type [`GetBuyWithFiatQuoteParams`](https://portal.thirdweb.com/references/typescript/v5/GetBuyWithFiatQuoteParams) * @returns Object of type [`BuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatQuote) which contains the information about the quote such as processing fees, estimated time, converted token amounts, etc. * @example * Get a quote for buying 10 USDC on polygon chain (chainId: 137) with USD fiat currency: * * ```ts * import { getBuyWithFiatQuote } from "thirdweb/pay"; * * const quote = await getBuyWithFiatQuote({ * client: client, // thirdweb client * fromCurrencySymbol: "USD", // fiat currency symbol * toChainId: 137, // polygon chain id * toAmount: "10", // amount of USDC to buy * toTokenAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" // USDC token address in polygon chain * toAddress: "0x...", // user's wallet address * isTestMode: false, // whether to use onramp in test mode for testing purpose (defaults to false) * }); * * window.open(quote.onRampLink, "_blank"); * ``` * @buyCrypto */ async function getBuyWithFiatQuote(params) { try { const clientFetch = getClientFetch(params.client); const response = await clientFetch(getPayBuyWithFiatQuoteEndpoint(), { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: stringify({ toAddress: params.toAddress, fromCurrencySymbol: params.fromCurrencySymbol, toChainId: params.toChainId.toString(), toTokenAddress: params.toTokenAddress, fromAmount: params.fromAmount, toAmount: params.toAmount, maxSlippageBPS: params.maxSlippageBPS, isTestMode: params.isTestMode, purchaseData: params.purchaseData, fromAddress: params.fromAddress, toGasAmountWei: params.toGasAmountWei, preferredProvider: params.preferredProvider, multiHopSupported: true, }), }); // Assuming the response directly matches the SwapResponse interface if (!response.ok) { const errorObj = await response.json(); if (errorObj && "error" in errorObj) { throw errorObj; } throw new Error(`HTTP error! status: ${response.status}`); } return (await response.json()).result; } catch (error) { console.error("Error getting buy with fiat quote", error); throw error; } }/** * Hook to get a price quote for performing a "Buy with Fiat" transaction that allows users to buy a token with fiat currency. * * The price quote is an object of type [`BuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatQuote). * This quote contains the information about the purchase such as token amounts, processing fees, estimated time etc. * * This hook is a React Query wrapper of the [`getBuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatQuote) function. * You can also use that function directly * * Once you have the `quote`, you can open a new window with `quote.onRampLink` to allow the user to buy the token with fiat currency. * and [`useBuyWithFiatStatus`](https://portal.thirdweb.com/references/typescript/v5/useBuyWithFiatStatus) function to start polling for the status of this transaction. * * @param params - object of type [`GetBuyWithFiatQuoteParams`](https://portal.thirdweb.com/references/typescript/v5/GetBuyWithFiatQuoteParams) * @param queryParams - options to configure the react query * @returns A React Query object which contains the data of type [`BuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatQuote) * @example * ```ts * import { NATIVE_TOKEN_ADDRESS } from "thirdweb"; * import { base } from "thirdweb/chains"; * import { useBuyWithFiatQuote } from "thirdweb/react"; * * // get a quote for buying 0.01 base native token with USD fiat currency * function Example() { * const quote = useBuyWithFiatQuote({ * client: client, // thirdweb client * fromCurrencySymbol: "USD", // fiat currency symbol * toChainId: base.id, // base chain id * toAmount: "0.01", // amount of token to buy * toTokenAddress: NATIVE_TOKEN_ADDRESS, // native token * toAddress: "0x...", // user's wallet address * }); * * return ( * <div> * {quote.data && ( * <a href={quote.data.onRampLink} target="_blank"> * open onramp provider * </a> * )} * </div> * ); * } * ``` * @buyCrypto */ function useBuyWithFiatQuote(params, queryOptions) { return useQuery({ ...queryOptions, queryKey: ["useBuyWithFiatQuote", params], queryFn: async () => { if (!params) { throw new Error("No params provided"); } return getBuyWithFiatQuote(params); }, enabled: !!params, retry: false, }); }function DirectPaymentModeScreen(props) { const { payUiOptions, supportedDestinations, client, onContinue, payerAccount, } = props; const theme = useCustomTheme(); const activeWallet = useActiveWallet(); const metadata = payUiOptions.metadata; const paymentInfo = payUiOptions.paymentInfo; const { data: chainData } = useChainMetadata(paymentInfo.chain); const { data: sellerEns } = useEnsName({ client, address: paymentInfo.sellerAddress, }); const totalCostQuery = useQuery({ queryKey: ["amount", paymentInfo], queryFn: async () => { let tokenDecimals = 18; if (paymentInfo.token && !isNativeToken(paymentInfo.token)) { tokenDecimals = await decimals({ contract: getContract({ address: paymentInfo.token.address, chain: paymentInfo.chain, client, }), }); } let cost; if ("amountWei" in paymentInfo) { cost = toTokens(paymentInfo.amountWei, tokenDecimals); } else { cost = paymentInfo.amount; } return cost; }, }); const totalCost = totalCostQuery.data; if (!chainData || totalCost === undefined) { return jsx(LoadingScreen, {}); } const token = paymentInfo.token ? { ...paymentInfo.token, icon: paymentInfo.token?.icon || supportedDestinations .find((c) => c.chain.id === paymentInfo.chain.id) ?.tokens.find((t) => t.address.toLowerCase() === paymentInfo.token?.address.toLowerCase())?.icon, } : { address: NATIVE_TOKEN_ADDRESS, name: chainData.nativeCurrency.name, symbol: chainData.nativeCurrency.symbol, icon: chainData.icon?.url, }; return (jsxs(Container, { p: "lg", children: [jsx(ModalHeader, { title: metadata?.name || "Payment Details" }), jsx(Spacer, { y: "lg" }), jsxs(Container, { children: [metadata?.image ? (jsx(Img, { client: client, src: metadata?.image, style: { width: "100%", borderRadius: spacing.md, backgroundColor: theme.colors.tertiaryBg, } })) : activeWallet ? (jsxs(Container, { flex: "row", center: "both", style: { padding: spacing.md, marginBottom: spacing.md, borderRadius: spacing.md, backgroundColor: theme.colors.tertiaryBg, }, children: [jsx(WalletImage, { size: iconSize.xl, id: activeWallet.id, client: client }), jsx("div", { style: { flexGrow: 1, borderBottom: "6px dotted", borderColor: theme.colors.secondaryIconColor, marginLeft: spacing.md, marginRight: spacing.md, } }), jsx(ChainIcon, { client: client, size: iconSize.xl, chainIconUrl: chainData.icon?.url })] })) : null, jsx(Spacer, { y: "md" }), jsxs(Container, { flex: "row", children: [jsx(Container, { flex: "column", expand: true, children: jsx(Text, { size: "md", color: "primaryText", weight: 700, children: "Price" }) }), jsx(Container, { expand: true, children: jsxs(Container, { flex: "row", gap: "xs", center: "y", style: { justifyContent: "right" }, children: [jsx(TokenIcon, { chain: paymentInfo.chain, client: props.client, size: "sm", token: token }), jsxs(Text, { color: "primaryText", size: "md", weight: 700, children: [String(formatNumber(Number(totalCost), 6)), " ", token.symbol] })] }) })] }), jsx(Spacer, { y: "md" }), jsx(Line, {}), jsx(Spacer, { y: "md" }), jsxs(Container, { flex: "row", children: [jsx(Container, { flex: "column", expand: true, children: jsx(Text, { size: "xs", color: "secondaryText", children: "Network" }) }), jsx(Container, { expand: true, children: jsxs(Container, { flex: "row", gap: "xs", center: "y", style: { justifyContent: "right" }, children: [jsx(ChainIcon, { chainIconUrl: chainData.icon?.url, size: "xs", client: props.client }), jsx(Text, { size: "xs", color: "secondaryText", style: { textAlign: "right" }, children: chainData.name })] }) })] }), jsx(Spacer, { y: "sm" }), jsxs(Container, { flex: "row", children: [jsx(Container, { flex: "column", expand: true, children: jsx(Text, { size: "xs", color: "secondaryText", children: "Seller" }) }), jsx(Container, { expand: true, children: jsx(Container, { flex: "row", gap: "xs", center: "y", style: { justifyContent: "right" }, children: jsx(Text, { size: "xs", color: "secondaryText", style: { textAlign: "right" }, children: sellerEns || shortenAddress(paymentInfo.sellerAddress) }) }) })] })] }), jsx(Spacer, { y: "xl" }), payerAccount ? (jsx(Button, { variant: "accent", fullWidth: true, onClick: () => { trackPayEvent({ event: "choose_payment_method_direct_payment_mode", client, walletAddress: payerAccount.address, walletType: activeWallet?.id, }); onContinue(totalCost, paymentInfo.chain, token); }, children: "Choose Payment Method" })) : (jsx("div", { children: jsx(ConnectButton, { ...props.connectOptions, client: client, theme: theme, connectButton: { style: { width: "100%", }, } }) }))] })); }function useTransactionCostAndData(args) { const { transaction, account, supportedDestinations } = args; // Compute query key of the transaction first const [txQueryKey, setTxQueryKey] = useState(); useEffect(() => { Promise.all([ resolvePromisedValue(transaction.value), resolvePromisedValue(transaction.erc20Value), resolvePromisedValue(transaction.to), encode(transaction), ]).then(([value, erc20Value, to, data]) => { setTxQueryKey({ value: value?.toString(), erc20Value: erc20Value?.amountWei?.toString(), erc20Currency: erc20Value?.tokenAddress, to, data, }); }); }, [transaction]); return useQuery({ queryKey: [ "transaction-cost", transaction.chain.id, account?.address, txQueryKey, ], queryFn: async () => { if (!account) { throw new Error("No payer account found"); } const erc20Value = await resolvePromisedValue(transaction.erc20Value); if (erc20Value) { const [tokenBalance, tokenMeta, gasCostWei] = await Promise.all([ getWalletBalance({ address: account.address, chain: transaction.chain, client: transaction.client, tokenAddress: erc20Value.tokenAddress, }), getCurrencyMetadata({ contract: getContract({ address: erc20Value.tokenAddress, chain: transaction.chain, client: transaction.client, }), }), getTransactionGasCost(transaction, account?.address), ]); const transactionValueWei = erc20Value.amountWei; const walletBalance = tokenBalance; const currency = { address: erc20Value.tokenAddress, name: tokenMeta.name, symbol: tokenMeta.symbol, icon: supportedDestinations .find((c) => c.chain.id === transaction.chain.id) ?.tokens.find((t) => t.address.toLowerCase() === erc20Value.tokenAddress.toLowerCase())?.icon, }; return { token: currency, decimals: tokenMeta.decimals, walletBalance, gasCostWei, transactionValueWei, }; } const [nativeWalletBalance, chainMetadata, gasCostWei] = await Promise.all([ getWalletBalance({ address: account.address, chain: transaction.chain, client: transaction.client, }), getChainMetadata(transaction.chain), getTransactionGasCost(transaction, account?.address), ]); const walletBalance = nativeWalletBalance; const transactionValueWei = (await resolvePromisedValue(transaction.value)) || 0n; return { token: { address: NATIVE_TOKEN_ADDRESS, name: chainMetadata.nativeCurrency.name, symbol: chainMetadata.nativeCurrency.symbol, icon: chainMetadata.icon?.url, }, decimals: 18, walletBalance, gasCostWei, transactionValueWei, }; }, enabled: !!transaction && !!txQueryKey, refetchInterval: () => { if (transaction.erc20Value) { // if erc20 value is set, we don't need to poll return undefined; } return 30_000; }, }); }function TransactionModeScreen(props) { const { payUiOptions, client, payerAccount, supportedDestinations, onContinue, } = props; const { data: chainData, error: chainDataError, isLoading: chainDataLoading, refetch: chainDataRefetch, } = useChainMetadata(payUiOptions.transaction.chain); const metadata = payUiOptions.metadata; const { data: transactionCostAndData, error: transactionCostAndDataError, isLoading: transactionCostAndDataLoading, refetch: transactionCostAndDataRefetch, } = useTransactionCostAndData({ transaction: payUiOptions.transaction, account: payerAccount, supportedDestinations, }); const theme = useCustomTheme(); const activeWallet = useActiveWallet(); const activeAccount = useActiveAccount(); const sponsoredTransactionsEnabled = hasSponsoredTransactionsEnabled(activeWallet); const balanceQuery = useWalletBalance({ address: activeAccount?.address, chain: payUiOptions.transaction.chain, tokenAddress: isNativeToken(transactionCostAndData?.token || NATIVE_TOKEN) ? undefined : transactionCostAndData?.token.address, client: props.client, }, { enabled: !!transactionCostAndData, }); if (transactionCostAndDataLoading || chainDataLoading) { return jsx(LoadingScreen, {}); } if (!activeAccount) { return (jsx(Container, { style: { minHeight: "350px", }, fullHeight: true, flex: "row", center: "both", children: jsxs(Container, { animate: "fadein", children: [jsx(Spacer, { y: "xxl" }), jsx(Container, { flex: "row", center: "x", children: jsx(OutlineWalletIcon, { size: iconSize["3xl"] }) }), jsx(Spacer, { y: "lg" }), jsx(Text, { center: true, color: "primaryText", size: "md", children: "Please connect a wallet to continue" }), jsx(Spacer, { y: "xl" }), jsx(Container, { flex: "row", center: "x", style: { width: "100%" }, children: jsx(ConnectButton, { client: client, theme: theme, ...props.connectOptions }) })] }) })); } if (transactionCostAndDataError || chainDataError) { return (jsx(Container, { style: { minHeight: "350px", }, fullHeight: true, flex: "row", center: "both", children: jsx(ErrorState, { title: transactionCostAndDataError?.message || chainDataError?.message || "Something went wrong", onTryAgain: transactionCostAndDataError ? transactionCostAndDataRefetch : chainDataRefetch }) })); } if (!transactionCostAndData || !chainData) { return jsx(LoadingScreen, {}); } const insufficientFunds = balanceQuery.data && balanceQuery.data.value < transactionCostAndData.transactionValueWei; return (jsxs(Container, { p: "lg", children: [jsx(ModalHeader, { title: metadata?.name || "Transaction" }), jsx(Spacer, { y: "lg" }), jsxs(Container, { children: [metadata?.image ? (jsx(Img, { client: client, src: metadata?.image, style: { width: "100%", borderRadius: spacing.md, border: `1px solid ${theme.colors.borderColor}`, backgroundColor: theme.colors.tertiaryBg, } })) : activeAccount ? (jsxs(Container, { flex: "column", gap: "sm", children: [insufficientFunds && (jsx(Text, { size: "sm", color: "danger", style: { textAlign: "center" }, children: "Insufficient funds" })), jsxs(Container, { flex: "row", style: { justifyContent: "space-between", padding: spacing.sm, marginBottom: spacing.sm, borderRadius: spacing.md, backgroundColor: theme.colors.tertiaryBg, border: `1px solid ${theme.colors.borderColor}`, }, children: [jsx(WalletRow, { address: activeAccount?.address, iconSize: "md", client: client }), balanceQuery.data ? (jsxs(Container, { flex: "row", gap: "3xs", center: "y", children: [jsx(Text, { size: "xs", color: "secondaryText", weight: 500, children: formatTokenBalance(balanceQuery.data, false) }), jsx(TokenSymbol, { token: transactionCostAndData.token, chain: payUiOptions.transaction.chain, size: "xs", color: "secondaryText" })] })) : (jsx(Skeleton, { width: "70px", height: fontSize.xs }))] })] })) : null, jsx(Spacer, { y: "md" }), jsxs(Container, { flex: "row", children: [jsx(Container, { flex: "column", expand: true, children: jsx(Text, { size: "md", color: "primaryText", weight: 700, children: "Price" }) }), jsx(Container, { expand: true, children: jsxs(Container, { flex: "row", gap: "xs", center: "y", style: { justifyContent: "right" }, children: [jsx(TokenIcon, { chain: payUiOptions.transaction.chain, client: props.client, size: "sm", token: transactionCostAndData.token }), jsxs(Text, { color: "primaryText", size: "md", weight: 700, children: [String(formatNumber(Number(toTokens(transactionCostAndData.transactionValueWei, transactionCostAndData.decimals)), 6)), " ", transactionCostAndData.token.symbol] })] }) })] }), jsx(Spacer, { y: "md" }), jsx(Line, {}), jsx(Spacer, { y: "md" }), jsxs(Container, { flex: "row", children: [jsx(Container, { flex: "column", expand: true, children: jsx(Text, { size: "xs", color: "secondaryText", children: "Gas Fees" }) }), jsx(Container, { expand: true, children: jsx(Container, { flex: "row", gap: "xs", center: "y", style: { justifyContent: "right" }, children: jsx(Text, { color: sponsoredTransactionsEnabled ? "success" : "primaryText", size: "xs", children: sponsoredTransactionsEnabled ? "Sponsored" : `${String(formatNumber(Number(toTokens(transactionCostAndData.gasCostWei, chainData.nativeCurrency.decimals)), 6))} ${chainData.nativeCurrency.symbol}` }) }) })] }), jsx(Spacer, { y: "sm" }), jsxs(Container, { flex: "row", children: [jsx(Container, { flex: "column", expand: true, children: jsx(Text, { size: "xs", color: "secondaryText", children: "Network" }) }), jsx(Container, { expand: true, children: jsxs(Container, { flex: "row", gap: "xs", center: "y", style: { justifyContent: "right" }, children: [jsx(ChainIcon, { chainIconUrl: chainData.icon?.url, size: "xs", client: props.client }), jsx(Text, { size: "xs", color: "secondaryText", style: { textAlign: "right" }, children: chainData.name })] }) })] })] }), jsx(Spacer, { y: "xl" }), payerAccount ? (jsx(Button, { variant: "accent", fullWidth: true, onClick: () => { let totalCostWei = insufficientFunds ? transactionCostAndData.transactionValueWei - (balanceQuery.data?.value || 0n) : transactionCostAndData.transactionValueWei; if (transactionCostAndData.token.address === NATIVE_TOKEN_ADDRESS && !sponsoredTransactionsEnabled) { totalCostWei += transactionCostAndData.gasCostWei; } trackPayEvent({ event: "choose_payment_method_transaction_mode", client, walletAddress: payerAccount.address, walletType: activeWallet?.id, }); onContinue(toTokens(totalCostWei, transactionCostAndData.decimals), payUiOptions.transaction.chain, transactionCostAndData.token); }, children: "Choose Payment Method" })) : (jsx("div", { children: jsx(ConnectButton, { ...props.connectOptions, client: client, theme: theme, connectButton: { style: { width: "100%", }, } }) }))] })); }function CurrencySelection(props) { return (jsxs(Container, { children: [jsx(Container, { p: "lg", children: jsx(ModalHeader, { title: "Pay with", onBack: props.onBack }) }), jsx(Line, {}), jsx(Spacer, { y: "lg" }), jsx(Container, { flex: "column", gap: "xs", px: "lg", children: currencies.map((c) => { return (jsxs(SelectCurrencyButton, { fullWidth: true, variant: "secondary", onClick: () => props.onSelect(c), gap: "sm", children: [getFiatIcon(c, "lg"), jsxs(Container, { flex: "column", gap: "xxs", children: [jsx(Text, { color: "primaryText", children: c.shorthand }), jsx(Text, { size: "sm", children: c.name })] })] }, c.shorthand)); }) }), jsx(Spacer, { y: "lg" })] })); } const SelectCurrencyButton = /* @__PURE__ */ newStyled(Button)(() => { const theme = useCustomTheme(); return { background: theme.colors.tertiaryBg, justifyContent: "flex-start", gap: spacing.sm, padding: spacing.sm, "&:hover": { background: theme.colors.secondaryButtonBg, transform: "scale(1.01)", }, transition: "background 200ms ease, transform 150ms ease", }; });const defaultMessage = "Unable to get price quote"; // biome-ignore lint/suspicious/noExplicitAny: <explanation> function getErrorMessage(err) { if (typeof err.error === "object" && err.error.code) { if (err.error.code === "MINIMUM_PURCHASE_AMOUNT") { return { code: "MINIMUM_PURCHASE_AMOUNT", message: "Purchase amount is too low", }; } } return { code: "UNABLE_TO_GET_PRICE_QUOTE", message: defaultMessage, }; }/** * * @internal */ const Drawer = /* @__PURE__ */ forwardRef(function Drawer_(props, ref) { return (jsx(DrawerContainer, { ref: ref, children: jsx(DynamicHeight, { children: jsxs(Container, { p: "lg", children: [jsx(CrossContainer, { children: jsx(IconButton, { type: "button", "aria-label": "Close", onClick: props.close, children: jsx(Cross2Icon, { width: iconSize.md, height: iconSize.md, style: { color: "inherit", } }) }) }), props.children] }) }) })); }); const DrawerContainer = /* @__PURE__ */ StyledDiv((_) => { const theme = useCustomTheme(); return { zIndex: 10000, borderTopLeftRadius: radius.xl, borderTopRightRadius: radius.xl, background: theme.colors.modalBg, position: "absolute", bottom: 0, left: 0, right: 0, animation: `${drawerOpenAnimation} 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.1)`, borderTop: `1px solid ${theme.colors.borderColor}`, }; }); const drawerOpenAnimation = keyframes ` from { opacity: 0; transform: translateY(100px); } to { opacity: 1; transform: translateY(0); } `; const DrawerOverlay = /* @__PURE__ */ StyledDiv((_) => { const theme = useCustomTheme(); return { backgroundColor: theme.colors.modalOverlayBg, zIndex: 9999, position: "absolute", inset: 0, animation: `${fadeInAnimation} 400ms cubic-bezier(0.16, 1, 0.3, 1)`, }; }); /** * * @internal */ function useDrawer() { const [isOpen, _setIsOpen] = useState(false); const drawerRef = useRef(null); const drawerOverlayRef = useRef(null); const closeDrawerAnimation = useCallback(() => { return new Promise((resolve) => { if (drawerRef.current) { const animOptions = { easing: "cubic-bezier(0.175, 0.885, 0.32, 1.1)", fill: "forwards", duration: 300, }; const closeAnimation = drawerRef.current.animate([{ transform: "translateY(100%)", opacity: 0 }], animOptions); drawerOverlayRef.current?.animate([{ opacity: 0 }], animOptions); closeAnimation.onfinish = () => resolve(); } else { resolve(); } }); }, []); const setIsOpen = useCallback(async (value) => { if (value) { _setIsOpen(true); } else { await closeDrawerAnimation(); _setIsOpen(false); } }, [closeDrawerAnimation]); // close on outside click useLayoutEffect(() => { if (!isOpen) { return; } const handleClick = (event) => { if (drawerRef.current && event.target instanceof Node && !drawerRef.current.contains(event.target)) { setIsOpen(false); } }; // avoid listening to the click event that opened the drawer by adding a frame delay requestAnimationFrame(() => { document.addEventListener("click", handleClick); }); return () => { document.removeEventListener("click", handleClick); }; }, [isOpen, setIsOpen]); return { drawerRef, drawerOverlayRef, setIsOpen, isOpen, }; }/** * @internal */ function formatSeconds(seconds) { // hours and minutes if (seconds > 3600) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); return `${hours} Hours ${minutes} Minutes`; } // minutes only if (seconds > 60) { const minutes = Math.ceil(seconds / 60); return `${minutes} Minutes`; } return `${seconds}s`; }function EstimatedTimeAndFees(props) { const { estimatedSeconds, quoteIsLoading } = props; return (jsxs(Container, { bg: "tertiaryBg", flex: "row", borderColor: "borderColor", style: { borderRadius: radius.md, borderTopLeftRadius: 0, borderTopRightRadius: 0, justifyContent: "space-between", alignItems: "center", borderWidth: "1px", borderStyle: "solid", }, children: [jsxs(Container, { flex: "row", center: "y", gap: "xxs", color: "accentText", p: "sm", children: [jsx(ClockIcon, { width: iconSize.sm, height: iconSize.sm }), quoteIsLoading ? (jsx(Skeleton, { height: fontSize.xs, width: "50px", color: "borderColor" })) : (jsx(Text, { size: "xs", color: "secondaryText", children: estimatedSeconds !== undefined ? `~${formatSeconds(estimatedSeconds)}` : "--" }))] }), jsxs(Button, { variant: "ghost", onClick: props.onViewFees, gap: "xs", children: [jsx(Container, { color: "accentText", flex: "row", center: "both", children: jsx(ViewFeeIcon, { size: iconSize.sm }) }), jsx(Text, { size: "xs", color: "secondaryText", children: "View Fees" })] })] })); } const ViewFeeIcon = (props) => { return (jsxs("svg", { width: props.size, height: props.size, viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [jsx("path", { d: "M9.5 1.5H2.5C1.94772 1.5 1.5 1.94772 1.5 2.5V9.5C1.5 10.0523 1.94772 10.5 2.5 10.5H9.5C10.0523 10.5 10.5 10.0523 10.5 9.5V2.5C10.5 1.94772 10.0523 1.5 9.5 1.5Z", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }), jsx("path", { d: "M4.5 7.5L7.5 4.5", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" })] })); };/** * Shows an amount "value" and renders the selected token and chain * It also renders the buttons to select the token and chain * It also renders the balance of active wallet for the selected token in selected chain * @internal */ function PayWithCreditCard(props) { return (jsxs(Container, { bg: "tertiaryBg", borderColor: "borderColor", flex: "row", style: { borderRadius: radius.md, borderBottomRightRadius: 0, borderBottomLeftRadius: 0, borderWidth: "1px", borderStyle: "solid", borderBottom: "none", flexWrap: "nowrap", justifyContent: "space-between", alignItems: "center", }, children: [jsxs(CurrencyButton, { variant: "ghost", onClick: props.onSelectCurrency, style: { minHeight: "64px", justifyContent: "flex-start", minWidth: "50%", }, gap: "sm", children: [getFiatIcon(props.currency, "md"), jsxs(Container, { flex: "row", center: "y", gap: "xxs", color: "secondaryText", children: [jsx(Text, { color: "primaryText", children: props.currency.shorthand }), jsx(ChevronDownIcon, { width: iconSize.sm, height: iconSize.sm })] })] }), jsx("div", { style: { flexGrow: 1, flexShrink: 1, display: "flex", flexDirection: "column", alignItems: "flex-end", gap: spacing.xxs, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", justifyContent: "center", paddingRight: spacing.sm, }, children: props.isLoading ? (jsx(Skeleton, { width: "100px", height: fontSize.lg })) : (jsx(Text, { size: "lg", color: props.value ? "primaryText" : "secondaryText", children: props.value ? `${props.currency.symbol}${formatNumber(Number(props.value), 6)}` : "--" })) })] })); } const CurrencyButton = /* @__PURE__ */ newStyled(Button)(() => { return { "&[disabled]:hover": { borderColor: "transparent", }, }; });/** * @internal */ function SwapFees(props) { return (jsx(Container, { flex: "column", gap: "xs", style: { alignItems: "flex-start", }, children: props.quote.processingFees.map((fee) => { const feeAmount = formatNumber(Number(fee.amount), 6); return (jsxs(Container, { flex: "row", gap: "xxs", children: [jsxs(Text, { color: "primaryText", size: "sm", children: [feeAmount === 0 ? "~" : "", feeAmount, " ", fee.token.symbol] }), jsxs(Text, { color: "secondaryText", size: "sm", children: ["($", (fee.amountUSDCents / 100).toFixed(2), ")"] })] }, `${fee.token.chainId}_${fee.token.tokenAddress}_${feeAmount}`)); }) })); } /** * @internal */ function FiatFees(props) { return (jsxs(Container, { flex: "column", gap: "xs", children: [jsxs("div", { style: { display: "flex", justifyContent: "space-between", }, children: [jsx(Text, { inline: true, color: "secondaryText", children: "Amount" }), jsxs(Text, { color: "primaryText", inline: true, children: [formatNumber(Number(props.quote.fromCurrency.amount), 2), " ", props.quote.fromCurrency.currencySymbol] })] }), props.quote.processingFees.map((fee, i) => { const feeAmount = formatNumber(Number(fee.amount), 6); return (jsxs("div", { style: { display: "flex", justifyContent: "space-between", }, children: [jsx(Text, { inline: true, color: "secondaryText", children: fee.feeType === "NETWORK" ? "Network Fee" : "Processing Fee" }), jsxs(Text, { color: "primaryText", inline: true, children: [feeAmount === 0 ? "~" : "", " ", feeAmount, " ", fee.currencySymbol] })] }, i)); }), jsx(Spacer, { y: "xxs" }), jsx(Line, {}), jsx(Spacer, { y: "xxs" }), jsxs("div", { style: { display: "flex", justifyContent: "space-between", }, children: [jsx(Text, { inline: true, color: "secondaryText", children: "Total" }), jsxs(Text, { color: "primaryText", inline: true, children: [formatNumber(Number(props.quote.fromCurrencyWithFees.amount), 6), " ", props.quote.fromCurrencyWithFees.currencySymbol] })] })] })); }const FiatProviders = ["COINBASE", "STRIPE", "TRANSAK", "KADO"];/** * @internal */ function Providers(props) { return (jsx(Container, { expand: true, flex: "column", gap: "sm", style: { alignItems: "flex-start", }, children: FiatProviders.map((provider) => { return (jsx(Container, { flex: "row", expand: true, style: { justifyContent: "space-between", }, children: jsx(Button, { fullWidth: true, onClick: () => props.onSelect(provider), variant: "link", children: jsx(Link, { color: props.preferredProvider === provider ? "primaryText" : "secondaryText", size: "sm", hoverColor: "primaryText", children: provider.charAt(0).toUpperCase() + provider.slice(1).toLowerCase() }) }) }, provider)); }) })); }function FiatScreenContent(props) { const { toToken, tokenAmount, payer, client, setScreen, toChain, showCurrencySelector, selectedCurrency, } = props; const defaultRecipientAddress = props.payOptions?.paymentInfo?.sellerAddress; const receiverAddress = defaultRecipientAddress || props.payer.account.address; const { drawerRef, drawerOverlayRef, isOpen, setIsOpen } = useDrawer(); const [drawerScreen, setDrawerScreen] = useState("fees"); const buyWithFiatOptions = props.payOptions.buyWithFiat; const [preferredProvider, setPreferredProvider] = useState(buyWithFiatOptions !== false ? buyWithFiatOptions?.preferredProvider || (localStorage.getItem(PREFERRED_FIAT_PROVIDER_STORAGE_KEY) ?? undefined) : undefined); const fiatQuoteQuery = useBuyWithFiatQuote(buyWithFiatOptions !== false && tokenAmount ? { fromCurrencySymbol: selectedCurrency.shorthand, toChainId: toChain.id, toAddress: receiverAddress, toTokenAddress: isNativeToken(toToken) ? NATIVE_TOKEN_ADDRESS : toToken.address, toAmount: tokenAmount, client, isTestMode: buyWithFiatOptions?.testMode, purchaseData: props.payOptions.purchaseData, fromAddress: payer.account.address, preferredProvider: preferredProvider, } : undefined); function handleSubmit() { if (!fiatQuoteQuery.data) { return; } setScreen({ id: "fiat-flow", quote: fiatQuoteQuery.data, }); } function showFees() { if (!fiatQuoteQuery.data) { return; } setDrawerScreen("fees"); setIsOpen(true); } function showProviders() { setDrawerScreen("providers"); setIsOpen(true); } const disableSubmit = !fiatQuoteQuery.data; const errorMsg = !fiatQuoteQuery.isLoading && fiatQuoteQuery.error ? getErrorMessage(fiatQuoteQuery.error) : undefined; return (jsxs(Container, { flex: "column", gap: "lg", animate: "fadein", children: [isOpen && (jsxs(Fragment, { children: [jsx(DrawerOverlay, { ref: drawerOverlayRef }), jsxs(Drawer, { ref: drawerRef, close: () => setIsOpen(false), children: [drawerScreen === "fees" && fiatQuoteQuery.data && (jsxs("div", { children: [jsx(Text, { size: "lg", color: "primaryText", children: "Fees" }), jsx(Spacer, { y: "lg" }), jsx(FiatFees, { quote: fiatQuoteQuery.data })] })), drawerScreen === "providers" && (jsxs("div", { children: [jsx(Text, { size: "lg", color: "primaryText", children: "Providers" }), jsx(Spacer, { y: "lg" }), jsx(Providers, { preferredProvider: preferredProvider || fiatQuoteQuery.data?.provider, onSelect: (provider) => { setPreferredProvider(provider); // save the pref in local storage localStorage.setItem(PREFERRED_FIAT_PROVIDER_STORAGE_KEY, provider); setIsOpen(false); } })] }))] })] })), jsxs(Container, { flex: "column", gap: "sm", children: [jsx(Text, { size: "sm", children: "Pay with credit card" }), jsxs("div", { children: [jsx(PayWithCreditCard, { isLoading: fiatQuoteQuery.isLoading, value: fiatQuoteQuery.data?.fromCurrencyWithFees.amount, client: client, currency: selectedCurrency, onSelectCurrency: showCurrencySelector }), jsxs(Container, { bg: "tertiaryBg", flex: "row", borderColor: "borderColor", style: { paddingLeft: spacing.md, justifyContent: "space-between", alignItems: "center", borderWidth: "1px", borderStyle: "solid", borderBottom: "none", }, children: [jsx(Text, { size: "xs", color: "secondaryText", children: "Provider" }), jsx(Button, { variant: "ghost", onClick: showProviders, children: jsxs(Container, { flex: "row", center: "y", gap: "xxs", color: "secondaryText", children: [jsx(Text, { size: "xs", children: preferredProvider ? `${preferred