import { CheckCircledIcon, CrossCircledIcon } from "@radix-ui/react-icons"; import { useId, useState } from "react"; import type { ThirdwebClient } from "../../../../../client/client.js"; import { fontSize, iconSize, spacing, } from "../../../../core/design-system/index.js"; import { useWalletBalance } from "../../../../core/hooks/others/useWalletBalance.js"; import { useActiveAccount } from "../../../../core/hooks/wallets/useActiveAccount.js"; import { useActiveWalletChain } from "../../../../core/hooks/wallets/useActiveWalletChain.js"; import { useSendToken } from "../../../../core/hooks/wallets/useSendToken.js"; import { defaultTokens, type SupportedTokens, } from "../../../../core/utils/defaultTokens.js"; import { Container, ModalHeader } from "../../components/basic.js"; import { Button } from "../../components/buttons.js"; import { Input, Label } from "../../components/formElements.js"; import { Skeleton } from "../../components/Skeleton.js"; import { Spacer } from "../../components/Spacer.js"; import { Spinner } from "../../components/Spinner.js"; import { TokenIcon } from "../../components/TokenIcon.js"; import { Text } from "../../components/text.js"; import { StyledDiv } from "../../design-system/elements.js"; import type { ConnectLocale } from "../locale/types.js"; import { formatTokenBalance } from "./formatTokenBalance.js"; import { type ERC20OrNativeToken, NATIVE_TOKEN } from "./nativeToken.js"; import { TokenSelector } from "./TokenSelector.js"; type TXError = Error & { data?: { message?: string } }; /** * @internal */ export function SendFunds(props: { supportedTokens?: SupportedTokens; onBack: () => void; connectLocale: ConnectLocale; client: ThirdwebClient; }) { const [screen, setScreen] = useState<"base" | "tokenSelector">("base"); const activeChain = useActiveWalletChain(); const chainId = activeChain?.id; const { connectLocale, client } = props; let defaultToken: ERC20OrNativeToken = NATIVE_TOKEN; const supportedTokens = props.supportedTokens || defaultTokens; if ( // if we know chainId chainId && // if there is a list of tokens for this chain supportedTokens[chainId] && // if the list of tokens is not the default list supportedTokens[chainId] !== defaultTokens[chainId] ) { // use the first token in the list as default selected const tokensForChain = supportedTokens[chainId]; const firstToken = tokensForChain?.[0]; if (firstToken) { defaultToken = firstToken; } } const [token, setToken] = useState(defaultToken); const [receiverAddress, setReceiverAddress] = useState(""); const [amount, setAmount] = useState("0"); const chain = useActiveWalletChain(); const tokenList = (chain?.id ? supportedTokens[chain.id] : undefined) || []; if (screen === "tokenSelector" && chain) { return ( { setScreen("base"); }} onTokenSelect={(_token) => { setToken(_token); setScreen("base"); }} tokenList={tokenList} /> ); } return ( { setScreen("tokenSelector"); }} receiverAddress={receiverAddress} setAmount={setAmount} setReceiverAddress={setReceiverAddress} token={token} /> ); } /** * @internal Exported for tests */ export function SendFundsForm(props: { onTokenSelect: () => void; token: ERC20OrNativeToken; receiverAddress: string; setReceiverAddress: (value: string) => void; amount: string; setAmount: (value: string) => void; onBack: () => void; client: ThirdwebClient; connectLocale: ConnectLocale; }) { const locale = props.connectLocale.sendFundsScreen; const tokenAddress = props.token && "address" in props.token ? props.token.address : undefined; const chain = useActiveWalletChain(); const activeAccount = useActiveAccount(); const activeChain = useActiveWalletChain(); const balanceQuery = useWalletBalance({ address: activeAccount?.address, chain, client: props.client, tokenAddress: tokenAddress, }); const { receiverAddress, setReceiverAddress, amount, setAmount } = props; const sendTokenMutation = useSendToken(props.client); function getErrorMessage(error?: TXError) { const message = error?.data?.message || error?.message; if (!message) { return locale.transactionFailed; } if ( message.includes("user rejected") || message.includes("user closed modal") || message.includes("user denied") ) { return locale.transactionRejected; } if (message.includes("insufficient funds")) { return locale.insufficientFunds; } return message; } const tokenId = useId(); const receiverId = useId(); const amountId = useId(); if (!activeChain) { return null; // this should never happen } if (sendTokenMutation.isError) { return ( { sendTokenMutation.reset(); }} title={locale.title} /> {getErrorMessage(sendTokenMutation.error)} ); } if (sendTokenMutation.isSuccess) { return ( { sendTokenMutation.reset(); }} title={locale.title} /> {locale.successMessage} ); } const tokenName = (props.token && "name" in props.token ? props.token.name : undefined) || balanceQuery?.data?.name; const tokenSymbol = (props.token && "symbol" in props.token ? props.token.symbol : undefined) || balanceQuery?.data?.symbol; return (
{ e.preventDefault(); }} > {/* Token */} {/* Send to */} { setReceiverAddress(e.target.value); }} placeholder="0x... or ENS name" required value={receiverAddress} variant="outline" /> {/* Amount */} { setAmount(e.target.value); }} required type="number" value={amount} variant="outline" /> {tokenSymbol} {/* Submit */}
); } const CurrencyBadge = /* @__PURE__ */ StyledDiv({ position: "absolute", right: spacing.sm, top: "50%", transform: "translateY(-50%)", });