/** biome-ignore-all lint/a11y/useSemanticElements: FIXME */ "use client"; import { ArrowDownIcon } from "@radix-ui/react-icons"; import { useState } from "react"; import type { TokenWithPrices } from "../../../../bridge/types/Token.js"; import type { ThirdwebClient } from "../../../../client/client.js"; import { getFiatSymbol, type SupportedFiatCurrency, } from "../../../../pay/convert/type.js"; import { type Address, checksumAddress, getAddress, isAddress, shortenAddress, } from "../../../../utils/address.js"; import { getDefaultWalletsForBridgeComponents } from "../../../../wallets/defaultWallets.js"; import { useCustomTheme } from "../../../core/design-system/CustomThemeProvider.js"; import { fontSize, iconSize, radius, spacing, type Theme, } from "../../../core/design-system/index.js"; import { useEnsName } from "../../../core/utils/wallet.js"; import { ConnectButton } from "../ConnectWallet/ConnectButton.js"; import { DetailsModal } from "../ConnectWallet/Details.js"; import { WalletDotIcon } from "../ConnectWallet/icons/WalletDotIcon.js"; import connectLocaleEn from "../ConnectWallet/locale/en.js"; import { PoweredByThirdweb } from "../ConnectWallet/PoweredByTW.js"; import { formatTokenAmount } from "../ConnectWallet/screens/formatTokenBalance.js"; import { Container } from "../components/basic.js"; import { Button } from "../components/buttons.js"; import { CopyIcon } from "../components/CopyIcon.js"; import { Modal } from "../components/Modal.js"; import { Skeleton } from "../components/Skeleton.js"; import { Spacer } from "../components/Spacer.js"; import { Text } from "../components/text.js"; import { useIsMobile } from "../hooks/useisMobile.js"; import type { PayEmbedConnectOptions } from "../PayEmbed.js"; import { ActiveWalletDetails } from "./common/active-wallet-details.js"; import { DecimalInput } from "./common/decimal-input.js"; import { SelectedTokenButton } from "./common/selected-token-button.js"; import { useTokenBalance } from "./common/token-balance.js"; import { useTokenQuery } from "./common/token-query.js"; // import { TokenAndChain } from "./common/TokenAndChain.js"; import { WithHeader } from "./common/WithHeader.js"; import { useActiveWalletInfo } from "./swap-widget/hooks.js"; import { SelectToken } from "./swap-widget/select-token-ui.js"; import type { ActiveWalletInfo } from "./swap-widget/types.js"; import { useBridgeChain } from "./swap-widget/use-bridge-chains.js"; type FundWalletProps = { /** * The receiver address, defaults to the connected wallet address */ receiverAddress: Address | undefined; /** * ThirdwebClient for price fetching */ client: ThirdwebClient; /** * Called when continue is clicked with the resolved requirements */ onContinue: ( amount: string, token: TokenWithPrices, receiverAddress: Address, ) => void; /** * Quick buy amounts */ presetOptions: [number, number, number]; /** * Connect options for wallet connection */ connectOptions: PayEmbedConnectOptions | undefined; /** * Whether to show thirdweb branding in the widget. */ showThirdwebBranding: boolean; selectedToken: SelectedToken | undefined; setSelectedToken: (token: SelectedToken | undefined) => void; amountSelection: AmountSelection; setAmountSelection: (amountSelection: AmountSelection) => void; /** * The currency to use for the payment. */ currency: SupportedFiatCurrency; /** * Override label to display on the button */ buttonLabel: string | undefined; theme: "light" | "dark" | Theme; onDisconnect: (() => void) | undefined; /** * The metadata to display in the widget. */ metadata: { title: string | undefined; description: string | undefined; image: string | undefined; }; /** * Whether the user can edit the amount. Defaults to true. */ amountEditable: boolean; /** * Whether the user can edit the token selection. Defaults to true. */ tokenEditable: boolean; }; export type SelectedToken = | { chainId: number; tokenAddress: string; } | undefined; export type AmountSelection = | { type: "usd"; value: string; } | { type: "token"; value: string; }; export function FundWallet(props: FundWalletProps) { const theme = useCustomTheme(); const activeWalletInfo = useActiveWalletInfo(); const receiver = props.receiverAddress ?? activeWalletInfo?.activeAccount?.address; const [detailsModalOpen, setDetailsModalOpen] = useState(false); const [isTokenSelectionOpen, setIsTokenSelectionOpen] = useState(false); const isReceiverDifferentFromActiveWallet = props.receiverAddress && isAddress(props.receiverAddress) && (activeWalletInfo?.activeAccount?.address ? checksumAddress(props.receiverAddress) !== checksumAddress(activeWalletInfo?.activeAccount?.address) : true); const tokenQuery = useTokenQuery({ tokenAddress: props.selectedToken?.tokenAddress, chainId: props.selectedToken?.chainId, client: props.client, }); const destinationToken = tokenQuery.data?.type === "success" ? tokenQuery.data.token : undefined; const tokenBalanceQuery = useTokenBalance({ chainId: props.selectedToken?.chainId, tokenAddress: props.selectedToken?.tokenAddress, client: props.client, walletAddress: activeWalletInfo?.activeAccount?.address, }); const actionLabel = isReceiverDifferentFromActiveWallet ? "Pay" : "Buy"; const isMobile = useIsMobile(); // if no receiver address is set - wallet must be connected because the user's wallet is the receiver const showConnectButton = !props.receiverAddress && !activeWalletInfo; return ( {detailsModalOpen && ( { setDetailsModalOpen(false); }} onDisconnect={() => { props.onDisconnect?.(); }} chains={[]} connectOptions={props.connectOptions} /> )} setIsTokenSelectionOpen(v)} autoFocusCrossIcon={false} > setIsTokenSelectionOpen(false)} client={props.client} selectedToken={props.selectedToken} setSelectedToken={(token) => { props.setSelectedToken(token); setIsTokenSelectionOpen(false); }} /> {/* Token Info */} { setIsTokenSelectionOpen(true); }} onWalletClick={() => { setDetailsModalOpen(true); }} currency={props.currency} amountEditable={props.amountEditable} tokenEditable={props.tokenEditable} /> {receiver && isReceiverDifferentFromActiveWallet && ( <> )} {(tokenQuery.isError || tokenQuery.data?.type === "unsupported_token") && (
Failed to fetch token details
)} {/* Continue Button */} {showConnectButton ? ( ) : ( )} {props.showThirdwebBranding ? (
) : ( )}
); } function getAmounts( amountSelection: AmountSelection, fiatPricePerToken: number | undefined, ) { const fiatValue = amountSelection.type === "usd" ? amountSelection.value : fiatPricePerToken ? fiatPricePerToken * Number(amountSelection.value) : undefined; const tokenValue = amountSelection.type === "token" ? amountSelection.value : fiatPricePerToken ? Number(amountSelection.value) / fiatPricePerToken : undefined; return { fiatValue, tokenValue, }; } function TokenSection(props: { amountSelection: AmountSelection; setAmount: (amountSelection: AmountSelection) => void; activeWalletInfo: ActiveWalletInfo | undefined; selectedToken: | { data: TokenWithPrices | undefined; isFetching: boolean; isError: boolean; } | undefined; currency: SupportedFiatCurrency; onSelectToken: () => void; client: ThirdwebClient; title: string; isConnected: boolean; balance: { data: | { value: bigint; decimals: number; symbol: string; name: string; } | undefined; isFetching: boolean; }; onWalletClick: () => void; presetOptions: [number, number, number]; amountEditable: boolean; tokenEditable: boolean; }) { const theme = useCustomTheme(); const chainQuery = useBridgeChain({ chainId: props.selectedToken?.data?.chainId, client: props.client, }); const chain = chainQuery.data; const fiatPricePerToken = props.selectedToken?.data?.prices[props.currency]; const { fiatValue, tokenValue } = getAmounts( props.amountSelection, fiatPricePerToken, ); return ( {props.title} {props.activeWalletInfo && ( )} } > {/* select token */} {/* token value input */} { props.setAmount({ type: "token", value, }); }} disabled={props.amountEditable === false} style={{ border: "none", boxShadow: "none", fontSize: fontSize.xl, fontWeight: 500, paddingInline: 0, paddingBlock: 0, letterSpacing: "-0.025em", }} /> {/* fiat value input */}
{getFiatSymbol(props.currency)} {props.selectedToken?.isFetching ? ( ) : ( { props.setAmount({ type: "usd", value, }); }} disabled={props.amountEditable === false} style={{ border: "none", boxShadow: "none", fontSize: fontSize.md, fontWeight: 400, color: theme.colors.secondaryText, paddingInline: 0, height: "20px", paddingBlock: 0, }} /> )}
{/* suggested amounts */} {props.amountEditable && ( <> {props.presetOptions.map((amount) => ( ))} )}
{/* balance */} {props.isConnected && props.selectedToken && (
Current Balance {props.balance.data === undefined ? ( ) : ( {formatTokenAmount( props.balance.data.value, props.balance.data.decimals, 5, )}{" "} {props.balance.data.symbol} )}
)}
); } function ReceiverWalletSection(props: { address: string; client: ThirdwebClient; }) { const ensNameQuery = useEnsName({ address: props.address, client: props.client, }); return ( To } > {ensNameQuery.data || shortenAddress(props.address)} ); } function SectionContainer(props: { children: React.ReactNode; header: React.ReactNode; }) { const theme = useCustomTheme(); return ( {/* make the background semi-transparent */} {/* header */} {props.header} {/* content */} {props.children} ); } function ArrowSection() { return (
); }