@0xfutbol/id
Version:
React component library with shared providers for 0xFutbol ID
844 lines • 153 kB
JavaScript
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