diff --git a/.changeset/dirty-bananas-fall.md b/.changeset/dirty-bananas-fall.md new file mode 100644 index 00000000000..f69609de374 --- /dev/null +++ b/.changeset/dirty-bananas-fall.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Use new BuyWidget in wallet details modal diff --git a/packages/thirdweb/src/insight/common.ts b/packages/thirdweb/src/insight/common.ts index aedbb62bb90..aa3614702df 100644 --- a/packages/thirdweb/src/insight/common.ts +++ b/packages/thirdweb/src/insight/common.ts @@ -14,8 +14,3 @@ export async function assertInsightEnabled(chains: Chain[]) { ); } } - -export async function isInsightEnabled(chain: Chain) { - const chainIds = await getInsightEnabledChainIds(); - return chainIds.includes(chain.id); -} diff --git a/packages/thirdweb/src/pay/buyWithCrypto/getStatus.ts b/packages/thirdweb/src/pay/buyWithCrypto/getStatus.ts index 8b0b5c66b61..353adecb42b 100644 --- a/packages/thirdweb/src/pay/buyWithCrypto/getStatus.ts +++ b/packages/thirdweb/src/pay/buyWithCrypto/getStatus.ts @@ -84,11 +84,6 @@ export type BuyWithCryptoStatus = purchaseData?: PurchaseData; }; -export type ValidBuyWithCryptoStatus = Exclude< - BuyWithCryptoStatus, - { status: "NOT_FOUND" } ->; - /** * Gets the status of a buy with crypto transaction * @param buyWithCryptoTransaction - Object of type [`BuyWithCryptoTransaction`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoTransaction) diff --git a/packages/thirdweb/src/pay/buyWithFiat/isSwapRequiredPostOnramp.ts b/packages/thirdweb/src/pay/buyWithFiat/isSwapRequiredPostOnramp.ts index 198c1b4a1bf..67d29b27caf 100644 --- a/packages/thirdweb/src/pay/buyWithFiat/isSwapRequiredPostOnramp.ts +++ b/packages/thirdweb/src/pay/buyWithFiat/isSwapRequiredPostOnramp.ts @@ -1,5 +1,4 @@ import { getAddress } from "../../utils/address.js"; -import type { PayTokenInfo } from "../utils/commonTypes.js"; import type { BuyWithFiatQuote } from "./getQuote.js"; /** @@ -28,58 +27,3 @@ export function isSwapRequiredPostOnramp( return !(sameChain && sameToken); } - -export function getOnRampSteps( - buyWithFiatQuote: BuyWithFiatQuote, -): OnRampStep[] { - const isSwapRequired = isSwapRequiredPostOnramp(buyWithFiatQuote); - - if (!isSwapRequired) { - return [ - { - action: "buy", - amount: buyWithFiatQuote.estimatedToAmountMin, - token: buyWithFiatQuote.toToken, - }, - ]; - } - - if (buyWithFiatQuote.routingToken) { - return [ - { - action: "buy", - amount: buyWithFiatQuote.onRampToken.amount, - token: buyWithFiatQuote.onRampToken.token, - }, - { - action: "swap", - amount: buyWithFiatQuote.routingToken.amount, - token: buyWithFiatQuote.routingToken.token, - }, - { - action: "bridge", - amount: buyWithFiatQuote.estimatedToAmountMin, - token: buyWithFiatQuote.toToken, - }, - ]; - } - - return [ - { - action: "buy", - amount: buyWithFiatQuote.onRampToken.amount, - token: buyWithFiatQuote.onRampToken.token, - }, - { - action: "swap", - amount: buyWithFiatQuote.estimatedToAmountMin, - token: buyWithFiatQuote.toToken, - }, - ]; -} - -export type OnRampStep = { - action: "buy" | "swap" | "bridge"; - token: PayTokenInfo; - amount: string; -}; diff --git a/packages/thirdweb/src/pay/utils/commonTypes.ts b/packages/thirdweb/src/pay/utils/commonTypes.ts index ef94c3fef4d..f4543d174a9 100644 --- a/packages/thirdweb/src/pay/utils/commonTypes.ts +++ b/packages/thirdweb/src/pay/utils/commonTypes.ts @@ -19,4 +19,4 @@ export type PayOnChainTransactionDetails = { export type FiatProvider = (typeof FiatProviders)[number]; -export const FiatProviders = ["coinbase", "stripe", "transak"] as const; +const FiatProviders = ["coinbase", "stripe", "transak"] as const; diff --git a/packages/thirdweb/src/react/core/hooks/usePaymentMethods.ts b/packages/thirdweb/src/react/core/hooks/usePaymentMethods.ts index cf07e6736eb..2e9ae093c26 100644 --- a/packages/thirdweb/src/react/core/hooks/usePaymentMethods.ts +++ b/packages/thirdweb/src/react/core/hooks/usePaymentMethods.ts @@ -141,11 +141,18 @@ export function usePaymentMethods(options: { limit: 100, maxSteps: 3, originChainId: chainId, - sortBy: "popularity", }); // Add all origin tokens from this chain's routes for (const route of routesForChain) { + // Skip if the origin token is the same as the destination token, will be added later only if includeDestinationToken is true + if ( + route.originToken.chainId === destinationToken.chainId && + route.originToken.address.toLowerCase() === + destinationToken.address.toLowerCase() + ) { + continue; + } const tokenKey = `${route.originToken.chainId}-${route.originToken.address.toLowerCase()}`; allValidOriginTokens.set(tokenKey, route.originToken); } diff --git a/packages/thirdweb/src/react/core/utils/storage.ts b/packages/thirdweb/src/react/core/utils/storage.ts index 0472b369528..c6bb7bb0d6b 100644 --- a/packages/thirdweb/src/react/core/utils/storage.ts +++ b/packages/thirdweb/src/react/core/utils/storage.ts @@ -2,7 +2,6 @@ import type { AsyncStorage } from "../../../utils/storage/AsyncStorage.js"; import type { AuthArgsType } from "../../../wallets/in-app/core/authentication/types.js"; export const LAST_AUTH_PROVIDER_STORAGE_KEY = "lastAuthProvider"; -export const PREFERRED_FIAT_PROVIDER_STORAGE_KEY = "preferredFiatProvider"; export async function setLastAuthProvider( authProvider: AuthArgsType["strategy"], diff --git a/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx b/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx index c35f0e22340..35a0c492105 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx @@ -227,7 +227,10 @@ export function BridgeOrchestrator({ { + send({ type: "RESET" }); + onCancel?.(); + }} onRetry={handleRetry} /> )} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx index 276d6aa8d08..c934d7115a1 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx @@ -20,6 +20,7 @@ import { } from "react"; import { trackPayEvent } from "../../../../analytics/track/pay.js"; import type { Chain } from "../../../../chains/types.js"; +import { getCachedChain } from "../../../../chains/utils.js"; import type { ThirdwebClient } from "../../../../client/client.js"; import { getContract } from "../../../../contract/contract.js"; import type { SupportedFiatCurrency } from "../../../../pay/convert/type.js"; @@ -61,7 +62,10 @@ import type { ConnectButtonProps, PayUIOptions, } from "../../../core/hooks/connection/ConnectButtonProps.js"; -import { useChainFaucets } from "../../../core/hooks/others/useChainQuery.js"; +import { + useChainFaucets, + useChainMetadata, +} from "../../../core/hooks/others/useChainQuery.js"; import { useActiveAccount } from "../../../core/hooks/wallets/useActiveAccount.js"; import { useActiveWallet } from "../../../core/hooks/wallets/useActiveWallet.js"; import { useActiveWalletChain } from "../../../core/hooks/wallets/useActiveWalletChain.js"; @@ -80,6 +84,7 @@ import type { } from "../../../core/utils/defaultTokens.js"; import { useWalletInfo } from "../../../core/utils/wallet.js"; import { WalletUIStatesProvider } from "../../providers/wallet-ui-states-provider.js"; +import { BuyWidget } from "../Bridge/BuyWidget.js"; import { Container, Line } from "../components/basic.js"; import { Button, IconButton } from "../components/buttons.js"; import { ChainActiveDot } from "../components/ChainActiveDot.js"; @@ -116,7 +121,6 @@ import { NetworkSelectorContent, type NetworkSelectorProps, } from "./NetworkSelector.js"; -import { LazyBuyScreen } from "./screens/Buy/LazyBuyScreen.js"; import { WalletManagerScreen } from "./screens/Details/WalletManagerScreen.js"; import { LinkedProfilesScreen } from "./screens/LinkedProfilesScreen.js"; import { LinkProfileScreen } from "./screens/LinkProfileScreen.js"; @@ -390,6 +394,7 @@ export function DetailsModal(props: { const activeWallet = useActiveWallet(); const chainFaucetsQuery = useChainFaucets(walletChain); + const chainMetadataQuery = useChainMetadata(walletChain); const disableSwitchChain = !activeWallet?.switchChain; @@ -622,33 +627,35 @@ export function DetailsModal(props: { )} - {!hideBuyFunds && ( - - )} + {!hideBuyFunds && + chainMetadataQuery.data && + !chainMetadataQuery.data.testnet && ( + + )} @@ -956,23 +963,26 @@ export function DetailsModal(props: { // thirdweb pay else if (screen === "buy") { + const requestedChainId = + props.detailsModal?.payOptions?.prefillBuy?.chain?.id || + walletChain?.id || + props.chains[0]?.id || + 1; content = ( - setScreen("main")} - onDone={closeModal} - payOptions={ - props.detailsModal?.payOptions || { - mode: "fund_wallet", - } - } + locale={locale.id} + onCancel={() => setScreen("main")} + onSuccess={() => setScreen("main")} supportedTokens={props.supportedTokens} - theme={typeof props.theme === "string" ? props.theme : props.theme.type} - title={locale.buy} + tokenAddress={ + props.displayBalanceToken?.[Number(requestedChainId)] as + | `0x${string}` + | undefined + } /> ); } @@ -1143,7 +1153,6 @@ const WalletInfoButton = /* @__PURE__ */ StyledButton((_) => { transition: "background 250ms ease", }, alignItems: "center", - all: "unset", animation: `${fadeInAnimation} 300ms ease`, background: theme.colors.connectedButtonBg, border: `1px solid ${theme.colors.borderColor}`, diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/MenuButton.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/MenuButton.tsx index c7cfa4e9f09..375b7d76ad6 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/MenuButton.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/MenuButton.tsx @@ -25,7 +25,6 @@ export const MenuButton = /* @__PURE__ */ StyledButton((_) => { cursor: "not-allowed", }, alignItems: "center", - all: "unset", backgroundColor: "transparent", borderRadius: radius.md, // border: `1px solid ${theme.colors.borderColor}`, diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/NetworkSelector.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/NetworkSelector.tsx index 15307c866ad..04cf3dca38c 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/NetworkSelector.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/NetworkSelector.tsx @@ -690,12 +690,10 @@ export const ChainButton = /* @__PURE__ */ memo(function ChainButton(props: { )} {confirming || switchingFailed ? ( -
} @@ -718,7 +716,7 @@ export const ChainButton = /* @__PURE__ */ memo(function ChainButton(props: { )} -
+ ) : ( background: theme.colors.secondaryButtonBg, color: theme.colors.primaryText, }, - all: "unset", borderRadius: radius.lg, color: theme.colors.secondaryText, cursor: "pointer", @@ -787,7 +784,6 @@ export const NetworkButton = /* @__PURE__ */ StyledButton((_) => { background: theme.colors.secondaryButtonBg, }, alignItems: "center", - all: "unset", borderRadius: radius.md, boxSizing: "border-box", color: theme.colors.primaryText, diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/WalletSelector.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/WalletSelector.tsx index 2fc370a6611..92b0943b1f5 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/WalletSelector.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/WalletSelector.tsx @@ -720,7 +720,6 @@ const ShowAllWalletsIcon = /* @__PURE__ */ StyledDiv(() => { }); const WalletList = /* @__PURE__ */ StyledUl({ - all: "unset", boxSizing: "border-box", display: "flex", flex: 1, diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx deleted file mode 100644 index 1061309b8e5..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx +++ /dev/null @@ -1,1075 +0,0 @@ -import { useQueryClient } from "@tanstack/react-query"; -import { useCallback, useMemo, useState } from "react"; -import { trackPayEvent } from "../../../../../../analytics/track/pay.js"; -import type { Chain } from "../../../../../../chains/types.js"; -import { getCachedChain } from "../../../../../../chains/utils.js"; -import type { ThirdwebClient } from "../../../../../../client/client.js"; -import { - NATIVE_TOKEN_ADDRESS, - ZERO_ADDRESS, -} from "../../../../../../constants/addresses.js"; -import type { BuyWithCryptoStatus } from "../../../../../../pay/buyWithCrypto/getStatus.js"; -import type { BuyWithFiatStatus } from "../../../../../../pay/buyWithFiat/getStatus.js"; -import { formatNumber } from "../../../../../../utils/formatNumber.js"; -import type { Account } from "../../../../../../wallets/interfaces/wallet.js"; -import type { WalletId } from "../../../../../../wallets/wallet-types.js"; -import { - fontSize, - spacing, - type Theme, -} from "../../../../../core/design-system/index.js"; -import type { - FundWalletOptions, - PayUIOptions, -} from "../../../../../core/hooks/connection/ConnectButtonProps.js"; -import { useActiveAccount } from "../../../../../core/hooks/wallets/useActiveAccount.js"; -import { invalidateWalletBalance } from "../../../../../core/providers/invalidateWalletBalance.js"; -import type { - SupportedTokens, - TokenInfo, -} from "../../../../../core/utils/defaultTokens.js"; -import { ErrorState } from "../../../../wallets/shared/ErrorState.js"; -import { LoadingScreen } from "../../../../wallets/shared/LoadingScreen.js"; -import { Container, Line, ModalHeader } from "../../../components/basic.js"; -import { Button } from "../../../components/buttons.js"; -import { ChainName } from "../../../components/ChainName.js"; -import { Input } from "../../../components/formElements.js"; -import { Spacer } from "../../../components/Spacer.js"; -import { TokenSymbol } from "../../../components/token/TokenSymbol.js"; -import type { PayEmbedConnectOptions } from "../../../PayEmbed.js"; -import { ConnectButton } from "../../ConnectButton.js"; -import type { ConnectLocale } from "../../locale/types.js"; -import { ChainButton, NetworkSelectorContent } from "../../NetworkSelector.js"; -import { PoweredByThirdweb } from "../../PoweredByTW.js"; -import { type ERC20OrNativeToken, isNativeToken } from "../nativeToken.js"; -import { TokenSelector } from "../TokenSelector.js"; -import { WalletSwitcherConnectionScreen } from "../WalletSwitcherConnectionScreen.js"; -import { DirectPaymentModeScreen } from "./DirectPaymentModeScreen.js"; -import { CurrencySelection } from "./fiat/CurrencySelection.js"; -import { FiatScreenContent } from "./fiat/FiatScreenContent.js"; -import { OnRampScreen } from "./fiat/OnRampScreen.js"; -import type { SelectedScreen } from "./main/types.js"; -import { - type PaymentMethods, - useEnabledPaymentMethods, -} from "./main/useEnabledPaymentMethods.js"; -import { - useFiatCurrencySelectionStates, - useFromTokenSelectionStates, - useToTokenSelectionStates, -} from "./main/useUISelectionStates.js"; -import { PayTokenIcon } from "./PayTokenIcon.js"; -import { BuyTokenInput } from "./swap/BuyTokenInput.js"; -import { FiatValue } from "./swap/FiatValue.js"; -import { useWalletsAndBalances } from "./swap/fetchBalancesForWallet.js"; -import { SwapFlow } from "./swap/SwapFlow.js"; -import { SwapScreenContent } from "./swap/SwapScreenContent.js"; -import { TokenSelectorScreen } from "./swap/TokenSelectorScreen.js"; -import { TransferFlow } from "./swap/TransferFlow.js"; -import { - type SupportedChainAndTokens, - useBuySupportedDestinations, - useBuySupportedSources, -} from "./swap/useSwapSupportedChains.js"; -import { TransactionModeScreen } from "./TransactionModeScreen.js"; -import { usePayerSetup } from "./usePayerSetup.js"; - -export type BuyScreenProps = { - title: string; - onBack: (() => void) | undefined; - supportedTokens: SupportedTokens | undefined; - client: ThirdwebClient; - connectLocale: ConnectLocale; - payOptions: PayUIOptions; - theme: "light" | "dark" | Theme; - onDone: () => void; - connectOptions: PayEmbedConnectOptions | undefined; - hiddenWallets?: WalletId[]; - isEmbed: boolean; - paymentLinkId?: string; -}; - -/** - * @internal - */ -export default function BuyScreen(props: BuyScreenProps) { - const isTestMode = props.payOptions.buyWithCrypto - ? props.payOptions.buyWithCrypto.testMode - : undefined; - const supportedDestinationsQuery = useBuySupportedDestinations( - props.client, - isTestMode, - ); - - if (supportedDestinationsQuery.isError) { - return ( - - - - ); - } - - if (!supportedDestinationsQuery.data) { - return ; - } - - const supportedDestinations = props.supportedTokens - ? Object.entries(props.supportedTokens).map(([chainId, tokens]) => ({ - chain: getCachedChain(Number.parseInt(chainId)), - tokens: tokens.map((t) => ({ - ...t, - buyWithCryptoEnabled: true, - buyWithFiatEnabled: true, - })), - })) - : supportedDestinationsQuery.data; - - return ( - - ); -} - -type BuyScreenContentProps = { - title: string; - client: ThirdwebClient; - onBack?: () => void; - supportedTokens?: SupportedTokens; - supportedDestinations: SupportedChainAndTokens; - connectLocale: ConnectLocale; - theme: "light" | "dark" | Theme; - payOptions: PayUIOptions; - onDone: () => void; - hiddenWallets?: WalletId[]; - connectOptions: PayEmbedConnectOptions | undefined; - isEmbed: boolean; - paymentLinkId?: string; -}; - -/** - * @internal - */ -function BuyScreenContent(props: BuyScreenContentProps) { - const { client, supportedDestinations, connectLocale, payOptions } = props; - - const activeAccount = useActiveAccount(); - const { payer, setPayer } = usePayerSetup(); - - const [screen, setScreen] = useState({ - id: "main", - }); - - const { - tokenAmount, - setTokenAmount, - toChain, - setToChain, - deferredTokenAmount, - toToken, - setToToken, - } = useToTokenSelectionStates({ - payOptions, - supportedDestinations, - }); - - const [hasEditedAmount, setHasEditedAmount] = useState(false); - - const onDone = useCallback(() => { - setScreen({ id: "main" }); - props.onDone(); - }, [props.onDone]); - - // check if the screen is expanded or not - - // update supportedSources whenever toToken or toChain is updated - const supportedSourcesQuery = useBuySupportedSources({ - client: props.client, - destinationChainId: toChain.id, - destinationTokenAddress: isNativeToken(toToken) - ? NATIVE_TOKEN_ADDRESS - : toToken.address, - }); - - const destinationSupportedTokens: SupportedTokens = useMemo(() => { - return createSupportedTokens( - supportedDestinations, - payOptions, - props.supportedTokens, - ); - }, [props.supportedTokens, supportedDestinations, payOptions]); - - const sourceSupportedTokens: SupportedTokens | undefined = useMemo(() => { - if (!supportedSourcesQuery.data) { - return undefined; - } - - const supportedSources = supportedSourcesQuery.data; - - return createSupportedTokens( - supportedSources, - payOptions, - props.supportedTokens, - ); - }, [props.supportedTokens, supportedSourcesQuery.data, payOptions]); - - // preload wallets and balances - useWalletsAndBalances({ - client: props.client, - mode: payOptions.mode, - sourceSupportedTokens: sourceSupportedTokens || [], - toChain: toChain, - toToken: toToken, - }); - - const { fromChain, setFromChain, fromToken, setFromToken } = - useFromTokenSelectionStates({ - payOptions, - supportedSources: supportedSourcesQuery.data || [], - }); - - const { selectedCurrency, setSelectedCurrency } = - useFiatCurrencySelectionStates({ - payOptions, - }); - - const enabledPaymentMethods = useEnabledPaymentMethods({ - payOptions: props.payOptions, - supportedDestinations: props.supportedDestinations, - toChain: toChain, - toToken: toToken, - }); - - const payDisabled = - enabledPaymentMethods.buyWithCryptoEnabled === false && - enabledPaymentMethods.buyWithFiatEnabled === false; - - // screens ---------------------------- - - const queryClient = useQueryClient(); - - const onSwapSuccess = useCallback( - (_status: BuyWithCryptoStatus) => { - props.payOptions.onPurchaseSuccess?.({ - status: _status, - type: "crypto", - }); - invalidateWalletBalance(queryClient); - }, - [props.payOptions.onPurchaseSuccess, queryClient], - ); - - const onFiatSuccess = useCallback( - (_status: BuyWithFiatStatus) => { - props.payOptions.onPurchaseSuccess?.({ - status: _status, - type: "fiat", - }); - invalidateWalletBalance(queryClient); - }, - [props.payOptions.onPurchaseSuccess, queryClient], - ); - - if (screen.id === "connect-payer-wallet") { - return ( - setScreen(screen.backScreen)} - onSelect={(w) => { - const account = w.getAccount(); - const chain = w.getChain(); - if (w && account && chain) { - setPayer({ - account, - chain, - wallet: w, - }); - } - }} - recommendedWallets={props.connectOptions?.recommendedWallets} - showAllWallets={ - props.connectOptions?.showAllWallets === undefined - ? true - : props.connectOptions?.showAllWallets - } - walletConnect={props.connectOptions?.walletConnect} - wallets={props.connectOptions?.wallets?.filter((w) => w.id !== "inApp")} - /> - ); - } - - if (screen.id === "swap-flow" && payer) { - return ( - { - setScreen({ - id: "buy-with-crypto", - }); - }} - onDone={onDone} - onSuccess={onSwapSuccess} - onTryAgain={() => { - setScreen({ - id: "buy-with-crypto", - }); - }} - payer={payer} - title={props.title} - transactionMode={payOptions.mode === "transaction"} - /> - ); - } - - if (screen.id === "fiat-flow" && payer) { - const defaultRecipientAddress = ( - props.payOptions as Extract - )?.paymentInfo?.sellerAddress; - const receiverAddress = defaultRecipientAddress || payer.account.address; - return ( - { - setScreen({ - id: "buy-with-fiat", - }); - }} - onDone={onDone} - onSuccess={onFiatSuccess} - payer={payer} - paymentLinkId={props.paymentLinkId} - quote={screen.quote} - receiverAddress={receiverAddress} - testMode={ - props.payOptions.buyWithFiat !== false && - props.payOptions.buyWithFiat?.testMode === true - } - theme={typeof props.theme === "string" ? props.theme : props.theme.type} - title={props.title} - transactionMode={payOptions.mode === "transaction"} - /> - ); - } - - if (screen.id === "transfer-flow" && payer && activeAccount) { - const goBack = () => setScreen({ id: "buy-with-crypto" }); - // TODO (pay) pass it via screen props - const defaultRecipientAddress = ( - props.payOptions as Extract - )?.paymentInfo?.sellerAddress; - const receiverAddress = defaultRecipientAddress || activeAccount.address; - return ( - { - setScreen({ - id: "buy-with-crypto", - }); - }} - payer={payer} - paymentLinkId={props.paymentLinkId} - payOptions={payOptions} - receiverAddress={receiverAddress} - title={props.title} - token={toToken} - tokenAmount={tokenAmount} - transactionMode={props.payOptions.mode === "transaction"} - /> - ); - } - - if (screen.id === "select-currency") { - const goBack = () => setScreen(screen.backScreen); - return ( - { - goBack(); - setSelectedCurrency(currency); - }} - /> - ); - } - - if (screen.id === "select-to-token") { - const chains = supportedDestinations.map((x) => x.chain); - const goBack = () => setScreen(screen.backScreen); - const allowEdits = (payOptions as FundWalletOptions)?.prefillBuy - ?.allowEdits; - // if token selection is disabled - only show network selector screen - if (allowEdits?.token === false) { - return ( - - ); - } - - return ( - { - setToChain(c); - }, - } - : undefined - } - client={client} - connectLocale={connectLocale} - modalTitle={props.title} - onBack={goBack} - onTokenSelect={(tokenInfo) => { - setToToken(tokenInfo); - goBack(); - }} - tokenList={( - (toChain?.id ? destinationSupportedTokens[toChain.id] : undefined) || - [] - ).filter( - (x) => x.address.toLowerCase() !== NATIVE_TOKEN_ADDRESS.toLowerCase(), - )} - /> - ); - } - - return ( - -
- {screen.id === "main" && ( - - setScreen({ backScreen: screen, id: "select-to-token" }) - } - payerAccount={payer?.account} - payOptions={payOptions} - setFromChain={setFromChain} - setFromToken={setFromToken} - setHasEditedAmount={setHasEditedAmount} - setScreen={setScreen} - setToChain={setToChain} - setTokenAmount={setTokenAmount} - setToToken={setToToken} - supportedDestinations={supportedDestinations} - theme={props.theme} - title={props.title} - toChain={toChain} - tokenAmount={tokenAmount} - toToken={toToken} - /> - )} - - {(screen.id === "select-payment-method" || - screen.id === "buy-with-crypto" || - screen.id === "buy-with-fiat" || - screen.id === "select-from-token") && - payer && ( - { - if ( - (screen.id === "buy-with-crypto" || - screen.id === "buy-with-fiat") && - enabledPaymentMethods.buyWithCryptoEnabled - ) { - setScreen({ - backScreen: { id: "main" }, - id: "select-from-token", - }); - } else if (screen.id === "select-from-token") { - setScreen(screen.backScreen); - } else { - setScreen({ id: "main" }); - } - }} - selectedChain={toChain} - selectedToken={toToken} - setTokenAmount={setTokenAmount} - title={props.title} - tokenAmount={tokenAmount} - > - {screen.id === "buy-with-crypto" && activeAccount && ( - { - setScreen({ - backScreen: screen, - id: "select-from-token", - }); - }} - toChain={toChain} - tokenAmount={deferredTokenAmount} - toToken={toToken} - /> - )} - - {screen.id === "buy-with-fiat" && ( - { - setScreen({ - backScreen: screen, - id: "select-currency", - }); - }} - theme={props.theme} - toChain={toChain} - tokenAmount={deferredTokenAmount} - toToken={toToken} - /> - )} - - {screen.id === "select-from-token" && - supportedSourcesQuery.data && - sourceSupportedTokens && ( - { - setScreen({ - backScreen: screen, - id: "connect-payer-wallet", - }); - }} - onPayWithFiat={() => { - setScreen({ - id: "buy-with-fiat", - }); - }} - onSelectToken={(w, token, chain) => { - const account = w.getAccount(); - if (account) { - setPayer({ - account, - chain, - wallet: w, - }); - setFromToken(token); - setFromChain(chain); - } - setScreen({ id: "buy-with-crypto" }); - }} - sourceSupportedTokens={sourceSupportedTokens} - sourceTokens={sourceSupportedTokens} - toChain={toChain} - tokenAmount={tokenAmount} - toToken={toToken} - /> - )} - - )} -
-
- ); -} - -function SelectedTokenInfo(props: { - selectedToken: ERC20OrNativeToken; - selectedChain: Chain; - tokenAmount: string; - setTokenAmount: (amount: string) => void; - client: ThirdwebClient; - disabled?: boolean; -}) { - const getWidth = () => { - const amount = formatNumber(Number(props.tokenAmount), 6).toString(); - let chars = amount.replace(".", "").length; - const hasDot = amount.includes("."); - if (hasDot) { - chars += 0.3; - } - return `calc(${`${Math.max(1, chars)}ch + 2px`})`; - }; - return ( -
- - - { - let value = e.target.value; - - // Replace comma with period if it exists - value = value.replace(",", "."); - - if (value.startsWith(".")) { - value = `0${value}`; - } - - if (value.length > 10) { - return; - } - - const numValue = Number(value); - if (Number.isNaN(numValue)) { - return; - } - - if (value.startsWith("0") && !value.startsWith("0.")) { - props.setTokenAmount(value.slice(1)); - } else { - props.setTokenAmount(value); - } - }} - onClick={(e) => { - // put cursor at the end of the input - if (props.tokenAmount === "") { - e.currentTarget.setSelectionRange( - e.currentTarget.value.length, - e.currentTarget.value.length, - ); - } - }} - pattern="^[0-9]*[.,]?[0-9]*$" - placeholder="0" - style={{ - border: "none", - borderRadius: "0", - boxShadow: "none", - fontSize: fontSize.lg, - fontWeight: 600, - padding: "0", - paddingBlock: "2px", - textAlign: "left", - width: getWidth(), - }} - tabIndex={-1} - type="text" - value={props.tokenAmount || "0"} - variant="outline" - /> - - - - - - - - - - - -
- ); -} - -function MainScreen(props: { - title: string; - client: ThirdwebClient; - setTokenAmount: (amount: string) => void; - setFromChain: (chain: Chain) => void; - setFromToken: (token: ERC20OrNativeToken) => void; - setToChain: (chain: Chain) => void; - setToToken: (token: ERC20OrNativeToken) => void; - payerAccount: Account | undefined; - tokenAmount: string; - payOptions: PayUIOptions; - toToken: ERC20OrNativeToken; - toChain: Chain; - onSelectBuyToken: () => void; - connectOptions: PayEmbedConnectOptions | undefined; - setScreen: (screen: SelectedScreen) => void; - supportedDestinations: SupportedChainAndTokens; - onBack: (() => void) | undefined; - theme: "light" | "dark" | Theme; - hasEditedAmount: boolean; - setHasEditedAmount: (hasEdited: boolean) => void; - enabledPaymentMethods: PaymentMethods; -}) { - const { - setTokenAmount, - setToChain, - setToToken, - setFromChain, - setFromToken, - payerAccount, - client, - tokenAmount, - payOptions, - toToken, - toChain, - supportedDestinations, - enabledPaymentMethods, - } = props; - - const { buyWithCryptoEnabled, buyWithFiatEnabled } = enabledPaymentMethods; - const disableContinue = !tokenAmount; - - switch (payOptions.mode) { - case "transaction": { - return ( - { - setTokenAmount(tokenAmount); - setToChain(toChain); - setFromChain(toChain); - setFromToken(toToken); - setToToken(toToken); - if (buyWithFiatEnabled && !buyWithCryptoEnabled) { - props.setScreen({ id: "buy-with-fiat" }); - } else { - props.setScreen({ - backScreen: { id: "main" }, - id: "select-from-token", - }); - } - }} - payerAccount={payerAccount} - payUiOptions={payOptions} - supportedDestinations={supportedDestinations} - /> - ); - } - case "direct_payment": { - return ( - { - setTokenAmount(tokenAmount); - setToChain(toChain); - setFromChain(toChain); - setFromToken(toToken); - setToToken(toToken); - if (buyWithFiatEnabled && !buyWithCryptoEnabled) { - props.setScreen({ id: "buy-with-fiat" }); - } else { - props.setScreen({ - backScreen: { id: "main" }, - id: "select-from-token", - }); - } - }} - payerAccount={payerAccount} - payUiOptions={payOptions} - supportedDestinations={supportedDestinations} - /> - ); - } - default: { - return ( - - - - - - - {/* To */} - { - props.setHasEditedAmount(true); - setTokenAmount(value); - }} - onSelectToken={props.onSelectBuyToken} - token={toToken} - value={tokenAmount} - /> - - - - {/* Continue */} - - {!payerAccount ? ( -
- -
- ) : ( - - )} -
- - {payOptions.showThirdwebBranding !== false && ( - <> - - - - )} -
- ); - } - } -} - -function TokenSelectedLayout(props: { - title: string; - children: React.ReactNode; - tokenAmount: string; - setTokenAmount: (amount: string) => void; - selectedToken: ERC20OrNativeToken; - selectedChain: Chain; - client: ThirdwebClient; - onBack: () => void; - disabled?: boolean; -}) { - return ( - - - - - - - - - - - - - {props.children} - - - ); -} - -function createSupportedTokens( - data: SupportedChainAndTokens, - payOptions: PayUIOptions, - supportedTokensOverrides?: SupportedTokens, -): SupportedTokens { - // dev override - if (supportedTokensOverrides) { - return supportedTokensOverrides; - } - - const tokens: SupportedTokens = {}; - const isBuyWithFiatDisabled = payOptions.buyWithFiat === false; - const isBuyWithCryptoDisabled = payOptions.buyWithCrypto === false; - - for (const x of data) { - tokens[x.chain.id] = x.tokens.filter((t) => { - if (t.address === ZERO_ADDRESS) { - return false; - } - // for source tokens, data is not provided, so we include all of them - if ( - t.buyWithCryptoEnabled === undefined && - t.buyWithFiatEnabled === undefined - ) { - return true; - } - // it token supports both - include it - if (t.buyWithCryptoEnabled && t.buyWithFiatEnabled) { - return true; - } - - // if buyWithFiat is disabled, and buyWithCrypto is not supported by token - exclude the token - if (!t.buyWithCryptoEnabled && isBuyWithFiatDisabled) { - return false; - } - - // if buyWithCrypto is disabled, and buyWithFiat is not supported by token - exclude the token - if (!t.buyWithFiatEnabled && isBuyWithCryptoDisabled) { - return false; - } - - return true; // include the token - }); - } - return tokens; -} - -function ChainSelectionScreen(props: { - goBack: () => void; - chains: Chain[]; - client: ThirdwebClient; - connectLocale: ConnectLocale; - setChain: (chain: Chain) => void; -}) { - return ( - { - props.setChain(renderChainProps.chain); - props.goBack(); - }} - switchingFailed={false} - /> - ); - }, - }} - onBack={props.goBack} - showTabs={false} - /> - ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/DirectPaymentModeScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/DirectPaymentModeScreen.tsx deleted file mode 100644 index b4a39f3e83d..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/DirectPaymentModeScreen.tsx +++ /dev/null @@ -1,286 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { trackPayEvent } from "../../../../../../analytics/track/pay.js"; -import type { Chain } from "../../../../../../chains/types.js"; -import type { ThirdwebClient } from "../../../../../../client/client.js"; -import { NATIVE_TOKEN_ADDRESS } from "../../../../../../constants/addresses.js"; -import { getContract } from "../../../../../../contract/contract.js"; -import { decimals } from "../../../../../../extensions/erc20/read/decimals.js"; -import { shortenAddress } from "../../../../../../utils/address.js"; -import { formatNumber } from "../../../../../../utils/formatNumber.js"; -import { toTokens } from "../../../../../../utils/units.js"; -import type { Account } from "../../../../../../wallets/interfaces/wallet.js"; -import { useCustomTheme } from "../../../../../core/design-system/CustomThemeProvider.js"; -import { iconSize, spacing } from "../../../../../core/design-system/index.js"; -import type { PayUIOptions } from "../../../../../core/hooks/connection/ConnectButtonProps.js"; -import { useChainMetadata } from "../../../../../core/hooks/others/useChainQuery.js"; -import { useActiveWallet } from "../../../../../core/hooks/wallets/useActiveWallet.js"; -import type { TokenInfo } from "../../../../../core/utils/defaultTokens.js"; -import { useEnsName } from "../../../../../core/utils/wallet.js"; -import { LoadingScreen } from "../../../../wallets/shared/LoadingScreen.js"; -import { Container, Line, ModalHeader } from "../../../components/basic.js"; -import { Button } from "../../../components/buttons.js"; -import { ChainIcon } from "../../../components/ChainIcon.js"; -import { Img } from "../../../components/Img.js"; -import { Spacer } from "../../../components/Spacer.js"; -import { TokenIcon } from "../../../components/TokenIcon.js"; -import { Text } from "../../../components/text.js"; -import { WalletImage } from "../../../components/WalletImage.js"; -import type { PayEmbedConnectOptions } from "../../../PayEmbed.js"; -import { ConnectButton } from "../../ConnectButton.js"; -import { PoweredByThirdweb } from "../../PoweredByTW.js"; -import { type ERC20OrNativeToken, isNativeToken } from "../nativeToken.js"; -import type { SupportedChainAndTokens } from "./swap/useSwapSupportedChains.js"; - -export function DirectPaymentModeScreen(props: { - client: ThirdwebClient; - payUiOptions: Extract; - supportedDestinations: SupportedChainAndTokens; - payerAccount: Account | undefined; - connectOptions: PayEmbedConnectOptions | undefined; - onContinue: ( - tokenAmount: string, - toChain: Chain, - toToken: ERC20OrNativeToken, - ) => void; -}) { - 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({ - address: paymentInfo.sellerAddress, - client, - }); - - const totalCostQuery = useQuery({ - 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: string; - if ("amountWei" in paymentInfo) { - cost = toTokens(paymentInfo.amountWei, tokenDecimals); - } else { - cost = paymentInfo.amount; - } - return cost; - }, - queryKey: ["amount", paymentInfo], - }); - - const totalCost = totalCostQuery.data; - if (!chainData || totalCost === undefined) { - return ; - } - - const token: TokenInfo = paymentInfo.token - ? { - address: paymentInfo.token.address || NATIVE_TOKEN_ADDRESS, - icon: - paymentInfo.token?.icon || - supportedDestinations - .find((c) => c.chain.id === paymentInfo.chain.id) - ?.tokens.find( - (t) => - t.address.toLowerCase() === - paymentInfo.token?.address.toLowerCase(), - )?.icon, - name: paymentInfo.token.name || chainData.nativeCurrency.name, - symbol: paymentInfo.token.symbol || chainData.nativeCurrency.symbol, - } - : { - address: NATIVE_TOKEN_ADDRESS, - icon: chainData.icon?.url, - name: chainData.nativeCurrency.name, - symbol: chainData.nativeCurrency.symbol, - }; - - return ( - - - - - - - {metadata?.image ? ( - - ) : activeWallet ? ( - - -
- - - ) : null} - - - - - Price - - - - - - - {String(formatNumber(Number(totalCost), 6))} {token.symbol} - - - - - - - - - - - Network - - - - - - - {chainData.name} - - - - - - - - - Seller - - - - - - {sellerEns || shortenAddress(paymentInfo.sellerAddress)} - - - - - - - {payerAccount ? ( - - ) : ( -
- -
- )} - - {payUiOptions.showThirdwebBranding !== false && ( - <> - - - - )} - - ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/EstimatedTimeAndFees.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/EstimatedTimeAndFees.tsx deleted file mode 100644 index d98f3bfeea7..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/EstimatedTimeAndFees.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { ClockIcon } from "@radix-ui/react-icons"; -import { - fontSize, - iconSize, - radius, -} from "../../../../../core/design-system/index.js"; -import { Container } from "../../../components/basic.js"; -import { Button } from "../../../components/buttons.js"; -import { Skeleton } from "../../../components/Skeleton.js"; -import { Text } from "../../../components/text.js"; -import type { IconFC } from "../../icons/types.js"; -import { formatSeconds } from "./swap/formatSeconds.js"; - -export function EstimatedTimeAndFees(props: { - estimatedSeconds?: number | undefined; - quoteIsLoading: boolean; - onViewFees: () => void; -}) { - const { estimatedSeconds, quoteIsLoading } = props; - - return ( - - - - {quoteIsLoading ? ( - - ) : ( - - {estimatedSeconds !== undefined - ? `~${formatSeconds(estimatedSeconds)}` - : "--"} - - )} - - - - - ); -} - -const ViewFeeIcon: IconFC = (props) => { - return ( - - ); -}; diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/LazyBuyScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/LazyBuyScreen.tsx deleted file mode 100644 index a0595b4ed60..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/LazyBuyScreen.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { lazy, Suspense } from "react"; -import { LoadingScreen } from "../../../../wallets/shared/LoadingScreen.js"; -import type { BuyScreenProps } from "./BuyScreen.js"; - -const BuyScreen = lazy(() => import("./BuyScreen.js")); - -export function LazyBuyScreen(props: BuyScreenProps) { - return ( - }> - - - ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/PayProviderSelection.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/PayProviderSelection.tsx deleted file mode 100644 index b035ed0cb04..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/PayProviderSelection.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { ChevronDownIcon } from "@radix-ui/react-icons"; -import type { FiatProvider } from "../../../../../../pay/utils/commonTypes.js"; -import { iconSize, spacing } from "../../../../../core/design-system/index.js"; -import { Container } from "../../../components/basic.js"; -import { Button } from "../../../components/buttons.js"; -import { Text } from "../../../components/text.js"; -import { getProviderLabel } from "./utils.js"; - -/** - * Shows the selected payment provider based on the preferred provider OR the quoted provider. - * @internal - */ -export const PayProviderSelection = (props: { - onShowProviders: () => void; - quotedProvider?: FiatProvider; - preferredProvider?: FiatProvider; - supportedProviders: FiatProvider[]; -}) => { - const ProviderItem = ( - - - {getProviderLabel( - props.preferredProvider ?? props.quotedProvider ?? "", - )} - - {props.supportedProviders.length > 1 && ( - - )} - - ); - - return ( - - - Provider - - {props.supportedProviders.length > 1 ? ( - - ) : ( - ProviderItem - )} - - ); -}; diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/PayTokenIcon.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/PayTokenIcon.tsx deleted file mode 100644 index f83bcc5eb94..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/PayTokenIcon.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import type { Chain } from "../../../../../../chains/types.js"; -import type { ThirdwebClient } from "../../../../../../client/client.js"; -import { getAddress } from "../../../../../../utils/address.js"; -import type { iconSize } from "../../../../../core/design-system/index.js"; -import { TokenIcon } from "../../../components/TokenIcon.js"; -import { isNativeToken, type NativeToken } from "../nativeToken.js"; -import { useBuySupportedDestinations } from "./swap/useSwapSupportedChains.js"; - -// This is a temporary solution to get the token icon working for ERC20 tokens -// TODO: The proper solutioon is to include the token icon in the quotes / status response objects, currently it is missing. - -export function PayTokenIcon(props: { - token: - | { - address: string; - icon?: string; - } - | NativeToken; - chain: Chain; - client: ThirdwebClient; - size: keyof typeof iconSize; -}) { - const supportedDestinationsQuery = useBuySupportedDestinations(props.client); - const token = props.token; - const tokenIcon = !isNativeToken(token) - ? supportedDestinationsQuery.data - ?.find((c) => c.chain.id === props.chain.id) - ?.tokens.find( - (t) => getAddress(t.address) === getAddress(token.address), - )?.icon - : undefined; - - return ( - - ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/PayWIthCreditCard.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/PayWIthCreditCard.tsx deleted file mode 100644 index bf5e3640a28..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/PayWIthCreditCard.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import styled from "@emotion/styled"; -import { ChevronDownIcon } from "@radix-ui/react-icons"; -import type { ThirdwebClient } from "../../../../../../client/client.js"; -import { formatNumber } from "../../../../../../utils/formatNumber.js"; -import { - fontSize, - iconSize, - radius, - spacing, -} from "../../../../../core/design-system/index.js"; -import { Container } from "../../../components/basic.js"; -import { Button } from "../../../components/buttons.js"; -import { Skeleton } from "../../../components/Skeleton.js"; -import { Text } from "../../../components/text.js"; -import { type CurrencyMeta, getFiatIcon } from "./fiat/currencies.js"; - -/** - * 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 - */ -export function PayWithCreditCard(props: { - value?: string; - isLoading: boolean; - client: ThirdwebClient; - currency: CurrencyMeta; - onSelectCurrency: () => void; -}) { - return ( - - {/* Left */} - - {getFiatIcon(props.currency, "md")} - - {props.currency.shorthand} - - - - - {/* Right */} -
- {props.isLoading ? ( - - ) : ( - - {props.value - ? `${props.currency.symbol}${formatNumber( - Number(props.value), - 2, - )}` - : "--"} - - )} -
-
- ); -} - -const CurrencyButton = /* @__PURE__ */ styled(Button)(() => { - return { - "&[disabled]:hover": { - borderColor: "transparent", - }, - }; -}); diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/Stepper.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/Stepper.tsx deleted file mode 100644 index 6f866e463b2..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/Stepper.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { keyframes } from "@emotion/react"; -import { CheckIcon } from "@radix-ui/react-icons"; -import { fontSize, iconSize } from "../../../../../core/design-system/index.js"; -import { Container } from "../../../components/basic.js"; -import { StyledDiv } from "../../../design-system/elements.js"; - -export function StepIcon(props: { isDone: boolean; isActive: boolean }) { - return ( - - - {props.isDone ? ( - - ) : ( - - )} - - - ); -} - -export function Step(props: { - isDone: boolean; - label: string; - isActive: boolean; -}) { - return ( - - - {props.label} - - ); -} - -const pulseAnimation = keyframes` -0% { - opacity: 1; - transform: scale(0.5); -} -100% { - opacity: 0; - transform: scale(1.5); -} -`; - -const PulsingDot = /* @__PURE__ */ StyledDiv(() => { - return { - '&[data-active="true"]': { - animation: `${pulseAnimation} 1s infinite`, - }, - background: "currentColor", - borderRadius: "50%", - height: "10px", - width: "10px", - }; -}); - -const Circle = /* @__PURE__ */ StyledDiv(() => { - return { - alignItems: "center", - border: "1px solid currentColor", - borderRadius: "50%", - display: "flex", - height: "20px", - justifyContent: "center", - width: "20px", - }; -}); diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx deleted file mode 100644 index 5f9f0b253e9..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx +++ /dev/null @@ -1,395 +0,0 @@ -import { trackPayEvent } from "../../../../../../analytics/track/pay.js"; -import type { Chain } from "../../../../../../chains/types.js"; -import type { ThirdwebClient } from "../../../../../../client/client.js"; -import { NATIVE_TOKEN_ADDRESS } from "../../../../../../constants/addresses.js"; -import { formatNumber } from "../../../../../../utils/formatNumber.js"; -import { toTokens } from "../../../../../../utils/units.js"; -import type { Account } from "../../../../../../wallets/interfaces/wallet.js"; -import { hasSponsoredTransactionsEnabled } from "../../../../../../wallets/smart/is-smart-wallet.js"; -import { useCustomTheme } from "../../../../../core/design-system/CustomThemeProvider.js"; -import { - fontSize, - iconSize, - spacing, -} from "../../../../../core/design-system/index.js"; -import type { PayUIOptions } from "../../../../../core/hooks/connection/ConnectButtonProps.js"; -import { useChainMetadata } from "../../../../../core/hooks/others/useChainQuery.js"; -import { useWalletBalance } from "../../../../../core/hooks/others/useWalletBalance.js"; -import { useActiveAccount } from "../../../../../core/hooks/wallets/useActiveAccount.js"; -import { useActiveWallet } from "../../../../../core/hooks/wallets/useActiveWallet.js"; -import { ErrorState } from "../../../../wallets/shared/ErrorState.js"; -import { LoadingScreen } from "../../../../wallets/shared/LoadingScreen.js"; -import { Container, Line, ModalHeader } from "../../../components/basic.js"; -import { Button } from "../../../components/buttons.js"; -import { ChainIcon } from "../../../components/ChainIcon.js"; -import { Img } from "../../../components/Img.js"; -import { Skeleton } from "../../../components/Skeleton.js"; -import { Spacer } from "../../../components/Spacer.js"; -import { TokenIcon } from "../../../components/TokenIcon.js"; -import { Text } from "../../../components/text.js"; -import { TokenSymbol } from "../../../components/token/TokenSymbol.js"; -import type { PayEmbedConnectOptions } from "../../../PayEmbed.js"; -import { ConnectButton } from "../../ConnectButton.js"; -import { OutlineWalletIcon } from "../../icons/OutlineWalletIcon.js"; -import { PoweredByThirdweb } from "../../PoweredByTW.js"; -import { formatTokenBalance } from "../formatTokenBalance.js"; -import { - type ERC20OrNativeToken, - isNativeToken, - NATIVE_TOKEN, -} from "../nativeToken.js"; -import { useTransactionCostAndData } from "./main/useBuyTxStates.js"; -import type { SupportedChainAndTokens } from "./swap/useSwapSupportedChains.js"; -import { WalletRow } from "./swap/WalletRow.js"; - -export function TransactionModeScreen(props: { - client: ThirdwebClient; - payUiOptions: Extract; - supportedDestinations: SupportedChainAndTokens; - payerAccount: Account | undefined; - connectOptions: PayEmbedConnectOptions | undefined; - onContinue: ( - tokenAmount: string, - toChain: Chain, - toToken: ERC20OrNativeToken, - ) => void; -}) { - 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({ - account: payerAccount, - supportedDestinations, - transaction: payUiOptions.transaction, - }); - const theme = useCustomTheme(); - const activeWallet = useActiveWallet(); - const activeAccount = useActiveAccount(); - const sponsoredTransactionsEnabled = - hasSponsoredTransactionsEnabled(activeWallet); - const balanceQuery = useWalletBalance( - { - address: activeAccount?.address, - chain: payUiOptions.transaction.chain, - client: props.client, - tokenAddress: isNativeToken(transactionCostAndData?.token || NATIVE_TOKEN) - ? undefined - : transactionCostAndData?.token.address, - }, - { - enabled: !!transactionCostAndData, - }, - ); - - if (transactionCostAndDataLoading || chainDataLoading) { - return ; - } - - if (!activeAccount) { - return ( - - - - - - - - - Please connect a wallet to continue - - - - - - - - ); - } - - if (transactionCostAndDataError || chainDataError) { - return ( - - - - ); - } - - if (!transactionCostAndData || !chainData) { - return ; - } - - const insufficientFunds = - balanceQuery.data && - balanceQuery.data.value < transactionCostAndData.transactionValueWei; - - return ( - - - - - - - {metadata?.image ? ( - - ) : activeAccount ? ( - - {insufficientFunds && ( -
- - Insufficient Funds - - - Select another token or pay with card. - -
- )} - - - {balanceQuery.data ? ( - - - {formatTokenBalance(balanceQuery.data, false)} - - - - ) : ( - - )} - -
- ) : null} - - - - - Price - - - - - - - {String( - formatNumber( - Number( - toTokens( - transactionCostAndData.transactionValueWei, - transactionCostAndData.decimals, - ), - ), - 6, - ), - )}{" "} - {transactionCostAndData.token.symbol} - - - - - - - - - - - Gas Fees - - - - - - {sponsoredTransactionsEnabled - ? "Sponsored" - : `${String( - formatNumber( - Number( - toTokens( - transactionCostAndData.gasCostWei, - chainData.nativeCurrency.decimals, - ), - ), - 6, - ), - )} ${chainData.nativeCurrency.symbol}`} - - - - - - - - - Network - - - - - - - {chainData.name} - - - - -
- - {payerAccount ? ( - - ) : ( -
- -
- )} - - {payUiOptions.showThirdwebBranding !== false && ( - <> - - - - )} -
- ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/CurrencySelection.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/CurrencySelection.tsx deleted file mode 100644 index e0ff6f22b59..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/CurrencySelection.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import styled from "@emotion/styled"; -import { useCustomTheme } from "../../../../../../core/design-system/CustomThemeProvider.js"; -import { spacing } from "../../../../../../core/design-system/index.js"; -import { Container, Line, ModalHeader } from "../../../../components/basic.js"; -import { Button } from "../../../../components/buttons.js"; -import { Spacer } from "../../../../components/Spacer.js"; -import { Text } from "../../../../components/text.js"; -import { type CurrencyMeta, currencies, getFiatIcon } from "./currencies.js"; - -export function CurrencySelection(props: { - onSelect: (currency: CurrencyMeta) => void; - onBack: () => void; -}) { - return ( - - - - - - - - - - {currencies.map((c) => { - return ( - props.onSelect(c)} - variant="secondary" - > - {getFiatIcon(c, "lg")} - - {c.shorthand} - {c.name} - - - ); - })} - - - - - ); -} - -const SelectCurrencyButton = /* @__PURE__ */ styled(Button)(() => { - const theme = useCustomTheme(); - return { - "&:hover": { - background: theme.colors.secondaryButtonBg, - transform: "scale(1.01)", - }, - background: theme.colors.tertiaryBg, - gap: spacing.sm, - justifyContent: "flex-start", - padding: spacing.sm, - transition: "background 200ms ease, transform 150ms ease", - }; -}); diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/FiatScreenContent.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/FiatScreenContent.tsx deleted file mode 100644 index b187e2961ee..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/FiatScreenContent.tsx +++ /dev/null @@ -1,263 +0,0 @@ -import { useState } from "react"; -import { trackPayEvent } from "../../../../../../../analytics/track/pay.js"; -import type { Chain } from "../../../../../../../chains/types.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import { NATIVE_TOKEN_ADDRESS } from "../../../../../../../constants/addresses.js"; -import { - type FiatProvider, - FiatProviders, -} from "../../../../../../../pay/utils/commonTypes.js"; -import type { Theme } from "../../../../../../core/design-system/index.js"; -import type { PayUIOptions } from "../../../../../../core/hooks/connection/ConnectButtonProps.js"; -import { useBuyWithFiatQuote } from "../../../../../../core/hooks/pay/useBuyWithFiatQuote.js"; -import { PREFERRED_FIAT_PROVIDER_STORAGE_KEY } from "../../../../../../core/utils/storage.js"; -import { getErrorMessage } from "../../../../../utils/errors.js"; -import { Container } from "../../../../components/basic.js"; -import { Button } from "../../../../components/buttons.js"; -import { - Drawer, - DrawerOverlay, - useDrawer, -} from "../../../../components/Drawer.js"; -import { Spacer } from "../../../../components/Spacer.js"; -import { Spinner } from "../../../../components/Spinner.js"; -import { Text } from "../../../../components/text.js"; -import { type ERC20OrNativeToken, isNativeToken } from "../../nativeToken.js"; -import { EstimatedTimeAndFees } from "../EstimatedTimeAndFees.js"; -import type { SelectedScreen } from "../main/types.js"; -import { PayProviderSelection } from "../PayProviderSelection.js"; -import { PayWithCreditCard } from "../PayWIthCreditCard.js"; -import { FiatFees } from "../swap/Fees.js"; -import type { PayerInfo } from "../types.js"; -import type { CurrencyMeta } from "./currencies.js"; -import { Providers } from "./Providers.js"; - -export function FiatScreenContent(props: { - setScreen: (screen: SelectedScreen) => void; - tokenAmount: string; - toToken: ERC20OrNativeToken; - toChain: Chain; - selectedCurrency: CurrencyMeta; - showCurrencySelector: () => void; - payOptions: PayUIOptions; - theme: "light" | "dark" | Theme; - client: ThirdwebClient; - onDone: () => void; - isEmbed: boolean; - payer: PayerInfo; - setTokenAmount: (amount: string) => void; - setHasEditedAmount: (hasEdited: boolean) => void; - paymentLinkId: undefined | string; -}) { - const { - toToken, - tokenAmount, - payer, - client, - setScreen, - toChain, - showCurrencySelector, - selectedCurrency, - paymentLinkId, - } = props; - const defaultRecipientAddress = ( - props.payOptions as Extract - )?.paymentInfo?.sellerAddress; - const receiverAddress = - defaultRecipientAddress || props.payer.account.address; - const { drawerRef, drawerOverlayRef, isOpen, setIsOpen } = useDrawer(); - const [drawerScreen, setDrawerScreen] = useState<"fees" | "providers">( - "fees", - ); - - const buyWithFiatOptions = props.payOptions.buyWithFiat; - const [preferredProvider, setPreferredProvider] = useState< - FiatProvider | undefined - >( - buyWithFiatOptions !== false - ? buyWithFiatOptions?.preferredProvider || - ((localStorage.getItem( - PREFERRED_FIAT_PROVIDER_STORAGE_KEY, - ) as FiatProvider | null) ?? - undefined) - : undefined, - ); - - const supportedProviders = (() => { - if (!buyWithFiatOptions) return [...FiatProviders]; - const options = buyWithFiatOptions?.supportedProviders ?? []; - const optionsWithPreferred = - options.length > 0 - ? new Set([ - ...(preferredProvider ? [preferredProvider] : []), - ...options, - ]) - : FiatProviders; - return Array.from(optionsWithPreferred); - })(); - - const fiatQuoteQuery = useBuyWithFiatQuote( - buyWithFiatOptions !== false && tokenAmount - ? { - client, - fromAddress: payer.account.address, - fromCurrencySymbol: selectedCurrency.shorthand, - isTestMode: buyWithFiatOptions?.testMode, - onrampChainId: buyWithFiatOptions?.onrampChainId, - onrampTokenAddress: buyWithFiatOptions?.onrampTokenAddress, - paymentLinkId: paymentLinkId, - preferredProvider: preferredProvider ?? supportedProviders[0], - purchaseData: props.payOptions.purchaseData, - toAddress: receiverAddress, - toAmount: tokenAmount, - toChainId: toChain.id, - toTokenAddress: isNativeToken(toToken) - ? NATIVE_TOKEN_ADDRESS - : toToken.address, - } - : 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 ( - - {isOpen && ( - <> - - setIsOpen(false)} ref={drawerRef}> - {drawerScreen === "fees" && fiatQuoteQuery.data && ( -
- - Fees - - - - -
- )} - {drawerScreen === "providers" && ( -
- - Providers - - - { - setPreferredProvider(provider); - // save the pref in local storage - localStorage.setItem( - PREFERRED_FIAT_PROVIDER_STORAGE_KEY, - provider, - ); - setIsOpen(false); - }} - preferredProvider={ - preferredProvider || fiatQuoteQuery.data?.provider - } - supportedProviders={supportedProviders} - /> -
- )} -
- - )} - - - Pay with card -
- - {/** Shows preferred or quoted provider and conditional selection */} - - {/* Estimated time + View fees button */} - -
- - {/* Error message */} - {errorMsg && ( -
- - {errorMsg.title} - - - {errorMsg.message} - -
- )} -
- - -
- ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/FiatSteps.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/FiatSteps.tsx deleted file mode 100644 index fc55f96db73..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/FiatSteps.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Cross1Icon } from "@radix-ui/react-icons"; -import { - iconSize, - radius, - spacing, - type Theme, -} from "../../../../../../core/design-system/index.js"; -import { Container } from "../../../../components/basic.js"; -import { Spinner } from "../../../../components/Spinner.js"; -import { Text } from "../../../../components/text.js"; -import type { FiatStatusMeta } from "../pay-transactions/statusMeta.js"; -import { StepIcon } from "../Stepper.js"; - -export function StepContainer(props: { - state?: FiatStatusMeta["progressStatus"]; - children: React.ReactNode; - style?: React.CSSProperties; - index?: number; -}) { - let color: keyof Theme["colors"] = "borderColor"; - let text: string | undefined; - - if (props.state === "pending") { - text = "Pending"; - color = "accentText"; - } else if (props.state === "actionRequired") { - color = "accentText"; - } else if (props.state === "completed") { - text = "Completed"; - color = "success"; - } else if (props.state === "failed") { - color = "danger"; - text = "Failed"; - } - - return ( - - {props.children} -
- {props.state && text && ( - - {text} - - )} - - {(props.state === "actionRequired" || props.state === "completed") && ( - - )} - - {props.state === "pending" && } - - {props.state === "failed" && ( - - - - )} -
-
- ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/OnRampScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/OnRampScreen.tsx deleted file mode 100644 index 514a5eaaa45..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/OnRampScreen.tsx +++ /dev/null @@ -1,775 +0,0 @@ -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { trackPayEvent } from "../../../../../../../analytics/track/pay.js"; -import { getCachedChain } from "../../../../../../../chains/utils.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import { getContract } from "../../../../../../../contract/contract.js"; -import { allowance } from "../../../../../../../extensions/erc20/__generated__/IERC20/read/allowance.js"; -import { approve } from "../../../../../../../extensions/erc20/write/approve.js"; -import { getBuyWithCryptoQuote } from "../../../../../../../pay/buyWithCrypto/getQuote.js"; -import type { BuyWithCryptoStatus } from "../../../../../../../pay/buyWithCrypto/getStatus.js"; -import type { BuyWithFiatQuote } from "../../../../../../../pay/buyWithFiat/getQuote.js"; -import { - type BuyWithFiatStatus, - getBuyWithFiatStatus, -} from "../../../../../../../pay/buyWithFiat/getStatus.js"; -import { - getOnRampSteps, - type OnRampStep, -} from "../../../../../../../pay/buyWithFiat/isSwapRequiredPostOnramp.js"; -import type { PayTokenInfo } from "../../../../../../../pay/utils/commonTypes.js"; -import { sendBatchTransaction } from "../../../../../../../transaction/actions/send-batch-transaction.js"; -import { sendTransaction } from "../../../../../../../transaction/actions/send-transaction.js"; -import type { WaitForReceiptOptions } from "../../../../../../../transaction/actions/wait-for-tx-receipt.js"; -import { waitForReceipt } from "../../../../../../../transaction/actions/wait-for-tx-receipt.js"; -import { formatNumber } from "../../../../../../../utils/formatNumber.js"; -import { isInAppSigner } from "../../../../../../../wallets/in-app/core/wallet/is-in-app-signer.js"; -import { spacing } from "../../../../../../core/design-system/index.js"; -import { useChainName } from "../../../../../../core/hooks/others/useChainQuery.js"; -import { useBuyWithCryptoStatus } from "../../../../../../core/hooks/pay/useBuyWithCryptoStatus.js"; -import { useBuyWithFiatStatus } from "../../../../../../core/hooks/pay/useBuyWithFiatStatus.js"; -import { useConnectedWallets } from "../../../../../../core/hooks/wallets/useConnectedWallets.js"; -import { invalidateWalletBalance } from "../../../../../../core/providers/invalidateWalletBalance.js"; -import { Container, ModalHeader } from "../../../../components/basic.js"; -import { Button } from "../../../../components/buttons.js"; -import { Spacer } from "../../../../components/Spacer.js"; -import { Spinner } from "../../../../components/Spinner.js"; -import { SwitchNetworkButton } from "../../../../components/SwitchNetwork.js"; -import { Text } from "../../../../components/text.js"; -import { TokenSymbol } from "../../../../components/token/TokenSymbol.js"; -import { openOnrampPopup } from "../openOnRamppopup.js"; -import { PayTokenIcon } from "../PayTokenIcon.js"; -import type { FiatStatusMeta } from "../pay-transactions/statusMeta.js"; -import { addPendingTx } from "../swap/pendingSwapTx.js"; -import { StepConnectorArrow } from "../swap/StepConnector.js"; -import { WalletRow } from "../swap/WalletRow.js"; -import type { PayerInfo } from "../types.js"; -import { getProviderLabel } from "../utils.js"; -import { StepContainer } from "./FiatSteps.js"; - -type OnRampScreenState = { - steps: Array<{ - index: number; - step: OnRampStep; - status: FiatStatusMeta["progressStatus"]; - }>; - handleContinue: () => void; - isLoading: boolean; - isDone: boolean; - isFailed: boolean; -}; - -export function OnRampScreen(props: { - title: string; - quote: BuyWithFiatQuote; - onBack: () => void; - client: ThirdwebClient; - testMode: boolean; - theme: "light" | "dark"; - onDone: () => void; - transactionMode: boolean; - isEmbed: boolean; - payer: PayerInfo; - onSuccess: (status: BuyWithFiatStatus) => void; - receiverAddress: string; - paymentLinkId?: string; -}) { - const connectedWallets = useConnectedWallets(); - const isAutoMode = isInAppSigner({ - connectedWallets, - wallet: props.payer.wallet, - }); - const state = useOnRampScreenState({ - client: props.client, - isAutoMode, - onDone: props.onDone, - onSuccess: props.onSuccess, - payer: props.payer, - paymentLinkId: props.paymentLinkId, - quote: props.quote, - theme: props.theme, - }); - const firstStepChainId = state.steps[0]?.step.token.chainId; - return ( - - - - - - - - - {state.steps.map(({ step, status }, index) => ( - - - - - {index < state.steps.length - 1 && } - - ))} - - - - - - Keep this window open until all transactions are complete. - - - - - - {!state.isDone && - firstStepChainId && - firstStepChainId !== props.payer.chain.id ? ( - { - await props.payer.wallet.switchChain( - getCachedChain(firstStepChainId), - ); - }} - variant="accent" - /> - ) : ( - - )} - - - ); -} - -function StepUI(props: { - step: OnRampStep; - index: number; - client: ThirdwebClient; - payer: PayerInfo; -}) { - const { step, client } = props; - const chain = useChainName(getCachedChain(step.token.chainId)); - return ( - - - - - - {getProviderLabel(step.action)} - - - - - - {formatNumber(Number(step.amount), 5)} - - - - - - {chain.name} - - - - - - - ); -} - -function useOnRampScreenState(props: { - quote: BuyWithFiatQuote; - client: ThirdwebClient; - onSuccess: (status: BuyWithFiatStatus) => void; - onDone: () => void; - payer: PayerInfo; - theme: "light" | "dark"; - isAutoMode?: boolean; - paymentLinkId?: string; -}): OnRampScreenState { - const onRampSteps = getOnRampSteps(props.quote); - const [currentStepIndex, setCurrentStepIndex] = useState(0); - const [swapTxHash, setSwapTxHash] = useState<{ - hash: string; - chainId: number; - }>(); - const [popupWindow, setPopupWindow] = useState(null); - - // Track onramp status - const { uiStatus: fiatOnrampStatus } = useOnRampStatus({ - client: props.client, - intentId: props.quote.intentId, - onSuccess: (status) => { - if (onRampSteps.length === 1) { - // If only one step, this is the final success - props.onSuccess(status); - } else { - // Move to next step (swap) - setCurrentStepIndex((prev) => prev + 1); - } - }, - openedWindow: popupWindow, - }); - - // Get quote for current swap/bridge step if needed - const previousStep = onRampSteps[currentStepIndex - 1]; - const currentStep = onRampSteps[currentStepIndex]; - - // Handle swap execution - const swapMutation = useSwapMutation({ - client: props.client, - isFiatFlow: true, - payer: props.payer, - paymentLinkId: props.paymentLinkId, - }); - - // Track swap status - const { uiStatus: swapStatus } = useSwapStatus({ - chainId: swapTxHash?.chainId, - client: props.client, - onSuccess: () => { - if (currentStepIndex === onRampSteps.length - 1) { - // Last step completed - call final success - getBuyWithFiatStatus({ - client: props.client, - intentId: props.quote.intentId, - }).then(props.onSuccess); - } else { - // Reset swap state before moving to next step - setSwapTxHash(undefined); - swapMutation.reset(); - // Move to next step - setCurrentStepIndex((prev) => prev + 1); - } - }, - transactionHash: swapTxHash?.hash, - }); - - // Map steps to their current status - const steps = onRampSteps.map((step, index) => { - let status: FiatStatusMeta["progressStatus"] = "unknown"; - - if (index === 0) { - // First step (onramp) status - status = fiatOnrampStatus; - } else if (index < currentStepIndex) { - // Previous steps are completed - status = "completed"; - } else if (index === currentStepIndex) { - // Current step - could be swap or bridge - if (swapMutation.isPending) { - status = "pending"; - } else if (swapMutation.error) { - status = "failed"; - } else if (swapTxHash) { - status = swapStatus; - } else { - status = "actionRequired"; - } - } - - return { - index, - status, - step, - }; - }); - - const isLoading = steps.some((step) => step.status === "pending"); - const isDone = steps.every((step) => step.status === "completed"); - const isFailed = steps.some((step) => step.status === "failed"); - - // Update handleContinue to handle done state - const handleContinue = useCallback(async () => { - if (isDone) { - props.onDone(); - return; - } - - if (currentStepIndex === 0) { - // First step - open onramp popup - const popup = openOnrampPopup(props.quote.onRampLink, props.theme); - trackPayEvent({ - amountWei: props.quote.onRampToken.amountWei, - client: props.client, - event: "open_onramp_popup", - toChainId: props.quote.onRampToken.token.chainId, - toToken: props.quote.onRampToken.token.tokenAddress, - walletAddress: props.payer.account.address, - walletType: props.payer.wallet.id, - }); - setPopupWindow(popup); - addPendingTx({ - intentId: props.quote.intentId, - type: "fiat", - }); - } else if (previousStep && currentStep && !swapTxHash) { - // Execute swap/bridge - try { - const result = await swapMutation.mutateAsync({ - amount: currentStep.amount, - fromToken: previousStep.token, - toToken: currentStep.token, - }); - setSwapTxHash({ - chainId: result.chainId, - hash: result.transactionHash, - }); - } catch (e) { - console.error("Failed to execute swap:", e); - } - } else if (isFailed) { - // retry the quote step - setSwapTxHash(undefined); - swapMutation.reset(); - } - }, [ - isDone, - currentStepIndex, - swapTxHash, - props.quote, - props.onDone, - swapMutation, - props.theme, - isFailed, - swapMutation.reset, - props.client, - props.payer.account.address, - props.payer.wallet.id, - currentStep, - previousStep, - ]); - - // Auto-progress effect - useEffect(() => { - if (!props.isAutoMode) { - return; - } - - // Auto-start next swap step when previous step completes - if ( - !isLoading && - !isDone && - !isFailed && - currentStepIndex > 0 && - currentStepIndex < onRampSteps.length && - !swapTxHash - ) { - handleContinue(); - } - }, [ - props.isAutoMode, - currentStepIndex, - swapTxHash, - onRampSteps.length, - handleContinue, - isDone, - isFailed, - isLoading, - ]); - - return { - handleContinue, - isDone, - isFailed, - isLoading, - steps, - }; -} - -function useOnRampStatus(props: { - intentId: string; - client: ThirdwebClient; - onSuccess: (status: BuyWithFiatStatus) => void; - openedWindow: Window | null; -}) { - const queryClient = useQueryClient(); - const statusQuery = useBuyWithFiatStatus({ - client: props.client, - intentId: props.intentId, - queryOptions: { - enabled: !!props.openedWindow, - }, - }); - let uiStatus: FiatStatusMeta["progressStatus"] = "actionRequired"; - - switch (statusQuery.data?.status) { - case "ON_RAMP_TRANSFER_COMPLETED": - uiStatus = "completed"; - break; - case "PAYMENT_FAILED": - uiStatus = "failed"; - break; - case "PENDING_PAYMENT": - uiStatus = "pending"; - break; - default: - uiStatus = "actionRequired"; - break; - } - - const purchaseCbCalled = useRef(false); - useEffect(() => { - if (purchaseCbCalled.current || !props.onSuccess) { - return; - } - - if (statusQuery.data && uiStatus === "completed") { - purchaseCbCalled.current = true; - props.onSuccess(statusQuery.data); - } - }, [props.onSuccess, statusQuery.data, uiStatus]); - - // close the onramp popup if onramp is completed - useEffect(() => { - if (!props.openedWindow) { - return; - } - - if (uiStatus === "completed") { - try { - if (props.openedWindow && !props.openedWindow.closed) { - props.openedWindow.close(); - } - } catch (e) { - console.warn("Failed to close payment window:", e); - } - } - }, [props.openedWindow, uiStatus]); - - // invalidate wallet balance when onramp is completed - const invalidatedBalance = useRef(false); - useEffect(() => { - if (!invalidatedBalance.current && uiStatus === "completed") { - invalidatedBalance.current = true; - invalidateWalletBalance(queryClient); - } - }, [uiStatus, queryClient]); - - return { uiStatus }; -} - -function useSwapStatus(props: { - client: ThirdwebClient; - transactionHash?: string; - chainId?: number; - onSuccess: (status: BuyWithCryptoStatus) => void; -}) { - const swapStatus = useBuyWithCryptoStatus( - props.transactionHash && props.chainId - ? { - chainId: props.chainId, - client: props.client, - transactionHash: props.transactionHash, - } - : undefined, - ); - - let uiStatus: FiatStatusMeta["progressStatus"] = "unknown"; - - switch (swapStatus.data?.status) { - case "COMPLETED": - uiStatus = "completed"; - break; - case "FAILED": - uiStatus = "failed"; - break; - case "PENDING": - case "NOT_FOUND": - uiStatus = "pending"; - break; - case "NONE": - uiStatus = "unknown"; - break; - default: - uiStatus = "unknown"; - break; - } - - const purchaseCbCalled = useRef(false); - useEffect(() => { - if (purchaseCbCalled.current || !props.onSuccess) { - return; - } - - if (swapStatus.data?.status === "COMPLETED") { - purchaseCbCalled.current = true; - props.onSuccess(swapStatus.data); - } - }, [props.onSuccess, swapStatus]); - - const queryClient = useQueryClient(); - const balanceInvalidated = useRef(false); - useEffect(() => { - if (uiStatus === "completed" && !balanceInvalidated.current) { - balanceInvalidated.current = true; - invalidateWalletBalance(queryClient); - } - }, [queryClient, uiStatus]); - - return { uiStatus }; -} - -function useSwapMutation(props: { - client: ThirdwebClient; - payer: PayerInfo; - isFiatFlow: boolean; - paymentLinkId?: string; -}) { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: async (input: { - fromToken: PayTokenInfo; - toToken: PayTokenInfo; - amount: string; - }) => { - const { fromToken, toToken, amount } = input; - const wallet = props.payer.wallet; - - // in case the wallet is not on the same chain as the fromToken, switch to it - if (wallet.getChain()?.id !== fromToken.chainId) { - await wallet.switchChain(getCachedChain(fromToken.chainId)); - } - - const account = wallet.getAccount(); - - if (!account) { - throw new Error("Payer wallet has no account"); - } - - // always get a fresh quote before executing - const quote = await getBuyWithCryptoQuote({ - client: props.client, - fromAddress: account.address, - fromChainId: fromToken.chainId, - fromTokenAddress: fromToken.tokenAddress, - paymentLinkId: props.paymentLinkId, - toAddress: account.address, - toAmount: amount, - toChainId: toToken.chainId, - toTokenAddress: toToken.tokenAddress, - }); - - const canBatch = account.sendBatchTransaction; - const tokenContract = getContract({ - address: quote.swapDetails.fromToken.tokenAddress, - chain: getCachedChain(quote.swapDetails.fromToken.chainId), - client: props.client, - }); - const approveTxRequired = - quote.approvalData && - (await allowance({ - contract: tokenContract, - owner: account.address, - spender: quote.approvalData.spenderAddress, - })) < BigInt(quote.approvalData.amountWei); - if (approveTxRequired && quote.approvalData && !canBatch) { - trackPayEvent({ - amountWei: quote.swapDetails.fromAmountWei, - chainId: quote.swapDetails.fromToken.chainId, - client: props.client, - event: "prompt_swap_approval", - fromToken: quote.swapDetails.fromToken.tokenAddress, - toChainId: quote.swapDetails.toToken.chainId, - toToken: quote.swapDetails.toToken.tokenAddress, - walletAddress: account.address, - walletType: props.payer.wallet.id, - }); - - const transaction = approve({ - amountWei: BigInt(quote.approvalData.amountWei), - contract: tokenContract, - spender: quote.approvalData.spenderAddress, - }); - - const tx = await sendTransaction({ - account, - transaction, - }); - - await waitForReceipt({ ...tx, maxBlocksWaitTime: 50 }); - - trackPayEvent({ - amountWei: quote.swapDetails.fromAmountWei, - chainId: quote.swapDetails.fromToken.chainId, - client: props.client, - event: "swap_approval_success", - fromToken: quote.swapDetails.fromToken.tokenAddress, - toChainId: quote.swapDetails.toToken.chainId, - toToken: quote.swapDetails.toToken.tokenAddress, - walletAddress: account.address, - walletType: props.payer.wallet.id, - }); - } - - trackPayEvent({ - amountWei: quote.swapDetails.fromAmountWei, - chainId: quote.swapDetails.fromToken.chainId, - client: props.client, - event: "prompt_swap_execution", - fromToken: quote.swapDetails.fromToken.tokenAddress, - toChainId: quote.swapDetails.toToken.chainId, - toToken: quote.swapDetails.toToken.tokenAddress, - walletAddress: account.address, - walletType: props.payer.wallet.id, - }); - const tx = quote.transactionRequest; - let _swapTx: WaitForReceiptOptions; - // check if we can batch approval and swap - if (canBatch && quote.approvalData && approveTxRequired) { - const approveTx = approve({ - amountWei: BigInt(quote.approvalData.amountWei), - contract: tokenContract, - spender: quote.approvalData.spenderAddress, - }); - - _swapTx = await sendBatchTransaction({ - account, - transactions: [approveTx, tx], - }); - } else { - _swapTx = await sendTransaction({ - account, - transaction: tx, - }); - } - - await waitForReceipt({ ..._swapTx, maxBlocksWaitTime: 50 }); - - trackPayEvent({ - amountWei: quote.swapDetails.fromAmountWei, - chainId: quote.swapDetails.fromToken.chainId, - client: props.client, - event: "swap_execution_success", - fromToken: quote.swapDetails.fromToken.tokenAddress, - toChainId: quote.swapDetails.toToken.chainId, - toToken: quote.swapDetails.toToken.tokenAddress, - walletAddress: account.address, - walletType: props.payer.wallet.id, - }); - - // do not add pending tx if the swap is part of fiat flow - if (!props.isFiatFlow) { - addPendingTx({ - chainId: _swapTx.chain.id, - txHash: _swapTx.transactionHash, - type: "swap", - }); - } - - return { - chainId: _swapTx.chain.id, - transactionHash: _swapTx.transactionHash, - }; - }, - onSuccess: () => { - invalidateWalletBalance(queryClient); - }, - }); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/Providers.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/Providers.tsx deleted file mode 100644 index 795f82d1191..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/Providers.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import type { FiatProvider } from "../../../../../../../pay/utils/commonTypes.js"; -import { Container } from "../../../../components/basic.js"; -import { Button } from "../../../../components/buttons.js"; -import { Link } from "../../../../components/text.js"; -import { getProviderLabel } from "../utils.js"; -/** - * @internal - */ - -export function Providers(props: { - supportedProviders: FiatProvider[]; - preferredProvider?: FiatProvider; - onSelect: (provider: FiatProvider) => void; -}) { - return ( - - {props.supportedProviders.map((provider) => ( - - - - ))} - - ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/currencies.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/currencies.tsx index e5ffa67c5ae..48611f9d8fc 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/currencies.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/currencies.tsx @@ -84,7 +84,7 @@ export function getCurrencyMeta(shorthand: string): CurrencyMeta { ); } -export function getFiatIcon( +function getFiatIcon( currency: CurrencyMeta, size: keyof typeof iconSize, ): React.ReactNode { diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/types.ts b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/types.ts index 4677ef7f58f..cb36d8615c2 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/types.ts +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/types.ts @@ -1,6 +1,4 @@ import type { ChainMetadata } from "../../../../../../../chains/types.js"; -import type { BuyWithCryptoQuote } from "../../../../../../../pay/buyWithCrypto/getQuote.js"; -import type { BuyWithFiatQuote } from "../../../../../../../pay/buyWithFiat/getQuote.js"; import type { GetWalletBalanceResult } from "../../../../../../../wallets/utils/getWalletBalance.js"; import type { TokenInfo } from "../../../../../../core/utils/defaultTokens.js"; @@ -12,40 +10,3 @@ export type TransactionCostAndData = { gasCostWei: bigint; chainMetadata: ChainMetadata; }; - -export type SelectedScreen = - | { - id: "main" | "select-payment-method" | "buy-with-fiat"; - } - | { - id: "buy-with-crypto"; - payDisabled?: boolean; - } - | { - id: "select-from-token"; - backScreen: SelectedScreen; - } - | { - id: "select-to-token"; - backScreen: SelectedScreen; - } - | { - id: "select-currency"; - backScreen: SelectedScreen; - } - | { - id: "swap-flow"; - quote: BuyWithCryptoQuote; - approvalAmount?: bigint; - } - | { - id: "fiat-flow"; - quote: BuyWithFiatQuote; - } - | { - id: "transfer-flow"; - } - | { - id: "connect-payer-wallet"; - backScreen: SelectedScreen; - }; diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/useEnabledPaymentMethods.ts b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/useEnabledPaymentMethods.ts deleted file mode 100644 index 5472b2a2c3e..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/useEnabledPaymentMethods.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { Chain } from "../../../../../../../chains/types.js"; -import { NATIVE_TOKEN_ADDRESS } from "../../../../../../../constants/addresses.js"; -import type { PayUIOptions } from "../../../../../../core/hooks/connection/ConnectButtonProps.js"; -import { type ERC20OrNativeToken, isNativeToken } from "../../nativeToken.js"; -import type { SupportedChainAndTokens } from "../swap/useSwapSupportedChains.js"; - -// Based on what toToken, toChain, and supportedDestinations are, determine which payment methods should be enabled -// change the current method if it should be disabled -// return whether the payment selection should be shown or not ( if only one payment method is enabled, don't show the selection ) -export type PaymentMethods = { - buyWithFiatEnabled: boolean; - buyWithCryptoEnabled: boolean; -}; - -export function useEnabledPaymentMethods(options: { - payOptions: PayUIOptions; - supportedDestinations: SupportedChainAndTokens; - toChain: Chain; - toToken: ERC20OrNativeToken; -}): PaymentMethods { - const { payOptions, supportedDestinations, toChain, toToken } = options; - - function getEnabledPayMethodsForSelectedToken(): { - fiat: boolean; - swap: boolean; - } { - const chain = supportedDestinations.find((c) => c.chain.id === toChain.id); - if (!chain) { - return { - fiat: false, - swap: false, - }; - } - - const toTokenAddress = isNativeToken(toToken) - ? NATIVE_TOKEN_ADDRESS - : toToken.address; - - const tokenInfo = chain.tokens.find( - (t) => t.address.toLowerCase() === toTokenAddress.toLowerCase(), - ); - - if (!tokenInfo) { - return { - fiat: true, - swap: true, - }; - } - - return { - fiat: tokenInfo.buyWithFiatEnabled, - swap: tokenInfo.buyWithCryptoEnabled, - }; - } - - const { fiat, swap } = getEnabledPayMethodsForSelectedToken(); - - const buyWithFiatEnabled = payOptions.buyWithFiat !== false && fiat; - const buyWithCryptoEnabled = payOptions.buyWithCrypto !== false && swap; - - return { - buyWithCryptoEnabled, - buyWithFiatEnabled, - }; -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.ts b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.ts deleted file mode 100644 index d2c0a601469..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { useEffect, useState } from "react"; -import { polygon } from "../../../../../../../chains/chain-definitions/polygon.js"; -import type { Chain } from "../../../../../../../chains/types.js"; -import type { - FundWalletOptions, - PayUIOptions, -} from "../../../../../../core/hooks/connection/ConnectButtonProps.js"; -import { useActiveWalletChain } from "../../../../../../core/hooks/wallets/useActiveWalletChain.js"; -import type { TokenInfo } from "../../../../../../core/utils/defaultTokens.js"; -import { useDebouncedValue } from "../../../../hooks/useDebouncedValue.js"; -import { type ERC20OrNativeToken, NATIVE_TOKEN } from "../../nativeToken.js"; -import { - type CurrencyMeta, - currencies, - usdCurrency, -} from "../fiat/currencies.js"; -import type { SupportedChainAndTokens } from "../swap/useSwapSupportedChains.js"; - -type SupportedSourcesInputData = { - chain: Chain; - tokens: { - address: string; - buyWithCryptoEnabled: boolean; - buyWithFiatEnabled: boolean; - name: string; - symbol: string; - }[]; -}; - -// handle states for token and chain selection -export function useToTokenSelectionStates(options: { - payOptions: PayUIOptions; - supportedDestinations: SupportedChainAndTokens; -}) { - const { payOptions, supportedDestinations } = options; - // -------------------------------------------------------------------------- - // buy token amount --------------------------------------------------------- - // NOTE - for transaction / direct payment modes, the token amount is set when the user tap continue - const prefillBuy = (payOptions as FundWalletOptions)?.prefillBuy; - const activeChain = useActiveWalletChain(); - const initialTokenAmount = prefillBuy?.amount || ""; - const [tokenAmount, setTokenAmount] = useState(initialTokenAmount); - const deferredTokenAmount = useDebouncedValue(tokenAmount, 300); - - useEffect(() => { - if (prefillBuy?.amount) { - setTokenAmount(prefillBuy.amount); - } - if (prefillBuy?.chain) { - setToChain(prefillBuy.chain); - } - if (prefillBuy?.token) { - setToToken(prefillBuy.token as TokenInfo); - } - }, [prefillBuy?.amount, prefillBuy?.chain, prefillBuy?.token]); - - // Destination chain and token selection ----------------------------------- - const [toChain, setToChain] = useState( - // use prefill chain if available - prefillBuy?.chain || - (payOptions.mode === "transaction" && payOptions.transaction?.chain) || - (payOptions.mode === "direct_payment" && payOptions.paymentInfo?.chain) || - // use active chain if its supported as destination - supportedDestinations.find((x) => x.chain.id === activeChain?.id) - ?.chain || - // default to the first chain in supportedDestinations, or polygon if nothing is found at all - supportedDestinations[0]?.chain || - polygon, - ); - - const [toToken, setToToken] = useState( - (prefillBuy?.token as TokenInfo) || - (payOptions.mode === "direct_payment" && payOptions.paymentInfo.token) || - NATIVE_TOKEN, - ); - - return { - deferredTokenAmount, - setToChain, - setTokenAmount, - setToToken, - toChain, - tokenAmount, - toToken, - }; -} - -export function useFromTokenSelectionStates(options: { - payOptions: PayUIOptions; - supportedSources: SupportedSourcesInputData[]; -}) { - const { payOptions } = options; - - // TODO (pay) - auto select token based on connected wallet balances - - // Source token and chain selection --------------------------------------------------- - const [fromChain_, setFromChain] = useState(); - - // use prefill chain if available - const fromChainDevSpecified = - (payOptions.buyWithCrypto !== false && - payOptions.buyWithCrypto?.prefillSource?.chain) || - (payOptions.mode === "transaction" && payOptions.transaction?.chain) || - (payOptions.mode === "direct_payment" && payOptions.paymentInfo?.chain); - - const fromChain = fromChain_ || fromChainDevSpecified || undefined; - - const [fromToken_, setFromToken] = useState(); - - // use prefill token if available - const fromTokenDevSpecified = - (payOptions.buyWithCrypto !== false && - payOptions.buyWithCrypto?.prefillSource?.token) || - (payOptions.mode === "direct_payment" && payOptions.paymentInfo.token); - - // supported tokens query in here - const fromToken = fromToken_ || fromTokenDevSpecified || undefined; - - return { - fromChain, - fromToken, - setFromChain, - setFromToken, - }; -} - -export function useFiatCurrencySelectionStates(options: { - payOptions: PayUIOptions; -}) { - const { payOptions } = options; - - // -------------------------------------------------------------------------- - const devSpecifiedDefaultCurrency = - payOptions.buyWithFiat !== false - ? payOptions.buyWithFiat?.prefillSource?.currency - : undefined; - - const defaultSelectedCurrencyShorthand = - devSpecifiedDefaultCurrency || getDefaultCurrencyBasedOnLocation(); - - const [selectedCurrency, setSelectedCurrency] = useState( - currencies.find((x) => x.shorthand === defaultSelectedCurrencyShorthand) || - usdCurrency, - ); - - return { - selectedCurrency, - setSelectedCurrency, - }; -} - -function getDefaultCurrencyBasedOnLocation(): CurrencyMeta["shorthand"] { - // if Intl is not supported - browser throws - try { - const timeZone = Intl.DateTimeFormat() - .resolvedOptions() - .timeZone.toLowerCase(); - - // Europe/London -> GBP - if (timeZone.includes("london")) { - return "GBP"; - } - - // Europe/* -> EUR - if (timeZone.includes("europe")) { - return "EUR"; - } - - // Japan - if (timeZone.includes("japan")) { - return "JPY"; - } - - // canada - if (timeZone.includes("canada")) { - return "CAD"; - } - - // australia - if (timeZone.includes("australia")) { - return "AUD"; - } - - // new zealand - if (timeZone.includes("new zealand")) { - return "NZD"; - } - - return "USD"; - } catch { - return "USD"; - } -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/openOnRamppopup.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/openOnRamppopup.tsx deleted file mode 100644 index 8d0fecbfbf3..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/openOnRamppopup.tsx +++ /dev/null @@ -1,12 +0,0 @@ -export function openOnrampPopup(link: string, theme: string) { - const height = 750; - const width = 500; - const top = (window.innerHeight - height) / 2; - const left = (window.innerWidth - width) / 2; - - return window.open( - `${link}&theme=${theme}`, - "thirdweb Pay", - `width=${width}, height=${height}, top=${top}, left=${left}`, - ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/SwapDetailsScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/SwapDetailsScreen.tsx deleted file mode 100644 index c66a5917ffa..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/SwapDetailsScreen.tsx +++ /dev/null @@ -1,294 +0,0 @@ -import { ExternalLinkIcon } from "@radix-ui/react-icons"; -import { getCachedChain } from "../../../../../../../chains/utils.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import type { BuyWithCryptoQuote } from "../../../../../../../pay/buyWithCrypto/getQuote.js"; -import type { ValidBuyWithCryptoStatus } from "../../../../../../../pay/buyWithCrypto/getStatus.js"; -import { formatExplorerTxUrl } from "../../../../../../../utils/url.js"; -import { - fontSize, - iconSize, - spacing, -} from "../../../../../../core/design-system/index.js"; -import { - useChainExplorers, - useChainName, -} from "../../../../../../core/hooks/others/useChainQuery.js"; -import { Container, Line } from "../../../../components/basic.js"; -import { ButtonLink } from "../../../../components/buttons.js"; -import { Spacer } from "../../../../components/Spacer.js"; -import { Text } from "../../../../components/text.js"; -import { WalletRow } from "../swap/WalletRow.js"; -import { getBuyWithCryptoStatusMeta, type StatusMeta } from "./statusMeta.js"; -import { TokenInfoRow } from "./TokenInfoRow.js"; - -type SwapTxDetailsData = { - fromToken: { - chainId: number; - symbol: string; - address: string; - amount: string; - }; - quotedToToken: { - chainId: number; - symbol: string; - address: string; - amount: string; - }; - gotToken?: { - chainId: number; - symbol: string; - address: string; - amount: string; - }; - statusMeta?: StatusMeta; - sourceTxHash?: string; - destinationTxHash?: string; - isPartialSuccess: boolean; - estimatedDuration: number; - fromAddress: string; - toAddress: string; -}; - -export function SwapTxDetailsTable( - props: - | { - type: "quote"; - quote: BuyWithCryptoQuote; - client: ThirdwebClient; - } - | { - client: ThirdwebClient; - type: "status"; - status: ValidBuyWithCryptoStatus; - hideStatusRow?: boolean; - }, -) { - let uiData: SwapTxDetailsData; - let showStatusRow = true; - if (props.type === "status") { - const status = props.status; - if (props.hideStatusRow) { - showStatusRow = false; - } - - const isPartialSuccess = - status.status === "COMPLETED" && status.subStatus === "PARTIAL_SUCCESS"; - - uiData = { - destinationTxHash: status.destination?.transactionHash, - estimatedDuration: status.quote.estimated.durationSeconds || 0, - fromAddress: status.fromAddress, - fromToken: { - address: status.quote.fromToken.tokenAddress, - amount: status.quote.fromAmount, - chainId: status.quote.fromToken.chainId, - symbol: status.quote.fromToken.symbol || "", - }, - gotToken: status.destination - ? { - address: status.destination.token.tokenAddress, - amount: status.destination.amount, - chainId: status.destination.token.chainId, - symbol: status.destination.token.symbol || "", - } - : undefined, - isPartialSuccess, - quotedToToken: { - address: status.quote.toToken.tokenAddress, - amount: status.quote.toAmount, - chainId: status.quote.toToken.chainId, - symbol: status.quote.toToken.symbol || "", - }, - sourceTxHash: status.source?.transactionHash, - statusMeta: getBuyWithCryptoStatusMeta(status), - toAddress: status.toAddress, - }; - } else { - const quote = props.quote; - uiData = { - estimatedDuration: quote.swapDetails.estimated.durationSeconds || 0, - fromAddress: quote.swapDetails.fromAddress, - fromToken: { - address: quote.swapDetails.fromToken.tokenAddress, - amount: quote.swapDetails.fromAmount, - chainId: quote.swapDetails.fromToken.chainId, - symbol: quote.swapDetails.fromToken.symbol || "", - }, - isPartialSuccess: false, - quotedToToken: { - address: quote.swapDetails.toToken.tokenAddress, - amount: quote.swapDetails.toAmount, - chainId: quote.swapDetails.toToken.chainId, - symbol: quote.swapDetails.toToken.symbol || "", - }, - toAddress: quote.swapDetails.toAddress, - }; - } - - const { client } = props; - - const { - fromToken, - quotedToToken: toToken, - statusMeta, - sourceTxHash, - destinationTxHash, - isPartialSuccess, - gotToken, - } = uiData; - - const fromChainId = fromToken.chainId; - const toChainId = toToken.chainId; - - const fromChainName = useChainName(getCachedChain(fromChainId)); - const fromChainExplorers = useChainExplorers(getCachedChain(fromChainId)); - const toChainName = useChainName(getCachedChain(toChainId)); - const toChainExplorers = useChainExplorers(getCachedChain(toChainId)); - - const lineSpacer = ( - <> - - - - - ); - - return ( -
- {/* Pay */} - - - {lineSpacer} - {isPartialSuccess && gotToken ? ( - // Expected + Got - <> - {/* Expected */} - - - {lineSpacer} - - {/* Got */} - - - ) : ( - // Receive - - )} - - {lineSpacer} - - Recipient - - - - {/* Status */} - {statusMeta && showStatusRow && ( - <> - {lineSpacer} - - Status - - - {statusMeta.status} - - - - - )} - - - - {/* source chain Tx hash link */} - {fromChainExplorers.explorers?.[0]?.url && sourceTxHash && ( - - View on {fromChainName.name} Explorer - - - )} - - {/* destination chain tx hash link */} - {destinationTxHash && - sourceTxHash !== destinationTxHash && - toChainExplorers?.explorers?.[0]?.url && ( - <> - - - View on {toChainName.name} Explorer - - - - )} -
- ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/TokenInfoRow.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/TokenInfoRow.tsx deleted file mode 100644 index bb322abe937..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/TokenInfoRow.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useMemo } from "react"; -import { getCachedChain } from "../../../../../../../chains/utils.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import { formatNumber } from "../../../../../../../utils/formatNumber.js"; -import { useChainName } from "../../../../../../core/hooks/others/useChainQuery.js"; -import { Container } from "../../../../components/basic.js"; -import { Text } from "../../../../components/text.js"; -import { PayTokenIcon } from "../PayTokenIcon.js"; - -export function TokenInfoRow(props: { - tokenSymbol: string; - tokenAmount: string; - tokenAddress: string; - chainId: number; - label: string; - client: ThirdwebClient; -}) { - const chainObj = useMemo( - () => getCachedChain(props.chainId), - [props.chainId], - ); - const { name } = useChainName(chainObj); - - return ( - - {props.label} - - - - - {formatNumber(Number(props.tokenAmount), 6)} {props.tokenSymbol} - - - {name} - - - ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/statusMeta.test.ts b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/statusMeta.test.ts deleted file mode 100644 index 5fdaefc0331..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/statusMeta.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { describe, expect, it } from "vitest"; -import type { BuyWithCryptoStatus } from "../../../../../../../pay/buyWithCrypto/getStatus.js"; -import { getBuyWithCryptoStatusMeta } from "./statusMeta.js"; - -describe("getBuyWithCryptoStatusMeta", () => { - it('returns "Unknown" for NOT_FOUND status', () => { - const result = getBuyWithCryptoStatusMeta({ - status: "NOT_FOUND", - } as BuyWithCryptoStatus); - expect(result).toEqual({ - color: "secondaryText", - status: "Unknown", - }); - }); - - it('returns "Bridging" for WAITING_BRIDGE subStatus', () => { - const result = getBuyWithCryptoStatusMeta({ - status: "PENDING", - subStatus: "WAITING_BRIDGE", - } as BuyWithCryptoStatus); - expect(result).toEqual({ - color: "accentText", - loading: true, - status: "Bridging", - }); - }); - - it('returns "Incomplete" for PARTIAL_SUCCESS subStatus', () => { - const result = getBuyWithCryptoStatusMeta({ - status: "COMPLETED", - subStatus: "PARTIAL_SUCCESS", - } as BuyWithCryptoStatus); - expect(result).toEqual({ - color: "secondaryText", - status: "Incomplete", - }); - }); - - it('returns "Pending" for PENDING status', () => { - const result = getBuyWithCryptoStatusMeta({ - status: "PENDING", - } as BuyWithCryptoStatus); - expect(result).toEqual({ - color: "accentText", - loading: true, - status: "Pending", - }); - }); - - it('returns "Failed" for FAILED status', () => { - const result = getBuyWithCryptoStatusMeta({ - status: "FAILED", - } as BuyWithCryptoStatus); - expect(result).toEqual({ - color: "danger", - status: "Failed", - }); - }); - - it('returns "Completed" for COMPLETED status', () => { - const result = getBuyWithCryptoStatusMeta({ - status: "COMPLETED", - } as BuyWithCryptoStatus); - expect(result).toEqual({ - color: "success", - status: "Completed", - }); - }); - - it('returns "Unknown" for unhandled status', () => { - const result = getBuyWithCryptoStatusMeta({ - // @ts-ignore Test purpose - status: "Unknown", - }); - expect(result).toEqual({ - color: "secondaryText", - status: "Unknown", - }); - }); -}); diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/statusMeta.ts b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/statusMeta.ts deleted file mode 100644 index 8db1e33eea0..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/pay-transactions/statusMeta.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { BuyWithCryptoStatus } from "../../../../../../../pay/buyWithCrypto/getStatus.js"; -import type { Theme } from "../../../../../../core/design-system/index.js"; - -export type StatusMeta = { - status: string; - color: keyof Theme["colors"]; - loading?: true; -}; - -export function getBuyWithCryptoStatusMeta( - cryptoStatus: BuyWithCryptoStatus, -): StatusMeta { - if (cryptoStatus.status === "NOT_FOUND") { - return { - color: "secondaryText", - status: "Unknown", - }; - } - - const subStatus = cryptoStatus.subStatus; - const status = cryptoStatus.status; - - if (subStatus === "WAITING_BRIDGE") { - return { - color: "accentText", - loading: true, - status: "Bridging", - }; - } - - if (subStatus === "PARTIAL_SUCCESS") { - return { - color: "secondaryText", - status: "Incomplete", - }; - } - - if (status === "PENDING") { - return { - color: "accentText", - loading: true, - status: "Pending", - }; - } - - if (status === "FAILED") { - return { - color: "danger", - status: "Failed", - }; - } - - if (status === "COMPLETED") { - return { - color: "success", - status: "Completed", - }; - } - - return { - color: "secondaryText", - status: "Unknown", - }; -} - -export type FiatStatusMeta = { - status: string; - color: keyof Theme["colors"]; - loading?: true; - step: 1 | 2; - progressStatus: - | "pending" - | "completed" - | "failed" - | "actionRequired" - | "unknown"; -}; diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/BuyTokenInput.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/BuyTokenInput.tsx deleted file mode 100644 index a9e225c74cf..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/BuyTokenInput.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { useRef } from "react"; -import type { Chain } from "../../../../../../../chains/types.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import { fontSize } from "../../../../../../core/design-system/index.js"; -import { Container } from "../../../../components/basic.js"; -import { Input } from "../../../../components/formElements.js"; -import { Spacer } from "../../../../components/Spacer.js"; -import { TokenRow } from "../../../../components/token/TokenRow.js"; -import { TokenSymbol } from "../../../../components/token/TokenSymbol.js"; -import type { ERC20OrNativeToken } from "../../nativeToken.js"; -import { getBuyTokenAmountFontSize } from "../utils.js"; -import { FiatValue } from "./FiatValue.js"; - -/** - * @internal - */ -export function BuyTokenInput(props: { - token: ERC20OrNativeToken; - chain: Chain; - value: string; - onChange: (value: string) => void; - onSelectToken: () => void; - client: ThirdwebClient; - hideTokenSelector?: boolean; - freezeAmount?: boolean; - freezeChainAndToken?: boolean; -}) { - const getWidth = () => { - let chars = props.value.replace(".", "").length; - const hasDot = props.value.includes("."); - if (hasDot) { - chars += 0.3; - } - return `calc(${`${Math.max(1, chars)}ch`} + 6px)`; - }; - - const inputRef = useRef(null); - - return ( - - {/* Input */} - {/** biome-ignore lint/a11y/noStaticElementInteractions: TODO */} - {/** biome-ignore lint/a11y/useKeyWithClickEvents: TODO */} -
{ - inputRef.current?.focus(); - }} - > - - { - let value = e.target.value; - - // Replace comma with period if it exists - value = value.replace(",", "."); - - if (value.startsWith(".")) { - value = `0${value}`; - } - - const numValue = Number(value); - if (Number.isNaN(numValue)) { - return; - } - - if (value.startsWith("0") && !value.startsWith("0.")) { - props.onChange(value.slice(1)); - } else { - props.onChange(value); - } - }} - onClick={(e) => { - // put cursor at the end of the input - if (props.value === "") { - e.currentTarget.setSelectionRange( - e.currentTarget.value.length, - e.currentTarget.value.length, - ); - } - }} - pattern="^[0-9]*[.,]?[0-9]*$" - placeholder="0" - ref={inputRef} - style={{ - border: "none", - borderRadius: "0", - boxShadow: "none", - fontSize: getBuyTokenAmountFontSize(props.value), - fontWeight: 600, - maxWidth: "calc(100% - 100px)", - padding: "0", - paddingBlock: "2px", - textAlign: "right", - width: getWidth(), - }} - tabIndex={-1} - type="text" - value={props.value || "0"} - variant="outline" - /> - - -
- - - - - - {!props.hideTokenSelector && ( - <> - - - {/* Token / Chain selector */} - - - - - )} -
- ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/ConfirmationScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/ConfirmationScreen.tsx deleted file mode 100644 index 53cfc9cd812..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/ConfirmationScreen.tsx +++ /dev/null @@ -1,377 +0,0 @@ -import { useMemo, useState } from "react"; -import { trackPayEvent } from "../../../../../../../analytics/track/pay.js"; -import type { Chain } from "../../../../../../../chains/types.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import { getContract } from "../../../../../../../contract/contract.js"; -import { approve } from "../../../../../../../extensions/erc20/write/approve.js"; -import type { BuyWithCryptoQuote } from "../../../../../../../pay/buyWithCrypto/getQuote.js"; -import { sendBatchTransaction } from "../../../../../../../transaction/actions/send-batch-transaction.js"; -import { sendTransaction } from "../../../../../../../transaction/actions/send-transaction.js"; -import { - type WaitForReceiptOptions, - waitForReceipt, -} from "../../../../../../../transaction/actions/wait-for-tx-receipt.js"; -import { useCustomTheme } from "../../../../../../core/design-system/CustomThemeProvider.js"; -import { Container, ModalHeader } from "../../../../components/basic.js"; -import { Button } from "../../../../components/buttons.js"; -import { Spacer } from "../../../../components/Spacer.js"; -import { Spinner } from "../../../../components/Spinner.js"; -import { StepBar } from "../../../../components/StepBar.js"; -import { SwitchNetworkButton } from "../../../../components/SwitchNetwork.js"; -import { Text } from "../../../../components/text.js"; -import { StyledDiv } from "../../../../design-system/elements.js"; -import type { ERC20OrNativeToken } from "../../nativeToken.js"; -import { Step } from "../Stepper.js"; -import type { PayerInfo } from "../types.js"; -import { ErrorText } from "./ErrorText.js"; -import { addPendingTx } from "./pendingSwapTx.js"; -import { SwapSummary } from "./SwapSummary.js"; - -/** - * @internal - */ -export function SwapConfirmationScreen(props: { - title: string; - onBack?: () => void; - client: ThirdwebClient; - quote: BuyWithCryptoQuote; - setSwapTxHash: (txHash: string) => void; - onTryAgain: () => void; - toChain: Chain; - toAmount: string; - toTokenSymbol: string; - fromChain: Chain; - toToken: ERC20OrNativeToken; - fromAmount: string; - fromToken: ERC20OrNativeToken; - fromTokenSymbol: string; - isFiatFlow: boolean; - payer: PayerInfo; - preApprovedAmount?: bigint; -}) { - const approveTxRequired = - props.quote.approvalData && - props.preApprovedAmount !== undefined && - props.preApprovedAmount < BigInt(props.quote.approvalData.amountWei); - const needsApprovalStep = - approveTxRequired && !props.payer.account.sendBatchTransaction; - const initialStep = needsApprovalStep ? "approval" : "swap"; - - const [step, setStep] = useState<"approval" | "swap">(initialStep); - const [error, setError] = useState(); - const [status, setStatus] = useState< - "pending" | "success" | "error" | "idle" - >("idle"); - - const receiver = props.quote.swapDetails.toAddress; - const sender = props.quote.swapDetails.fromAddress; - - const uiErrorMessage = useMemo(() => { - if (step === "approval" && status === "error" && error) { - if ( - error.toLowerCase().includes("user rejected") || - error.toLowerCase().includes("user closed modal") || - error.toLowerCase().includes("user denied") - ) { - return { - message: "Your wallet rejected the approval request.", - title: "Failed to Approve", - }; - } - if (error.toLowerCase().includes("insufficient funds for gas")) { - return { - message: - "You do not have enough native funds to approve the transaction.", - title: "Insufficient Native Funds", - }; - } - return { - message: - "Your wallet failed to approve the transaction for an unknown reason. Please try again or contact support.", - title: "Failed to Approve", - }; - } - - if (step === "swap" && status === "error" && error) { - if ( - error.toLowerCase().includes("user rejected") || - error.toLowerCase().includes("user closed modal") || - error.toLowerCase().includes("user denied") - ) { - return { - message: "Your wallet rejected the confirmation request.", - title: "Failed to Confirm", - }; - } - if (error.toLowerCase().includes("insufficient funds for gas")) { - return { - message: - "You do not have enough native funds to confirm the transaction.", - title: "Insufficient Native Funds", - }; - } - return { - message: - "Your wallet failed to confirm the transaction for an unknown reason. Please try again or contact support.", - title: "Failed to Confirm", - }; - } - - return undefined; - }, [error, step, status]); - - return ( - - - - {props.isFiatFlow ? ( - <> - - - - - Step 2 of 2 - Converting {props.fromTokenSymbol} to{" "} - {props.toTokenSymbol} - - - - ) : ( - <> - - Confirm payment - - - )} - - - - - - {/* Show 2 steps - Approve and confirm */} - {needsApprovalStep && ( - <> - - - - - - - - - )} - - {uiErrorMessage && ( - <> - - - - )} - - {props.payer.chain.id !== props.fromChain.id ? ( - <> - - { - await props.payer.wallet.switchChain(props.fromChain); - }} - variant="accent" - /> - - ) : ( - <> - - - - )} - - ); -} - -export const ConnectorLine = /* @__PURE__ */ StyledDiv(() => { - const theme = useCustomTheme(); - return { - background: theme.colors.borderColor, - flex: 1, - height: "4px", - }; -}); diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/ErrorText.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/ErrorText.tsx deleted file mode 100644 index 8bfa0229335..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/ErrorText.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { CrossCircledIcon } from "@radix-ui/react-icons"; -import { iconSize } from "../../../../../../core/design-system/index.js"; -import { Container } from "../../../../components/basic.js"; -import { Text } from "../../../../components/text.js"; - -export function ErrorText(props: { title: string; message: string }) { - return ( - - - - - {props.title} - - - - {props.message} - - - ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/Fees.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/Fees.tsx deleted file mode 100644 index b5cb30292c2..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/Fees.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import type { BuyWithCryptoQuote } from "../../../../../../../pay/buyWithCrypto/getQuote.js"; -import type { BuyWithFiatQuote } from "../../../../../../../pay/buyWithFiat/getQuote.js"; -import { formatNumber } from "../../../../../../../utils/formatNumber.js"; -import { Container, Line } from "../../../../components/basic.js"; -import { Spacer } from "../../../../components/Spacer.js"; -import { Text } from "../../../../components/text.js"; - -/** - * @internal - */ -export function SwapFees(props: { quote: BuyWithCryptoQuote }) { - return ( - - {props.quote.processingFees.map((fee) => { - const feeAmount = formatNumber(Number(fee.amount), 6); - return ( - - - {feeAmount === 0 ? "~" : ""} - {feeAmount} {fee.token.symbol} - - - (${(fee.amountUSDCents / 100).toFixed(2)}) - - - ); - })} - - ); -} - -/** - * @internal - */ -export function FiatFees(props: { quote: BuyWithFiatQuote }) { - return ( - - {/* Amount ( without fees included ) */} -
- - Amount - - - {formatNumber(Number(props.quote.fromCurrency.amount), 2)}{" "} - {props.quote.fromCurrency.currencySymbol} - -
- - {/* Processing Fees */} - {props.quote.processingFees.map((fee, i) => { - const feeAmount = formatNumber(Number(fee.amount), 6); - - return ( -
- - {fee.feeType === "NETWORK" ? "Network Fee" : "Processing Fee"} - - - - {feeAmount === 0 ? "~" : ""} {feeAmount} {fee.currencySymbol} - -
- ); - })} - - - - - - {/* Total Amount */} -
- - Total - - - {formatNumber(Number(props.quote.fromCurrencyWithFees.amount), 6)}{" "} - {props.quote.fromCurrencyWithFees.currencySymbol} - -
-
- ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/PayWithCrypto.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/PayWithCrypto.tsx deleted file mode 100644 index 43141db3ffa..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/PayWithCrypto.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import type { Chain } from "../../../../../../../chains/types.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import type { Account } from "../../../../../../../wallets/interfaces/wallet.js"; -import { useCustomTheme } from "../../../../../../core/design-system/CustomThemeProvider.js"; -import { - fontSize, - radius, - spacing, -} from "../../../../../../core/design-system/index.js"; -import { useWalletBalance } from "../../../../../../core/hooks/others/useWalletBalance.js"; -import type { TokenInfo } from "../../../../../../core/utils/defaultTokens.js"; -import { Container } from "../../../../components/basic.js"; -import { Skeleton } from "../../../../components/Skeleton.js"; -import { Text } from "../../../../components/text.js"; -import { TokenRow } from "../../../../components/token/TokenRow.js"; -import { TokenSymbol } from "../../../../components/token/TokenSymbol.js"; -import { formatTokenBalance } from "../../formatTokenBalance.js"; -import { isNativeToken, type NativeToken } from "../../nativeToken.js"; -import { WalletRow } from "./WalletRow.js"; - -/** - * 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 - */ -export function PayWithCryptoQuoteInfo(props: { - value: string; - chain: Chain | undefined; - token: TokenInfo | NativeToken | undefined; - isLoading: boolean; - client: ThirdwebClient; - freezeChainAndTokenSelection?: boolean; - payerAccount: Account; - swapRequired: boolean; - onSelectToken: () => void; -}) { - const theme = useCustomTheme(); - const balanceQuery = useWalletBalance( - { - address: props.payerAccount.address, - chain: props.chain, - client: props.client, - tokenAddress: isNativeToken(props.token) - ? undefined - : props.token?.address, - }, - { - enabled: !!props.chain && !!props.token, - }, - ); - - return ( - - {/* Wallet and balance */} - - - {props.token && props.chain && balanceQuery.data ? ( - - - {formatTokenBalance(balanceQuery.data, false, 4)} - - - - ) : props.token && props.chain && balanceQuery.isLoading ? ( - - ) : null} - - {/* Quoted price & token selector */} - - - ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapFlow.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapFlow.tsx deleted file mode 100644 index 34c22c135db..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapFlow.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { useMemo, useState } from "react"; -import { getCachedChain } from "../../../../../../../chains/utils.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import { NATIVE_TOKEN_ADDRESS } from "../../../../../../../constants/addresses.js"; -import type { BuyWithCryptoQuote } from "../../../../../../../pay/buyWithCrypto/getQuote.js"; -import type { BuyWithCryptoStatus } from "../../../../../../../pay/buyWithCrypto/getStatus.js"; -import type { TokenInfo } from "../../../../../../core/utils/defaultTokens.js"; -import { type ERC20OrNativeToken, NATIVE_TOKEN } from "../../nativeToken.js"; -import type { PayerInfo } from "../types.js"; -import { SwapConfirmationScreen } from "./ConfirmationScreen.js"; -import { SwapStatusScreen } from "./SwapStatusScreen.js"; - -type SwapFlowProps = { - title: string; - onBack?: () => void; - buyWithCryptoQuote: BuyWithCryptoQuote; - payer: PayerInfo; - client: ThirdwebClient; - isFiatFlow: boolean; - onDone: () => void; - onTryAgain: () => void; - transactionMode: boolean; - isEmbed: boolean; - onSuccess: ((status: BuyWithCryptoStatus) => void) | undefined; - approvalAmount?: bigint; -}; - -export function SwapFlow(props: SwapFlowProps) { - const [swapTxHash, setSwapTxHash] = useState(); - - const quote = props.buyWithCryptoQuote; - - const fromChain = useMemo( - () => getCachedChain(quote.swapDetails.fromToken.chainId), - [quote], - ); - - const toChain = useMemo( - () => getCachedChain(quote.swapDetails.toToken.chainId), - [quote], - ); - - const fromTokenSymbol = quote.swapDetails.fromToken.symbol || ""; - const toTokenSymbol = quote.swapDetails.toToken.symbol || ""; - - const fromAmount = quote.swapDetails.fromAmount; - const toAmount = quote.swapDetails.toAmount; - - const _toToken = quote.swapDetails.toToken; - const _fromToken = quote.swapDetails.fromToken; - - const toToken: ERC20OrNativeToken = useMemo(() => { - if (_toToken.tokenAddress === NATIVE_TOKEN_ADDRESS) { - return NATIVE_TOKEN; - } - - const tokenInfo: TokenInfo = { - address: _toToken.tokenAddress, - name: _toToken.name || "", - symbol: _toToken.symbol || "", - }; - return tokenInfo; - }, [_toToken]); - - const fromToken: ERC20OrNativeToken = useMemo(() => { - if (_fromToken.tokenAddress === NATIVE_TOKEN_ADDRESS) { - return NATIVE_TOKEN; - } - - const tokenInfo: TokenInfo = { - address: _fromToken.tokenAddress, - name: _fromToken.name || "", - symbol: _fromToken.symbol || "", - }; - return tokenInfo; - }, [_fromToken]); - - if (swapTxHash) { - return ( - - ); - } - - return ( - - ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapScreenContent.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapScreenContent.tsx deleted file mode 100644 index 4c1270d72dd..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapScreenContent.tsx +++ /dev/null @@ -1,359 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { useState } from "react"; -import { trackPayEvent } from "../../../../../../../analytics/track/pay.js"; -import type { Chain } from "../../../../../../../chains/types.js"; -import { getCachedChain } from "../../../../../../../chains/utils.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import { NATIVE_TOKEN_ADDRESS } from "../../../../../../../constants/addresses.js"; -import { getContract } from "../../../../../../../contract/contract.js"; -import { allowance } from "../../../../../../../extensions/erc20/__generated__/IERC20/read/allowance.js"; -import type { GetBuyWithCryptoQuoteParams } from "../../../../../../../pay/buyWithCrypto/getQuote.js"; -import type { Account } from "../../../../../../../wallets/interfaces/wallet.js"; -import type { PayUIOptions } from "../../../../../../core/hooks/connection/ConnectButtonProps.js"; -import { useWalletBalance } from "../../../../../../core/hooks/others/useWalletBalance.js"; -import { useBuyWithCryptoQuote } from "../../../../../../core/hooks/pay/useBuyWithCryptoQuote.js"; -import { getErrorMessage } from "../../../../../utils/errors.js"; -import { Container } from "../../../../components/basic.js"; -import { Button } from "../../../../components/buttons.js"; -import { - Drawer, - DrawerOverlay, - useDrawer, -} from "../../../../components/Drawer.js"; -import { Spacer } from "../../../../components/Spacer.js"; -import { Spinner } from "../../../../components/Spinner.js"; -import { Text } from "../../../../components/text.js"; -import { TokenSymbol } from "../../../../components/token/TokenSymbol.js"; -import type { PayEmbedConnectOptions } from "../../../../PayEmbed.js"; -import type { ConnectLocale } from "../../../locale/types.js"; -import { type ERC20OrNativeToken, isNativeToken } from "../../nativeToken.js"; -import { EstimatedTimeAndFees } from "../EstimatedTimeAndFees.js"; -import type { SelectedScreen } from "../main/types.js"; -import type { PayerInfo } from "../types.js"; -import { SwapFees } from "./Fees.js"; -import { PayWithCryptoQuoteInfo } from "./PayWithCrypto.js"; - -export function SwapScreenContent(props: { - setScreen: (screen: SelectedScreen) => void; - tokenAmount: string; - toToken: ERC20OrNativeToken; - toChain: Chain; - fromChain: Chain | undefined; - fromToken: ERC20OrNativeToken | undefined; - showFromTokenSelector: () => void; - payer: PayerInfo; - client: ThirdwebClient; - payOptions: PayUIOptions; - isEmbed: boolean; - onDone: () => void; - connectOptions: PayEmbedConnectOptions | undefined; - connectLocale: ConnectLocale; - setPayer: (payer: PayerInfo) => void; - activeAccount: Account; - setTokenAmount: (amount: string) => void; - setHasEditedAmount: (hasEdited: boolean) => void; - disableTokenSelection: boolean; - paymentLinkId: undefined | string; -}) { - const { - setScreen, - payer, - client, - toChain, - tokenAmount, - toToken, - fromChain, - fromToken, - payOptions, - disableTokenSelection, - } = props; - - const defaultRecipientAddress = ( - props.payOptions as Extract - )?.paymentInfo?.sellerAddress; - const receiverAddress = - defaultRecipientAddress || props.activeAccount.address; - const { drawerRef, drawerOverlayRef, isOpen, setIsOpen } = useDrawer(); - const [drawerScreen, setDrawerScreen] = useState< - "fees" | "receiver" | "payer" - >("fees"); - - const fromTokenBalanceQuery = useWalletBalance( - { - address: payer.account.address, - chain: fromChain, - client, - tokenAddress: isNativeToken(fromToken) ? undefined : fromToken?.address, - }, - { - enabled: !!fromChain && !!fromToken, - }, - ); - - const fromTokenId = isNativeToken(fromToken) - ? NATIVE_TOKEN_ADDRESS - : fromToken?.address?.toLowerCase(); - const toTokenId = isNativeToken(toToken) - ? NATIVE_TOKEN_ADDRESS - : toToken.address.toLowerCase(); - const swapRequired = - !!tokenAmount && - !!fromChain && - !!fromTokenId && - !(fromChain?.id === toChain.id && fromTokenId === toTokenId); - const quoteParams: GetBuyWithCryptoQuoteParams | undefined = - fromChain && fromToken && swapRequired - ? { - client, - // wallets - fromAddress: payer.account.address, - // from - fromChainId: fromChain.id, - fromTokenAddress: isNativeToken(fromToken) - ? NATIVE_TOKEN_ADDRESS - : fromToken.address, - paymentLinkId: props.paymentLinkId, - purchaseData: payOptions.purchaseData, - toAddress: receiverAddress, - toAmount: tokenAmount, - // to - toChainId: toChain.id, - toTokenAddress: isNativeToken(toToken) - ? NATIVE_TOKEN_ADDRESS - : toToken.address, - } - : undefined; - - const quoteQuery = useBuyWithCryptoQuote(quoteParams, { - gcTime: 30 * 1000, - refetchInterval: 30 * 1000, - // refetch every 30 seconds - staleTime: 30 * 1000, - }); - - const allowanceQuery = useQuery({ - enabled: !!quoteQuery.data?.approvalData, - queryFn: () => { - if (!quoteQuery.data?.approvalData) { - return null; - } - return allowance({ - contract: getContract({ - address: quoteQuery.data.swapDetails.fromToken.tokenAddress, - chain: getCachedChain(quoteQuery.data.swapDetails.fromToken.chainId), - client: props.client, - }), - owner: props.payer.account.address, - spender: quoteQuery.data.approvalData.spenderAddress, - }); - }, - queryKey: [ - "allowance", - payer.account.address, - quoteQuery.data?.approvalData, - ], - refetchOnMount: true, - }); - - const sourceTokenAmount = swapRequired - ? quoteQuery.data?.swapDetails.fromAmount - : tokenAmount; - - const isNotEnoughBalance = - !!sourceTokenAmount && - !!fromTokenBalanceQuery.data && - Number(fromTokenBalanceQuery.data.displayValue) < Number(sourceTokenAmount); - - const disableContinue = - !fromChain || - !fromToken || - (swapRequired && !quoteQuery.data) || - isNotEnoughBalance || - allowanceQuery.isLoading; - - const errorMsg = - !quoteQuery.isLoading && quoteQuery.error - ? getErrorMessage(quoteQuery.error) - : undefined; - - function showSwapFlow() { - if ( - (props.payOptions.mode === "direct_payment" || - props.payOptions.mode === "fund_wallet") && - !isNotEnoughBalance && - !swapRequired - ) { - // same currency, just direct transfer - setScreen({ - id: "transfer-flow", - }); - } else if ( - props.payOptions.mode === "transaction" && - !isNotEnoughBalance && - !swapRequired - ) { - if (payer.account.address !== receiverAddress) { - // needs transfer from another wallet before executing the transaction - setScreen({ - id: "transfer-flow", - }); - } else { - // has enough balance to just do the transaction directly - props.onDone(); - } - - return; - } - - if (!quoteQuery.data) { - return; - } - - setScreen({ - approvalAmount: allowanceQuery.data ?? undefined, - id: "swap-flow", - quote: quoteQuery.data, - }); - } - - function showFees() { - if (!quoteQuery.data) { - return; - } - - setIsOpen(true); - setDrawerScreen("fees"); - } - - return ( - - {isOpen && ( - <> - - setIsOpen(false)} ref={drawerRef}> - {drawerScreen === "fees" && quoteQuery.data && ( -
- - Fees - - - -
- )} -
- - )} - - {/* Quote info */} - - - Pay with - {fromToken && fromChain ? ( - - ) : ( - "crypto" - )} - -
- - {swapRequired && fromChain && fromToken && ( - - )} -
- {/* Error message */} - {errorMsg && ( -
-
- - {errorMsg.title} - - - {errorMsg.message} - -
-
- )} - - {!errorMsg && isNotEnoughBalance && ( -
- - Insufficient Funds - - - Select another token or pay with card. - -
- )} -
- - {/* Button */} - {isNotEnoughBalance || errorMsg ? ( - - ) : ( - - )} -
- ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapStatusScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapStatusScreen.tsx deleted file mode 100644 index c44e69a33e6..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapStatusScreen.tsx +++ /dev/null @@ -1,232 +0,0 @@ -import { CheckCircledIcon } from "@radix-ui/react-icons"; -import { useQueryClient } from "@tanstack/react-query"; -import { useEffect, useRef, useState } from "react"; -import type { Chain } from "../../../../../../../chains/types.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import type { BuyWithCryptoQuote } from "../../../../../../../pay/buyWithCrypto/getQuote.js"; -import type { BuyWithCryptoStatus } from "../../../../../../../pay/buyWithCrypto/getStatus.js"; -import { iconSize } from "../../../../../../core/design-system/index.js"; -import { useBuyWithCryptoStatus } from "../../../../../../core/hooks/pay/useBuyWithCryptoStatus.js"; -import { invalidateWalletBalance } from "../../../../../../core/providers/invalidateWalletBalance.js"; -import { Container, ModalHeader } from "../../../../components/basic.js"; -import { Button } from "../../../../components/buttons.js"; -import { Spacer } from "../../../../components/Spacer.js"; -import { Spinner } from "../../../../components/Spinner.js"; -import { Text } from "../../../../components/text.js"; -import { AccentFailIcon } from "../../../icons/AccentFailIcon.js"; -import { SwapTxDetailsTable } from "../pay-transactions/SwapDetailsScreen.js"; - -type UIStatus = "pending" | "success" | "failed" | "partialSuccess"; - -export function SwapStatusScreen(props: { - title: string; - onBack?: () => void; - swapTxHash: string; - fromChain: Chain; - client: ThirdwebClient; - onTryAgain: () => void; - onDone: () => void; - transactionMode: boolean; - isEmbed: boolean; - quote: BuyWithCryptoQuote | undefined; - onSuccess: ((status: BuyWithCryptoStatus) => void) | undefined; -}) { - const { onSuccess } = props; - const [showDetails, setShowDetails] = useState(false); - - const swapStatus = useBuyWithCryptoStatus({ - chainId: props.fromChain.id, - client: props.client, - transactionHash: props.swapTxHash, - }); - - let uiStatus: UIStatus = "pending"; - if (swapStatus.data?.status === "COMPLETED") { - uiStatus = "success"; - } else if (swapStatus.data?.status === "FAILED") { - uiStatus = "failed"; - } - - if ( - swapStatus.data?.status === "COMPLETED" && - swapStatus.data?.subStatus === "PARTIAL_SUCCESS" - ) { - uiStatus = "partialSuccess"; - } - - const purchaseCbCalled = useRef(false); - useEffect(() => { - if (purchaseCbCalled.current || !onSuccess) { - return; - } - - if (swapStatus.data?.status === "COMPLETED") { - purchaseCbCalled.current = true; - onSuccess(swapStatus.data); - } - }, [onSuccess, swapStatus]); - - const queryClient = useQueryClient(); - const balanceInvalidated = useRef(false); - useEffect(() => { - if ( - (uiStatus === "success" || uiStatus === "partialSuccess") && - !balanceInvalidated.current - ) { - balanceInvalidated.current = true; - invalidateWalletBalance(queryClient); - } - }, [queryClient, uiStatus]); - - const swapDetails = - swapStatus.data && swapStatus.data.status !== "NOT_FOUND" ? ( - - ) : props.quote ? ( - - ) : null; - - if (showDetails) { - return ( - - - setShowDetails(false)} - title={"Transaction Details"} - /> - - {swapDetails} - - - ); - } - - return ( - - - - - - {uiStatus === "success" && ( - <> - - - - - - Buy Complete - - - - - - - - - )} - - {uiStatus === "partialSuccess" && - swapStatus.data?.status !== "NOT_FOUND" && - swapStatus.data?.destination && ( - <> - - - - - - Incomplete - - - - Expected {swapStatus.data.quote.toToken.symbol}, Got{" "} - {swapStatus.data.destination.token.symbol} instead - - - - - )} - - {uiStatus === "failed" && ( - <> - - - - - - - - Transaction Failed - - - - - Your transaction {`couldn't`} be processed - - - - - - - - - - - - )} - - {uiStatus === "pending" && ( - <> - - -
- -
- - - Buy Pending - - - - This may take a minute to complete - -
- - - )} -
-
- ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapSummary.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapSummary.tsx deleted file mode 100644 index 8da74f1b008..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapSummary.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import type { Chain } from "../../../../../../../chains/types.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import { useCustomTheme } from "../../../../../../core/design-system/CustomThemeProvider.js"; -import { radius } from "../../../../../../core/design-system/index.js"; -import { Container } from "../../../../components/basic.js"; -import { TokenRow } from "../../../../components/token/TokenRow.js"; -import type { ERC20OrNativeToken } from "../../nativeToken.js"; -import { StepConnectorArrow } from "./StepConnector.js"; -import { WalletRow } from "./WalletRow.js"; - -export function SwapSummary(props: { - sender: string; - receiver: string; - client: ThirdwebClient; - fromToken: ERC20OrNativeToken; - fromChain: Chain; - toToken: ERC20OrNativeToken; - toChain: Chain; - fromAmount: string; - toAmount: string; -}) { - const theme = useCustomTheme(); - const isDifferentRecipient = - props.receiver.toLowerCase() !== props.sender.toLowerCase(); - return ( - - {/* Sell */} - - - - - {}} - style={{ - background: "transparent", - border: "none", - borderRadius: 0, - }} - token={props.fromToken} - value={props.fromAmount} - /> - - {/* Connector Icon */} - - {/* Buy */} - - {isDifferentRecipient && ( - - - - )} - {}} - style={{ - background: "transparent", - border: "none", - borderRadius: 0, - }} - token={props.toToken} - value={props.toAmount} - /> - - - ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TokenSelectorScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TokenSelectorScreen.tsx deleted file mode 100644 index 0d54e91929d..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TokenSelectorScreen.tsx +++ /dev/null @@ -1,448 +0,0 @@ -import styled from "@emotion/styled"; -import { - CardStackIcon, - ChevronRightIcon, - Cross2Icon, -} from "@radix-ui/react-icons"; -import { trackPayEvent } from "../../../../../../../analytics/track/pay.js"; -import type { Chain } from "../../../../../../../chains/types.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import type { Wallet } from "../../../../../../../wallets/interfaces/wallet.js"; -import type { WalletId } from "../../../../../../../wallets/wallet-types.js"; -import { useCustomTheme } from "../../../../../../core/design-system/CustomThemeProvider.js"; -import { - iconSize, - radius, - spacing, -} from "../../../../../../core/design-system/index.js"; -import type { PayUIOptions } from "../../../../../../core/hooks/connection/ConnectButtonProps.js"; -import { - useChainMetadata, - useChainName, -} from "../../../../../../core/hooks/others/useChainQuery.js"; -import { useActiveAccount } from "../../../../../../core/hooks/wallets/useActiveAccount.js"; -import { useActiveWallet } from "../../../../../../core/hooks/wallets/useActiveWallet.js"; -import { useConnectedWallets } from "../../../../../../core/hooks/wallets/useConnectedWallets.js"; -import { useDisconnect } from "../../../../../../core/hooks/wallets/useDisconnect.js"; -import type { - SupportedTokens, - TokenInfo, -} from "../../../../../../core/utils/defaultTokens.js"; -import { LoadingScreen } from "../../../../../wallets/shared/LoadingScreen.js"; -import { Container } from "../../../../components/basic.js"; -import { Button } from "../../../../components/buttons.js"; -import { Spacer } from "../../../../components/Spacer.js"; -import { TextDivider } from "../../../../components/TextDivider.js"; -import { TokenIcon } from "../../../../components/TokenIcon.js"; -import { Text } from "../../../../components/text.js"; -import { OutlineWalletIcon } from "../../../icons/OutlineWalletIcon.js"; -import { formatTokenBalance } from "../../formatTokenBalance.js"; -import { type ERC20OrNativeToken, isNativeToken } from "../../nativeToken.js"; -import { FiatValue } from "./FiatValue.js"; -import { - type TokenBalance, - useWalletsAndBalances, -} from "./fetchBalancesForWallet.js"; -import { WalletRow } from "./WalletRow.js"; - -export function TokenSelectorScreen(props: { - client: ThirdwebClient; - sourceTokens: SupportedTokens | undefined; - sourceSupportedTokens: SupportedTokens | undefined; - toChain: Chain; - toToken: ERC20OrNativeToken; - tokenAmount: string; - mode: PayUIOptions["mode"]; - hiddenWallets?: WalletId[]; - onSelectToken: (wallet: Wallet, token: TokenInfo, chain: Chain) => void; - onConnect: () => void; - onPayWithFiat: () => void; - fiatSupported: boolean; -}) { - const connectedWallets = useConnectedWallets(); - const activeAccount = useActiveAccount(); - const activeWallet = useActiveWallet(); - const chainInfo = useChainMetadata(props.toChain); - const theme = useCustomTheme(); - - const walletsAndBalances = useWalletsAndBalances({ - client: props.client, - mode: props.mode, - sourceSupportedTokens: props.sourceSupportedTokens || [], - toChain: props.toChain, - toToken: props.toToken, - }); - - if ( - walletsAndBalances.isLoading || - chainInfo.isLoading || - !chainInfo.data || - !props.sourceSupportedTokens - ) { - return ; - } - - const filteredWallets = Array.from(walletsAndBalances.data?.entries() || []) - .filter(([w]) => !props.hiddenWallets?.includes(w.id)) - .filter(([, balances]) => { - const hasEnoughBalance = balances.some((b) => b.balance.value > 0); - return hasEnoughBalance; - }); - - return ( - - {filteredWallets.length === 0 ? ( - - - No suitable payment token found -
- in connected wallets -
-
- ) : ( - - Select payment token - - - )} - - - {filteredWallets.map(([w, balances]) => { - const address = w.address; - const wallet = connectedWallets.find( - (w) => w.getAccount()?.address === address, - ); - if (!wallet) return null; - return ( - { - trackPayEvent({ - chainId: chain.id, - client: props.client, - event: "choose_payment_method_token", - fromToken: isNativeToken(token) ? undefined : token.address, - toChainId: props.toChain.id, - toToken: isNativeToken(props.toToken) - ? undefined - : props.toToken.address, - walletAddress: activeAccount?.address, - walletType: activeWallet?.id, - }); - props.onSelectToken(wallet, token, chain); - }} - wallet={wallet} - /> - ); - })} - {filteredWallets.length > 0 && } - - {props.fiatSupported && ( - - )} - - -
- ); -} - -function WalletRowWithBalances(props: { - client: ThirdwebClient; - address: string; - wallet: Wallet; - balances: TokenBalance[]; - onClick: (wallet: Wallet, token: TokenInfo, chain: Chain) => void; - hideConnectButton?: boolean; -}) { - const theme = useCustomTheme(); - const displayedBalances = props.balances; - const activeAccount = useActiveAccount(); - const { disconnect } = useDisconnect(); - const isActiveAccount = activeAccount?.address === props.address; - - return ( - - - - {!isActiveAccount && ( - - )} - - - {props.balances.length > 0 ? ( - displayedBalances.map((b, idx) => ( - props.onClick(props.wallet, b.token, b.chain)} - style={{ - borderBottom: - idx === displayedBalances.length - 1 - ? "none" - : `1px solid ${theme.colors.borderColor}`, - borderBottomLeftRadius: - idx === displayedBalances.length - 1 ? radius.lg : 0, - borderBottomRightRadius: - idx === displayedBalances.length - 1 ? radius.lg : 0, - borderTopLeftRadius: 0, - borderTopRightRadius: 0, - }} - tokenBalance={b} - wallet={props.wallet} - /> - )) - ) : ( - - - Insufficient Funds - - - )} - - - ); -} - -function TokenBalanceRow(props: { - client: ThirdwebClient; - tokenBalance: TokenBalance; - wallet: Wallet; - onClick: (token: TokenInfo, wallet: Wallet) => void; - style?: React.CSSProperties; -}) { - const { tokenBalance, wallet, onClick, client, style } = props; - const chainInfo = useChainName(tokenBalance.chain); - return ( - onClick(tokenBalance.token, wallet)} - style={{ - ...style, - display: "flex", - justifyContent: "space-between", - minWidth: 0, // Needed for text truncation to work - }} - variant="secondary" - > - - - - - {tokenBalance.token.symbol} - - {chainInfo && ( - - {chainInfo.name} - - )} - - - - - - - {formatTokenBalance(tokenBalance.balance, true, 2)} - - - - - - - ); -} - -const StyledButton = /* @__PURE__ */ styled(Button)((props) => { - const theme = useCustomTheme(); - return { - "&:hover": { - background: theme.colors.secondaryButtonBg, - transform: "scale(1.01)", - }, - background: "transparent", - flexDirection: "row", - flexWrap: "nowrap", - gap: spacing.sm, - justifyContent: "space-between", - padding: spacing.sm, - paddingRight: spacing.xs, - transition: "background 200ms ease, transform 150ms ease", - ...props.style, - }; -}); diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferConfirmationScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferConfirmationScreen.tsx deleted file mode 100644 index 6fee5b8fa07..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferConfirmationScreen.tsx +++ /dev/null @@ -1,558 +0,0 @@ -import { CheckCircledIcon } from "@radix-ui/react-icons"; -import { useQuery } from "@tanstack/react-query"; -import { useMemo, useState } from "react"; -import { trackPayEvent } from "../../../../../../../analytics/track/pay.js"; -import type { Chain } from "../../../../../../../chains/types.js"; -import { getCachedChain } from "../../../../../../../chains/utils.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import { NATIVE_TOKEN_ADDRESS } from "../../../../../../../constants/addresses.js"; -import { getContract } from "../../../../../../../contract/contract.js"; -import { allowance } from "../../../../../../../extensions/erc20/__generated__/IERC20/read/allowance.js"; -import { - type GetCurrencyMetadataResult, - getCurrencyMetadata, -} from "../../../../../../../extensions/erc20/read/getCurrencyMetadata.js"; -import { approve } from "../../../../../../../extensions/erc20/write/approve.js"; -import { transfer } from "../../../../../../../extensions/erc20/write/transfer.js"; -import type { BuyWithCryptoStatus } from "../../../../../../../pay/buyWithCrypto/getStatus.js"; -import { getBuyWithCryptoTransfer } from "../../../../../../../pay/buyWithCrypto/getTransfer.js"; -import { sendAndConfirmTransaction } from "../../../../../../../transaction/actions/send-and-confirm-transaction.js"; -import { sendTransaction } from "../../../../../../../transaction/actions/send-transaction.js"; -import { prepareTransaction } from "../../../../../../../transaction/prepare-transaction.js"; -import type { TransactionReceipt } from "../../../../../../../transaction/types.js"; -import type { Address } from "../../../../../../../utils/address.js"; -import { toWei } from "../../../../../../../utils/units.js"; -import { iconSize } from "../../../../../../core/design-system/index.js"; -import type { PayUIOptions } from "../../../../../../core/hooks/connection/ConnectButtonProps.js"; -import { Container, ModalHeader } from "../../../../components/basic.js"; -import { Button } from "../../../../components/buttons.js"; -import { Spacer } from "../../../../components/Spacer.js"; -import { Spinner } from "../../../../components/Spinner.js"; -import { StepBar } from "../../../../components/StepBar.js"; -import { SwitchNetworkButton } from "../../../../components/SwitchNetwork.js"; -import { Text } from "../../../../components/text.js"; -import { type ERC20OrNativeToken, isNativeToken } from "../../nativeToken.js"; -import { Step } from "../Stepper.js"; -import type { PayerInfo } from "../types.js"; -import { ConnectorLine } from "./ConfirmationScreen.js"; -import { ErrorText } from "./ErrorText.js"; -import { SwapSummary } from "./SwapSummary.js"; - -type TransferConfirmationScreenProps = { - title: string; - onBack?: () => void; - setTransactionHash: (txHash: string) => void; - payer: PayerInfo; - receiverAddress: string; - client: ThirdwebClient; - onDone: () => void; - chain: Chain; - token: ERC20OrNativeToken; - tokenAmount: string; - transactionMode?: boolean; - payOptions?: PayUIOptions; - onSuccess: ((status: BuyWithCryptoStatus) => void) | undefined; - paymentLinkId: undefined | string; -}; - -export function TransferConfirmationScreen( - props: TransferConfirmationScreenProps, -) { - const { - title, - onBack, - receiverAddress, - client, - payer, - onDone, - chain, - token, - tokenAmount, - transactionMode, - setTransactionHash, - payOptions, - paymentLinkId, - } = props; - const [step, setStep] = useState<"approve" | "transfer" | "execute">( - "transfer", - ); - const [status, setStatus] = useState< - | { id: "idle" } - | { id: "pending" } - | { id: "error"; error: string } - | { id: "done" } - >({ id: "idle" }); - - const transferQuery = useQuery({ - queryFn: async () => { - const transferResponse = await getBuyWithCryptoTransfer({ - amount: tokenAmount, - chainId: chain.id, - client, - feePayer: - payOptions?.mode === "direct_payment" - ? payOptions.paymentInfo.feePayer - : undefined, - fromAddress: payer.account.address, - paymentLinkId: paymentLinkId, - purchaseData: payOptions?.purchaseData, - toAddress: receiverAddress, - tokenAddress: isNativeToken(token) - ? NATIVE_TOKEN_ADDRESS - : token.address, - }); - return transferResponse; - }, - queryKey: [ - "transfer", - isNativeToken(token) ? NATIVE_TOKEN_ADDRESS : token.address, - tokenAmount, - receiverAddress, - payer.account.address, - payOptions?.purchaseData, - ], - refetchInterval: 30 * 1000, - }); - - const uiErrorMessage = useMemo(() => { - if (step === "approve" && status.id === "error" && status.error) { - if ( - status.error.toLowerCase().includes("user rejected") || - status.error.toLowerCase().includes("user closed modal") || - status.error.toLowerCase().includes("user denied") - ) { - return { - message: "Your wallet rejected the approval request.", - title: "Failed to Approve", - }; - } - if (status.error.toLowerCase().includes("insufficient funds for gas")) { - return { - message: - "You do not have enough native funds to approve the transaction.", - title: "Insufficient Native Funds", - }; - } - return { - message: - "Your wallet failed to approve the transaction for an unknown reason. Please try again or contact support.", - title: "Failed to Approve", - }; - } - - if ( - (step === "transfer" || step === "execute") && - status.id === "error" && - status.error - ) { - if ( - status.error.toLowerCase().includes("user rejected") || - status.error.toLowerCase().includes("user closed modal") || - status.error.toLowerCase().includes("user denied") - ) { - return { - message: "Your wallet rejected the confirmation request.", - title: "Failed to Confirm", - }; - } - if (status.error.toLowerCase().includes("insufficient funds for gas")) { - return { - message: - "You do not have enough native funds to confirm the transaction.", - title: "Insufficient Native Funds", - }; - } - return { - message: - "Your wallet failed to confirm the transaction for an unknown reason. Please try again or contact support.", - title: "Failed to Confirm", - }; - } - - return undefined; - }, [step, status]); - - if (transferQuery.isLoading) { - return ( - - - - - - - - - ); - } - - const transferFromAmountWithFees = - transferQuery.data?.paymentToken.amount || tokenAmount; - - return ( - - - - - {transactionMode ? ( - <> - - - - {step === "transfer" - ? "Step 1 of 2 - Transfer funds" - : "Step 2 of 2 - Finalize transaction"} - - - - ) : ( - <> - Confirm payment - - - )} - - - - - - {transactionMode && ( - <> - - - - - - - - - )} - - {uiErrorMessage && ( - <> - - - - )} - - {!transactionMode && step === "execute" && status.id === "done" && ( - <> - - - - Payment completed - - - - - )} - - {/* Execute */} - {payer.chain.id !== chain.id ? ( - { - await props.payer.wallet.switchChain(chain); - }} - variant="accent" - /> - ) : ( - - )} - - ); -} - -function transferBuyWithCryptoQuote(args: { - token: ERC20OrNativeToken; - chain: Chain; - tokenMetadata: GetCurrencyMetadataResult; - tokenAmount: string; - fromAddress: string; - toAddress: string; - transaction: TransactionReceipt; -}): BuyWithCryptoStatus { - const { - token, - chain, - tokenMetadata, - tokenAmount, - fromAddress, - toAddress, - transaction, - } = args; - return { - fromAddress, - quote: { - createdAt: new Date().toISOString(), - estimated: { - durationSeconds: 0, - feesUSDCents: 0, - fromAmountUSDCents: 0, - gasCostUSDCents: 0, - slippageBPS: 0, - toAmountMinUSDCents: 0, - toAmountUSDCents: 0, - }, - fromAmount: tokenAmount, - fromAmountWei: toWei(tokenAmount).toString(), - fromToken: { - chainId: chain.id, - decimals: tokenMetadata.decimals, - name: tokenMetadata.name, - priceUSDCents: 0, - symbol: tokenMetadata.symbol, - tokenAddress: isNativeToken(token) - ? NATIVE_TOKEN_ADDRESS - : token.address, - }, - toAmount: tokenAmount, - toAmountMin: tokenAmount, - toAmountMinWei: toWei(tokenAmount).toString(), - toAmountWei: toWei(tokenAmount).toString(), - toToken: { - chainId: chain.id, - decimals: tokenMetadata.decimals, - name: tokenMetadata.name, - priceUSDCents: 0, - symbol: tokenMetadata.symbol, - tokenAddress: isNativeToken(token) - ? NATIVE_TOKEN_ADDRESS - : token.address, - }, - }, - source: { - amount: tokenAmount, - amountUSDCents: 0, - amountWei: toWei(tokenAmount).toString(), - completedAt: new Date().toISOString(), - token: { - chainId: chain.id, - decimals: tokenMetadata.decimals, - name: tokenMetadata.name, - priceUSDCents: 0, - symbol: tokenMetadata.symbol, - tokenAddress: isNativeToken(token) - ? NATIVE_TOKEN_ADDRESS - : token.address, - }, - transactionHash: transaction.transactionHash, - }, - status: "COMPLETED", - subStatus: "SUCCESS", - swapType: "TRANSFER", - toAddress, - }; -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferFlow.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferFlow.tsx deleted file mode 100644 index 54a4cb3f01b..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferFlow.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useState } from "react"; -import type { Chain } from "../../../../../../../chains/types.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import type { BuyWithCryptoStatus } from "../../../../../../../pay/buyWithCrypto/getStatus.js"; -import type { PayUIOptions } from "../../../../../../core/hooks/connection/ConnectButtonProps.js"; -import type { ERC20OrNativeToken } from "../../nativeToken.js"; -import type { PayerInfo } from "../types.js"; -import { SwapStatusScreen } from "./SwapStatusScreen.js"; -import { TransferConfirmationScreen } from "./TransferConfirmationScreen.js"; - -type TransferFlowProps = { - title: string; - onBack?: () => void; - payer: PayerInfo; - receiverAddress: string; - client: ThirdwebClient; - onDone: () => void; - onTryAgain: () => void; - isEmbed: boolean; - onSuccess: ((status: BuyWithCryptoStatus) => void) | undefined; - chain: Chain; - token: ERC20OrNativeToken; - tokenAmount: string; - transactionMode?: boolean; - payOptions?: PayUIOptions; - paymentLinkId: undefined | string; -}; - -export function TransferFlow(props: TransferFlowProps) { - const [transferTxHash, setTransferTxHash] = useState(); - - if (transferTxHash) { - return ( - - ); - } - - return ( - - ); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/fetchBalancesForWallet.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/fetchBalancesForWallet.tsx deleted file mode 100644 index 2e1931eac9c..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/fetchBalancesForWallet.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import type { Chain } from "../../../../../../../chains/types.js"; -import { getCachedChain } from "../../../../../../../chains/utils.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import { NATIVE_TOKEN_ADDRESS } from "../../../../../../../constants/addresses.js"; -import { isInsightEnabled } from "../../../../../../../insight/common.js"; -import { getOwnedTokens } from "../../../../../../../insight/get-tokens.js"; -import type { Wallet } from "../../../../../../../wallets/interfaces/wallet.js"; -import { - type GetWalletBalanceResult, - getWalletBalance, -} from "../../../../../../../wallets/utils/getWalletBalance.js"; -import type { WalletId } from "../../../../../../../wallets/wallet-types.js"; -import type { PayUIOptions } from "../../../../../../core/hooks/connection/ConnectButtonProps.js"; -import { useChainMetadata } from "../../../../../../core/hooks/others/useChainQuery.js"; -import { useActiveAccount } from "../../../../../../core/hooks/wallets/useActiveAccount.js"; -import { useConnectedWallets } from "../../../../../../core/hooks/wallets/useConnectedWallets.js"; -import type { - SupportedTokens, - TokenInfo, -} from "../../../../../../core/utils/defaultTokens.js"; -import { type ERC20OrNativeToken, isNativeToken } from "../../nativeToken.js"; - -const CHUNK_SIZE = 5; - -function chunkChains(chains: T[]): T[][] { - const chunks: T[][] = []; - for (let i = 0; i < chains.length; i += CHUNK_SIZE) { - chunks.push(chains.slice(i, i + CHUNK_SIZE)); - } - return chunks; -} - -type FetchBalancesParams = { - wallet: Wallet; - accountAddress: string | undefined; - sourceSupportedTokens: SupportedTokens; - toChain: Chain; - toToken: ERC20OrNativeToken; - mode: PayUIOptions["mode"]; - client: ThirdwebClient; -}; - -export type TokenBalance = { - balance: GetWalletBalanceResult; - chain: Chain; - token: TokenInfo; -}; - -type WalletKey = { - id: WalletId; - address: string; -}; - -export function useWalletsAndBalances(props: { - sourceSupportedTokens: SupportedTokens; - toChain: Chain; - toToken: ERC20OrNativeToken; - mode: PayUIOptions["mode"]; - client: ThirdwebClient; -}) { - const activeAccount = useActiveAccount(); - const connectedWallets = useConnectedWallets(); - const chainInfo = useChainMetadata(props.toChain); - - return useQuery({ - enabled: - !!props.sourceSupportedTokens && !!chainInfo.data && !!activeAccount, - queryFn: async () => { - const entries = await Promise.all( - connectedWallets.map(async (wallet) => { - const balances = await fetchBalancesForWallet({ - accountAddress: activeAccount?.address, - client: props.client, - mode: props.mode, - sourceSupportedTokens: props.sourceSupportedTokens || [], - toChain: props.toChain, - toToken: props.toToken, - wallet, - }); - return [ - { - address: wallet.getAccount()?.address || "", - id: wallet.id, - } as WalletKey, - balances, - ] as const; - }), - ); - const map = new Map(); - for (const entry of entries) { - map.set(entry[0], entry[1]); - } - return map; - }, - queryKey: [ - "wallets-and-balances", - props.sourceSupportedTokens, - props.toChain.id, - props.toToken, - props.mode, - activeAccount?.address, - connectedWallets.map((w) => w.getAccount()?.address), - ], - }); -} - -async function fetchBalancesForWallet({ - wallet, - accountAddress, - sourceSupportedTokens, - toChain, - toToken, - mode, - client, -}: FetchBalancesParams): Promise { - const account = wallet.getAccount(); - if (!account) { - return []; - } - - const balances: TokenBalance[] = []; - - // 1. Resolve all unique chains in the supported token map - const uniqueChains = Object.keys(sourceSupportedTokens).map((id) => - getCachedChain(Number(id)), - ); - - // 2. Check insight availability once per chain - const insightSupport = await Promise.all( - uniqueChains.map(async (c) => ({ - chain: c, - enabled: await isInsightEnabled(c), - })), - ); - const insightEnabledChains = insightSupport - .filter((c) => c.enabled) - .map((c) => c.chain); - - // 3. ERC-20 balances for insight-enabled chains (batched 5 chains / call) - const insightChunks = chunkChains(insightEnabledChains); - await Promise.all( - insightChunks.map(async (chunk) => { - let owned: GetWalletBalanceResult[] = []; - let page = 0; - const limit = 100; - - while (true) { - const batch = await getOwnedTokens({ - chains: chunk, - client, - ownerAddress: account.address, - queryOptions: { - limit, - page, - }, - }).catch((err) => { - console.error("error fetching balances from insight", err); - return []; - }); - - if (batch.length === 0) { - break; - } - - owned = [...owned, ...batch]; - page += 1; - } - - for (const b of owned) { - const matching = sourceSupportedTokens[b.chainId]?.find( - (t) => t.address.toLowerCase() === b.tokenAddress.toLowerCase(), - ); - if (matching && b.value > 0n) { - balances.push({ - balance: b, - chain: getCachedChain(b.chainId), - token: matching, - }); - } - } - }), - ); - - // 4. Build a token map that also includes the destination token so it can be used to pay - const destinationToken = isNativeToken(toToken) - ? { - address: NATIVE_TOKEN_ADDRESS, - icon: toChain.icon?.url, - name: toChain.nativeCurrency?.name || "", - symbol: toChain.nativeCurrency?.symbol || "", - } - : toToken; - - const tokenMap: Record = { - ...sourceSupportedTokens, - [toChain.id]: [ - destinationToken, - ...(sourceSupportedTokens[toChain.id] || []), - ], - }; - - // 5. Fallback RPC balances (native currency & ERC-20 that we couldn't fetch from insight) - const rpcCalls: Promise[] = []; - - for (const [chainIdStr, tokens] of Object.entries(tokenMap)) { - const chainId = Number(chainIdStr); - const chain = getCachedChain(chainId); - - if (insightEnabledChains.some((c) => c.id === chainId)) { - continue; - } - - for (const token of tokens) { - const isNative = isNativeToken(token); - const isAlreadyFetched = balances.some( - (b) => - b.chain.id === chainId && - b.token.address.toLowerCase() === token.address.toLowerCase(), - ); - if (isAlreadyFetched) { - // ERC20 on insight-enabled chain already handled by insight call - continue; - } - rpcCalls.push( - (async () => { - try { - const balance = await getWalletBalance({ - address: account.address, - chain, - client, - tokenAddress: isNative ? undefined : token.address, - }); - - const include = - token.address.toLowerCase() === - destinationToken.address.toLowerCase() && - chain.id === toChain.id - ? !( - mode === "fund_wallet" && account.address === accountAddress - ) && balance.value > 0n - : balance.value > 0n; - - if (include) { - balances.push({ balance, chain, token }); - } - } catch (err) { - console.warn( - `Failed to fetch RPC balance for ${token.symbol} on chain ${chainId}`, - err, - ); - } - })(), - ); - } - } - - await Promise.all(rpcCalls); - - // Remove duplicates (same chainId + token address) - { - const uniq: Record = {}; - for (const b of balances) { - const k = `${b.chain.id}-${b.token.address.toLowerCase()}`; - if (!uniq[k]) { - uniq[k] = b; - } - } - balances.splice(0, balances.length, ...Object.values(uniq)); - } - // 6. Sort so that the destination token always appears first, then tokens on the destination chain, then by chain id - balances.sort((a, b) => { - const destAddress = destinationToken.address; - if (a.chain.id === toChain.id && a.token.address === destAddress) return -1; - if (b.chain.id === toChain.id && b.token.address === destAddress) return 1; - if (a.chain.id === toChain.id) return -1; - if (b.chain.id === toChain.id) return 1; - return a.chain.id - b.chain.id; - }); - - return balances; -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/pendingSwapTx.ts b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/pendingSwapTx.ts deleted file mode 100644 index d2a61de5b15..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/pendingSwapTx.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createStore } from "../../../../../../../reactive/store.js"; - -type PendingTxInfo = - | { - type: "swap"; - txHash: string; - chainId: number; - } - | { - type: "fiat"; - intentId: string; - }; - -const pendingTransactions = /* @__PURE__ */ createStore([]); - -/** - * @internal - */ -export const addPendingTx = (txInfo: PendingTxInfo) => { - const currentValue = pendingTransactions.getValue(); - // prepend the new tx to list - pendingTransactions.setValue([txInfo, ...currentValue]); -}; diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.ts b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.ts index 4b8fdd1ba65..2265b1b224b 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.ts +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.ts @@ -1,11 +1,4 @@ -import { useQuery } from "@tanstack/react-query"; -import { Address as ox__Address } from "ox"; -import * as Bridge from "../../../../../../../bridge/index.js"; import type { Chain } from "../../../../../../../chains/types.js"; -import { getCachedChain } from "../../../../../../../chains/utils.js"; -import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import type { Address } from "../../../../../../../utils/address.js"; -import { withCache } from "../../../../../../../utils/promise/withCache.js"; export type SupportedChainAndTokens = Array<{ chain: Chain; @@ -18,156 +11,3 @@ export type SupportedChainAndTokens = Array<{ icon?: string; }>; }>; - -async function fetchBuySupportedDestinations({ - client, - originChainId, - originTokenAddress, -}: { - client: ThirdwebClient; - originChainId?: number; - originTokenAddress?: Address; -}): Promise { - return withCache( - async () => { - const routes = await Bridge.routes({ - client, - limit: 1_000_000, - maxSteps: 1, - originChainId, - originTokenAddress, - sortBy: "popularity", - }); - const tokens = new Set(); - const chains = new Set(); - const destinationTokens: Record< - number, - Array<{ - address: Address; - buyWithCryptoEnabled: boolean; - buyWithFiatEnabled: boolean; - name: string; - symbol: string; - icon?: string; - }> - > = []; - for (const route of routes) { - const key = `${route.destinationToken.chainId}:${route.destinationToken.address}`; - if (!tokens.has(key)) { - tokens.add(key); - if (!chains.has(route.destinationToken.chainId)) { - chains.add(route.destinationToken.chainId); - } - const existing = destinationTokens[route.destinationToken.chainId]; - if (!existing) { - destinationTokens[route.destinationToken.chainId] = []; - } - destinationTokens[route.destinationToken.chainId] = [ - ...(existing || []), - { - address: ox__Address.checksum( - route.destinationToken.address, - ) as Address, - // We support both options for all tokens - buyWithCryptoEnabled: true, - buyWithFiatEnabled: true, - icon: route.destinationToken.iconUri, - name: route.destinationToken.name, - symbol: route.destinationToken.symbol, - }, - ]; - } - } - - return [...chains].map((chainId) => ({ - chain: getCachedChain(chainId), - tokens: destinationTokens[chainId] || [], - })); - }, - { - cacheKey: `buy-supported-destinations-${originChainId}:${originTokenAddress}`, - cacheTime: 5 * 60 * 1000, - }, - ); -} - -/** - * @internal - */ -export function useBuySupportedDestinations( - client: ThirdwebClient, - _isTestMode?: boolean, -) { - return useQuery({ - queryFn: async () => { - return fetchBuySupportedDestinations({ client }); - }, - queryKey: ["destination-tokens", client], - }); -} - -export function useBuySupportedSources(options: { - client: ThirdwebClient; - destinationChainId: number; - destinationTokenAddress: string; -}) { - return useQuery({ - queryFn: async () => { - const routes = await Bridge.routes({ - client: options.client, - destinationChainId: options.destinationChainId, - destinationTokenAddress: options.destinationTokenAddress, - limit: 50, - maxSteps: 1, - sortBy: "popularity", - }); - - const tokens = new Set(); - const chains = new Set(); - const originTokens: Record< - number, - Array<{ - address: Address; - buyWithCryptoEnabled: boolean; - buyWithFiatEnabled: boolean; - name: string; - symbol: string; - icon?: string; - }> - > = []; - for (const route of routes) { - const key = `${route.originToken.chainId}:${route.originToken.address}`; - if (!tokens.has(key)) { - tokens.add(key); - if (!chains.has(route.originToken.chainId)) { - chains.add(route.originToken.chainId); - } - const existing = originTokens[route.originToken.chainId]; - if (!existing) { - originTokens[route.originToken.chainId] = []; - } - originTokens[route.originToken.chainId] = [ - ...(existing || []), - { - address: ox__Address.checksum( - route.originToken.address, - ) as Address, - // We support both options for all tokens - buyWithCryptoEnabled: true, - buyWithFiatEnabled: true, - icon: route.originToken.iconUri, - name: route.originToken.name, - symbol: route.originToken.symbol, - }, - ]; - } - } - - return [...chains].map((chainId) => ({ - chain: getCachedChain(chainId), - tokens: originTokens[chainId] || [], - })); - }, - queryKey: ["source-tokens", options], - }); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/usePayerSetup.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/usePayerSetup.tsx deleted file mode 100644 index d3cd23b11f5..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/usePayerSetup.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useEffect, useState } from "react"; -import { useActiveAccount } from "../../../../../core/hooks/wallets/useActiveAccount.js"; -import { useActiveWallet } from "../../../../../core/hooks/wallets/useActiveWallet.js"; -import { useActiveWalletChain } from "../../../../../core/hooks/wallets/useActiveWalletChain.js"; -import type { PayerInfo } from "./types.js"; - -export function usePayerSetup() { - const wallet = useActiveWallet(); - const account = useActiveAccount(); - const activeChain = useActiveWalletChain(); - - const [_payer, setPayer] = useState(); - - useEffect(() => { - const wallet = _payer?.wallet; - - function update() { - if (!wallet) { - setPayer(undefined); - return; - } - - const account = wallet.getAccount(); - const chain = wallet.getChain(); - if (account && chain) { - setPayer({ - account, - chain, - wallet, - }); - } else { - setPayer(undefined); - } - } - - if (wallet) { - const unsubChainChanged = wallet.subscribe("chainChanged", update); - const unsubAccountChanged = wallet.subscribe("accountChanged", update); - return () => { - unsubChainChanged(); - unsubAccountChanged(); - }; - } - - return undefined; - }, [_payer]); - - const initialPayer = - account && activeChain && wallet - ? { account, chain: activeChain, wallet } - : undefined; - - // return the payer state if its set - // otherwise return the active wallet as payer - const payer: PayerInfo | undefined = _payer || initialPayer; - - return { - payer, - setPayer, - }; -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/utils.ts b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/utils.ts index 302cbd3387b..1266bdf0023 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/utils.ts +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/utils.ts @@ -1,12 +1,3 @@ export function getBuyTokenAmountFontSize(value: string) { return value.length > 10 ? "26px" : value.length > 6 ? "34px" : "50px"; } - -/** - * - * @param str accepts any string but expects a fully upppercased string / type FiatProvider - * @returns Fiat provider label to be rendered used within presentation logic - */ -export function getProviderLabel(str: string) { - return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); -} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/ReceiveFunds.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/ReceiveFunds.tsx index 7aad13133cd..30c5efbea79 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/ReceiveFunds.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/ReceiveFunds.tsx @@ -88,7 +88,6 @@ const WalletAddressContainer = /* @__PURE__ */ StyledButton((_) => { "&:hover": { borderColor: theme.colors.accentText, }, - all: "unset", border: `1px solid ${theme.colors.borderColor}`, borderRadius: radius.md, boxSizing: "border-box", diff --git a/packages/thirdweb/src/react/web/ui/PayEmbed.tsx b/packages/thirdweb/src/react/web/ui/PayEmbed.tsx index 434ede8f1e5..ea474f724d5 100644 --- a/packages/thirdweb/src/react/web/ui/PayEmbed.tsx +++ b/packages/thirdweb/src/react/web/ui/PayEmbed.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import type { Chain } from "../../../chains/types.js"; import type { ThirdwebClient } from "../../../client/client.js"; import type { Address } from "../../../utils/address.js"; @@ -10,27 +10,18 @@ import type { AppMetadata } from "../../../wallets/types.js"; import type { WalletId } from "../../../wallets/wallet-types.js"; import { CustomThemeProvider } from "../../core/design-system/CustomThemeProvider.js"; import type { Theme } from "../../core/design-system/index.js"; -import { - type SiweAuthOptions, - useSiweAuth, -} from "../../core/hooks/auth/useSiweAuth.js"; +import type { SiweAuthOptions } from "../../core/hooks/auth/useSiweAuth.js"; import type { ConnectButton_connectModalOptions, PayUIOptions, } from "../../core/hooks/connection/ConnectButtonProps.js"; -import { useActiveAccount } from "../../core/hooks/wallets/useActiveAccount.js"; -import { useActiveWallet } from "../../core/hooks/wallets/useActiveWallet.js"; import { useConnectionManager } from "../../core/providers/connection-manager.js"; import type { SupportedTokens } from "../../core/utils/defaultTokens.js"; -import { AutoConnect } from "../../web/ui/AutoConnect/AutoConnect.js"; import { BuyWidget } from "./Bridge/BuyWidget.js"; import { CheckoutWidget } from "./Bridge/CheckoutWidget.js"; import { TransactionWidget } from "./Bridge/TransactionWidget.js"; -import { useConnectLocale } from "./ConnectWallet/locale/getConnectLocale.js"; import { EmbedContainer } from "./ConnectWallet/Modal/ConnectEmbed.js"; -import BuyScreen from "./ConnectWallet/screens/Buy/BuyScreen.js"; import { DynamicHeight } from "./components/DynamicHeight.js"; -import { Spinner } from "./components/Spinner.js"; import type { LocaleId } from "./types.js"; /** @@ -309,17 +300,8 @@ export type PayEmbedProps = { * @deprecated Use `BuyWidget`, `CheckoutWidget` or `TransactionWidget` instead. */ export function PayEmbed(props: PayEmbedProps) { - const localeQuery = useConnectLocale(props.locale || "en_US"); - const [screen, setScreen] = useState<"buy" | "execute-tx">("buy"); const theme = props.theme || "dark"; const connectionManager = useConnectionManager(); - const activeAccount = useActiveAccount(); - const activeWallet = useActiveWallet(); - const siweAuth = useSiweAuth( - activeWallet, - activeAccount, - props.connectOptions?.auth, - ); // Add props.chain and props.chains to defined chains store useEffect(() => { @@ -340,7 +322,7 @@ export function PayEmbed(props: PayEmbedProps) { } }, [props.activeWallet, connectionManager]); - let content = null; + const content = null; const metadata = props.payOptions && "metadata" in props.payOptions ? props.payOptions.metadata @@ -421,51 +403,6 @@ export function PayEmbed(props: PayEmbedProps) { ); } - if (!localeQuery.data) { - content = ( -
- -
- ); - } else { - content = ( - <> - - {screen === "buy" && ( - { - if (props.payOptions?.mode === "transaction") { - setScreen("execute-tx"); - } - }} - paymentLinkId={props.paymentLinkId} - payOptions={ - props.payOptions || { - mode: "fund_wallet", - } - } - supportedTokens={props.supportedTokens} - theme={theme} - title={metadata?.name || "Buy"} - /> - )} - - ); - } - return ( { "&:hover": { borderColor: theme.colors.accentText, }, - all: "unset", border: `1px solid ${theme.colors.borderColor}`, borderRadius: `0 0 ${radius.md} ${radius.md}`, boxSizing: "border-box", diff --git a/packages/thirdweb/src/react/web/ui/components/Drawer.tsx b/packages/thirdweb/src/react/web/ui/components/Drawer.tsx deleted file mode 100644 index c32feb592b6..00000000000 --- a/packages/thirdweb/src/react/web/ui/components/Drawer.tsx +++ /dev/null @@ -1,171 +0,0 @@ -"use client"; -import { keyframes } from "@emotion/react"; -import { Cross2Icon } from "@radix-ui/react-icons"; -import { - forwardRef, - useCallback, - useLayoutEffect, - useRef, - useState, -} from "react"; -import { useCustomTheme } from "../../../core/design-system/CustomThemeProvider.js"; -import { iconSize, radius } from "../../../core/design-system/index.js"; -import { fadeInAnimation } from "../design-system/animations.js"; -import { StyledDiv } from "../design-system/elements.js"; -import { Container } from "./basic.js"; -import { IconButton } from "./buttons.js"; -import { DynamicHeight } from "./DynamicHeight.js"; -import { CrossContainer } from "./Modal.js"; - -type DrawerProps = { - children: React.ReactNode; - close: () => void; -}; -/** - * - * @internal - */ -export const Drawer = /* @__PURE__ */ forwardRef( - function Drawer_(props, ref) { - return ( - - - - - - - - - - {props.children} - - - - ); - }, -); - -const DrawerContainer = /* @__PURE__ */ StyledDiv((_) => { - const theme = useCustomTheme(); - return { - animation: `${drawerOpenAnimation} 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.1)`, - background: theme.colors.modalBg, - borderTop: `1px solid ${theme.colors.borderColor}`, - borderTopLeftRadius: radius.xl, - borderTopRightRadius: radius.xl, - bottom: 0, - left: 0, - position: "absolute", - right: 0, - zIndex: 10000, - }; -}); - -const drawerOpenAnimation = keyframes` - from { - opacity: 0; - transform: translateY(100px); - } - to { - opacity: 1; - transform: translateY(0); - } -`; - -export const DrawerOverlay = /* @__PURE__ */ StyledDiv((_) => { - const theme = useCustomTheme(); - return { - animation: `${fadeInAnimation} 400ms cubic-bezier(0.16, 1, 0.3, 1)`, - backgroundColor: theme.colors.modalOverlayBg, - inset: 0, - position: "absolute", - zIndex: 9999, - }; -}); - -/** - * - * @internal - */ -export 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 = { - duration: 300, - easing: "cubic-bezier(0.175, 0.885, 0.32, 1.1)", - fill: "forwards", - } as const; - - const closeAnimation = drawerRef.current.animate( - [{ opacity: 0, transform: "translateY(100%)" }], - animOptions, - ); - - drawerOverlayRef.current?.animate([{ opacity: 0 }], animOptions); - closeAnimation.onfinish = () => resolve(); - } else { - resolve(); - } - }); - }, []); - - const setIsOpen = useCallback( - async (value: boolean) => { - if (value) { - _setIsOpen(true); - } else { - await closeDrawerAnimation(); - _setIsOpen(false); - } - }, - [closeDrawerAnimation], - ); - - // close on outside click - useLayoutEffect(() => { - if (!isOpen) { - return; - } - - const handleClick = (event: MouseEvent) => { - 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 { - drawerOverlayRef, - drawerRef, - isOpen, - setIsOpen, - }; -} diff --git a/packages/thirdweb/src/react/web/ui/components/Modal.tsx b/packages/thirdweb/src/react/web/ui/components/Modal.tsx index c0093ac1843..6b249dd2fa0 100644 --- a/packages/thirdweb/src/react/web/ui/components/Modal.tsx +++ b/packages/thirdweb/src/react/web/ui/components/Modal.tsx @@ -150,7 +150,7 @@ export const Modal: React.FC<{ ); }; -export const CrossContainer = /* @__PURE__ */ StyledDiv({ +const CrossContainer = /* @__PURE__ */ StyledDiv({ position: "absolute", right: spacing.lg, top: spacing.lg, diff --git a/packages/thirdweb/src/react/web/ui/components/StepBar.tsx b/packages/thirdweb/src/react/web/ui/components/StepBar.tsx deleted file mode 100644 index ef527afae20..00000000000 --- a/packages/thirdweb/src/react/web/ui/components/StepBar.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { radius } from "../../../core/design-system/index.js"; -import { Container } from "./basic.js"; - -export function StepBar(props: { steps: number; currentStep: number }) { - return ( - - - {null} - - - ); -} diff --git a/packages/thirdweb/src/react/web/ui/components/SwitchNetwork.tsx b/packages/thirdweb/src/react/web/ui/components/SwitchNetwork.tsx deleted file mode 100644 index 99dc6698dce..00000000000 --- a/packages/thirdweb/src/react/web/ui/components/SwitchNetwork.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useState } from "react"; -import { Button, type ButtonProps } from "./buttons.js"; -import { Spinner } from "./Spinner.js"; - -export function SwitchNetworkButton( - props: ButtonProps & { - fullWidth?: boolean; - switchChain: () => Promise; - }, -) { - const [isSwitching, setIsSwitching] = useState(false); - - return ( - - ); -} diff --git a/packages/thirdweb/src/react/web/ui/components/buttons.tsx b/packages/thirdweb/src/react/web/ui/components/buttons.tsx index 70af6273071..82c242dfcc7 100644 --- a/packages/thirdweb/src/react/web/ui/components/buttons.tsx +++ b/packages/thirdweb/src/react/web/ui/components/buttons.tsx @@ -8,7 +8,7 @@ import { } from "../../../core/design-system/index.js"; import { StyledButton } from "../design-system/elements.js"; -export type ButtonProps = { +type ButtonProps = { variant: "primary" | "secondary" | "link" | "accent" | "outline" | "ghost"; unstyled?: boolean; fullWidth?: boolean; @@ -38,7 +38,6 @@ export const Button = /* @__PURE__ */ StyledButton((props: ButtonProps) => { cursor: "not-allowed", }, alignItems: "center", - all: "unset", background: (() => { if (props.bg) { return theme.colors[props.bg]; @@ -152,7 +151,6 @@ export const IconButton = /* @__PURE__ */ StyledButton((_) => { cursor: "not-allowed", }, alignItems: "center", - all: "unset", borderRadius: radius.sm, color: theme.colors.secondaryIconColor, cursor: "pointer", diff --git a/packages/thirdweb/src/react/web/ui/components/text.tsx b/packages/thirdweb/src/react/web/ui/components/text.tsx index 63ad1a8cc09..4026d33aa20 100644 --- a/packages/thirdweb/src/react/web/ui/components/text.tsx +++ b/packages/thirdweb/src/react/web/ui/components/text.tsx @@ -48,7 +48,6 @@ export const Link = /* @__PURE__ */ StyledAnchor((p) => { color: theme.colors[p.hoverColor || "primaryText"], textDecoration: "none", }, - all: "unset", color: theme.colors[p.color || "accentText"], cursor: "pointer", display: p.inline ? "inline" : "block", diff --git a/packages/thirdweb/src/react/web/ui/components/token/TokenRow.tsx b/packages/thirdweb/src/react/web/ui/components/token/TokenRow.tsx deleted file mode 100644 index a0b2cba2b87..00000000000 --- a/packages/thirdweb/src/react/web/ui/components/token/TokenRow.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import styled from "@emotion/styled"; -import { ChevronDownIcon } from "@radix-ui/react-icons"; -import type { Chain } from "../../../../../chains/types.js"; -import type { ThirdwebClient } from "../../../../../client/client.js"; -import { formatNumber } from "../../../../../utils/formatNumber.js"; -import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvider.js"; -import { - fontSize, - iconSize, - spacing, -} from "../../../../core/design-system/index.js"; -import { useChainName } from "../../../../core/hooks/others/useChainQuery.js"; -import { PayTokenIcon } from "../../ConnectWallet/screens/Buy/PayTokenIcon.js"; -import type { ERC20OrNativeToken } from "../../ConnectWallet/screens/nativeToken.js"; -import { Container } from "../basic.js"; -import { Button } from "../buttons.js"; -import { Skeleton } from "../Skeleton.js"; -import { Text } from "../text.js"; -import { TokenSymbol } from "./TokenSymbol.js"; - -export function TokenRow(props: { - token: ERC20OrNativeToken | undefined; - chain: Chain | undefined; - client: ThirdwebClient; - onSelectToken: () => void; - freezeChainAndToken?: boolean; - value?: string; - isLoading?: boolean; - style?: React.CSSProperties; -}) { - const { name } = useChainName(props.chain); - - if (!props.token || !props.chain) { - return ( - - ); - } - - return ( - - - - - - {/* Token Symbol */} - - {props.isLoading ? ( - - ) : props.value ? ( - - - {formatNumber(Number(props.value), 6) || ""} - - - - ) : ( - - )} - - - {/* Network Name */} - {name ? ( - - {name} - - ) : ( - - )} - - - {!props.freezeChainAndToken && ( - - - - )} - - ); -} - -const TokenButton = /* @__PURE__ */ styled(Button)(() => { - const theme = useCustomTheme(); - return { - background: theme.colors.tertiaryBg, - border: `1px solid ${theme.colors.borderColor}`, - justifyContent: "space-between", - padding: spacing.sm, - transition: "background 0.3s", - }; -}); diff --git a/packages/thirdweb/src/react/web/utils/errors.ts b/packages/thirdweb/src/react/web/utils/errors.ts deleted file mode 100644 index 55cd5df53c9..00000000000 --- a/packages/thirdweb/src/react/web/utils/errors.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ApiError } from "../../../bridge/types/Errors.js"; - -type UiError = { - code: string; - title: string; - message: string; -}; - -export function getErrorMessage(err: unknown): UiError { - if (err instanceof ApiError) { - if (err.code === "INTERNAL_SERVER_ERROR") { - return { - code: "INTERNAL_SERVER_ERROR", - message: "An unknown error occurred. Please try again.", - title: "Failed to Find Quote", - }; - } - return { - code: err.code, - message: getErrorMessageFromBridgeApiError(err), - title: "Failed to Find Quote", - }; - } - - if (err instanceof Error) { - return { - code: "UNABLE_TO_GET_PRICE_QUOTE", - message: - err.message || - "We couldn't get a quote for this token pair. Select another token or pay with card.", - title: "Failed to Find Quote", - }; - } - - return { - code: "UNABLE_TO_GET_PRICE_QUOTE", - message: - "We couldn't get a quote for this token pair. Select another token or pay with card.", - title: "Failed to Find Quote", - }; -} - -function getErrorMessageFromBridgeApiError(err: ApiError) { - let msg = err.message; - if (msg.includes("Details")) { - msg = msg.substring(0, msg.indexOf("Details")); - } - if (msg.includes("{")) { - msg = msg.substring(0, msg.indexOf("{")); - } - return msg; -} diff --git a/packages/thirdweb/src/react/web/wallets/shared/GetStartedScreen.tsx b/packages/thirdweb/src/react/web/wallets/shared/GetStartedScreen.tsx index 26acec24779..126a64e61c6 100644 --- a/packages/thirdweb/src/react/web/wallets/shared/GetStartedScreen.tsx +++ b/packages/thirdweb/src/react/web/wallets/shared/GetStartedScreen.tsx @@ -218,7 +218,6 @@ const ButtonLink = /* @__PURE__ */ StyledButton((_) => { textDecoration: "none", }, alignItems: "center", - all: "unset", background: theme.colors.secondaryButtonBg, borderRadius: radius.sm, boxSizing: "border-box", diff --git a/packages/thirdweb/src/react/web/wallets/shared/OTPLoginUI.tsx b/packages/thirdweb/src/react/web/wallets/shared/OTPLoginUI.tsx index bb173048bb5..270ecb8233a 100644 --- a/packages/thirdweb/src/react/web/wallets/shared/OTPLoginUI.tsx +++ b/packages/thirdweb/src/react/web/wallets/shared/OTPLoginUI.tsx @@ -345,7 +345,6 @@ const LinkButton = /* @__PURE__ */ StyledButton((_) => { "&:hover": { color: theme.colors.primaryText, }, - all: "unset", color: theme.colors.accentText, cursor: "pointer", fontSize: fontSize.sm,