diff --git a/apps/playground-web/src/app/insight/utils.ts b/apps/playground-web/src/app/insight/utils.ts
index 360e8f9a267..a9503422a65 100644
--- a/apps/playground-web/src/app/insight/utils.ts
+++ b/apps/playground-web/src/app/insight/utils.ts
@@ -55,17 +55,22 @@ export async function fetchBlueprintSpec(params: {
}
export async function fetchAllBlueprints() {
- // fetch list
- const blueprintSpecs = await fetchBlueprintList();
-
- // fetch all blueprints
- const blueprints = await Promise.all(
- blueprintSpecs.map((spec) =>
- fetchBlueprintSpec({
- blueprintId: spec.id,
- }),
- ),
- );
-
- return blueprints;
+ try {
+ // fetch list
+ const blueprintSpecs = await fetchBlueprintList();
+
+ // fetch all blueprints
+ const blueprints = await Promise.all(
+ blueprintSpecs.map((spec) =>
+ fetchBlueprintSpec({
+ blueprintId: spec.id,
+ }),
+ ),
+ );
+
+ return blueprints;
+ } catch (error) {
+ console.error(error);
+ return [];
+ }
}
diff --git a/apps/playground-web/src/components/pay/direct-payment.tsx b/apps/playground-web/src/components/pay/direct-payment.tsx
index 09879b44a77..3641f94cbfd 100644
--- a/apps/playground-web/src/components/pay/direct-payment.tsx
+++ b/apps/playground-web/src/components/pay/direct-payment.tsx
@@ -1,6 +1,5 @@
"use client";
-
-import { sepolia } from "thirdweb/chains";
+import { base } from "thirdweb/chains";
import { PayEmbed, getDefaultToken } from "thirdweb/react";
import { THIRDWEB_CLIENT } from "../../lib/client";
import { StyledConnectButton } from "../styled-connect-button";
@@ -16,9 +15,9 @@ export function BuyMerchPreview() {
payOptions={{
mode: "direct_payment",
paymentInfo: {
- amount: "0.1",
- chain: sepolia,
- token: getDefaultToken(sepolia, "USDC"),
+ amount: "2",
+ chain: base,
+ token: getDefaultToken(base, "USDC"),
sellerAddress: "0xEb0effdFB4dC5b3d5d3aC6ce29F3ED213E95d675",
},
metadata: {
diff --git a/apps/playground-web/src/components/pay/transaction-button.tsx b/apps/playground-web/src/components/pay/transaction-button.tsx
index ee2404a6932..82cebc4effc 100644
--- a/apps/playground-web/src/components/pay/transaction-button.tsx
+++ b/apps/playground-web/src/components/pay/transaction-button.tsx
@@ -2,7 +2,7 @@
import { useTheme } from "next-themes";
import { getContract } from "thirdweb";
-import { base, sepolia } from "thirdweb/chains";
+import { base, polygon } from "thirdweb/chains";
import { transfer } from "thirdweb/extensions/erc20";
import { claimTo, getNFT } from "thirdweb/extensions/erc1155";
import {
@@ -21,12 +21,12 @@ const nftContract = getContract({
client: THIRDWEB_CLIENT,
});
-const USDC = getDefaultToken(sepolia, "USDC");
+const USDC = getDefaultToken(polygon, "USDC");
const usdcContract = getContract({
// biome-ignore lint/style/noNonNullAssertion: its there
address: USDC!.address,
- chain: sepolia,
+ chain: polygon,
client: THIRDWEB_CLIENT,
});
@@ -55,6 +55,7 @@ export function PayTransactionPreview() {
to: account?.address || "",
}),
metadata: nft?.metadata,
+ buyWithFiat: false,
}}
/>
)}
@@ -69,6 +70,7 @@ export function PayTransactionButtonPreview() {
return (
<>
+
{account && (
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
index 507fb089f88..9984a284ae0 100644
--- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx
+++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx
@@ -8,11 +8,9 @@ import type { BuyWithFiatStatus } from "../../../../../../pay/buyWithFiat/getSta
import { formatNumber } from "../../../../../../utils/formatNumber.js";
import type { Account } from "../../../../../../wallets/interfaces/wallet.js";
import type { WalletId } from "../../../../../../wallets/wallet-types.js";
-import { useCustomTheme } from "../../../../../core/design-system/CustomThemeProvider.js";
import {
type Theme,
fontSize,
- radius,
spacing,
} from "../../../../../core/design-system/index.js";
import type {
@@ -27,10 +25,9 @@ import { LoadingScreen } from "../../../../wallets/shared/LoadingScreen.js";
import type { PayEmbedConnectOptions } from "../../../PayEmbed.js";
import { ChainName } from "../../../components/ChainName.js";
import { Spacer } from "../../../components/Spacer.js";
-import { Container, ModalHeader } from "../../../components/basic.js";
+import { Container, Line, ModalHeader } from "../../../components/basic.js";
import { Button } from "../../../components/buttons.js";
import { Input } from "../../../components/formElements.js";
-import { Text } from "../../../components/text.js";
import { TokenSymbol } from "../../../components/token/TokenSymbol.js";
import { ConnectButton } from "../../ConnectButton.js";
import { ChainButton, NetworkSelectorContent } from "../../NetworkSelector.js";
@@ -432,67 +429,6 @@ function BuyScreenContent(props: BuyScreenContentProps) {
);
}
- if (
- screen.id === "select-from-token" &&
- supportedSourcesQuery.data &&
- sourceSupportedTokens
- ) {
- const chains = supportedSourcesQuery.data.map((x) => x.chain);
- const goBack = () => setScreen(screen.backScreen);
- // if token selection is disabled - only show network selector screen
- if (
- payOptions.buyWithCrypto !== false &&
- payOptions.buyWithCrypto?.prefillSource?.allowEdits?.token === false
- ) {
- return (
-
- );
- }
-
- return (
-
{
- setScreen({
- id: "connect-payer-wallet",
- backScreen: screen,
- });
- }}
- modalTitle="Available tokens"
- connectLocale={props.connectLocale}
- client={props.client}
- sourceTokens={sourceSupportedTokens}
- sourceSupportedTokens={sourceSupportedTokens}
- toChain={toChain}
- toToken={toToken}
- tokenAmount={tokenAmount}
- fromChain={fromChain}
- fromToken={fromToken}
- mode={payOptions.mode}
- hiddenWallets={props.hiddenWallets}
- onSelect={(w, token, chain) => {
- const account = w.getAccount();
- if (account) {
- setPayer({
- account,
- chain,
- wallet: w,
- });
- setFromToken(token);
- setFromChain(chain);
- }
- goBack();
- }}
- />
- );
- }
-
return (
@@ -526,19 +462,10 @@ function BuyScreenContent(props: BuyScreenContentProps) {
{(screen.id === "select-payment-method" ||
screen.id === "buy-with-crypto" ||
- screen.id === "buy-with-fiat") &&
+ screen.id === "buy-with-fiat" ||
+ screen.id === "select-from-token") &&
payer && (
{
- setScreen({
- id: mode === "swap" ? "buy-with-crypto" : "buy-with-fiat",
- });
- }}
disabled={
("prefillBuy" in payOptions &&
payOptions.prefillBuy?.allowEdits?.amount === false) ||
@@ -551,7 +478,19 @@ function BuyScreenContent(props: BuyScreenContentProps) {
setTokenAmount={setTokenAmount}
client={client}
onBack={() => {
- setScreen({ id: "main" });
+ if (
+ screen.id === "buy-with-crypto" ||
+ screen.id === "buy-with-fiat"
+ ) {
+ setScreen({
+ id: "select-from-token",
+ backScreen: { id: "main" },
+ });
+ } else if (screen.id === "select-from-token") {
+ setScreen(screen.backScreen);
+ } else {
+ setScreen({ id: "main" });
+ }
}}
>
{screen.id === "buy-with-crypto" && activeAccount && (
@@ -614,6 +553,45 @@ function BuyScreenContent(props: BuyScreenContentProps) {
setHasEditedAmount={setHasEditedAmount}
/>
)}
+
+ {screen.id === "select-from-token" &&
+ supportedSourcesQuery.data &&
+ sourceSupportedTokens && (
+ {
+ setScreen({
+ id: "connect-payer-wallet",
+ backScreen: screen,
+ });
+ }}
+ 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" });
+ }}
+ />
+ )}
)}
@@ -802,7 +780,10 @@ function MainScreen(props: {
if (buyWithFiatEnabled && !buyWithCryptoEnabled) {
props.setScreen({ id: "buy-with-fiat" });
} else {
- props.setScreen({ id: "buy-with-crypto" });
+ props.setScreen({
+ id: "select-from-token",
+ backScreen: { id: "main" },
+ });
}
}}
/>
@@ -825,7 +806,10 @@ function MainScreen(props: {
if (buyWithFiatEnabled && !buyWithCryptoEnabled) {
props.setScreen({ id: "buy-with-fiat" });
} else {
- props.setScreen({ id: "buy-with-crypto" });
+ props.setScreen({
+ id: "select-from-token",
+ backScreen: { id: "main" },
+ });
}
}}
/>
@@ -888,7 +872,10 @@ function MainScreen(props: {
if (buyWithFiatEnabled && !buyWithCryptoEnabled) {
props.setScreen({ id: "buy-with-fiat" });
} else {
- props.setScreen({ id: "buy-with-crypto" });
+ props.setScreen({
+ id: "select-from-token",
+ backScreen: { id: "main" },
+ });
}
}}
>
@@ -912,12 +899,7 @@ function TokenSelectedLayout(props: {
client: ThirdwebClient;
onBack: () => void;
disabled?: boolean;
- mode: "buy" | "swap";
- onModeChange: (mode: "buy" | "swap") => void;
- isBuyWithFiatEnabled: boolean;
- isBuyWithCryptoEnabled: boolean;
}) {
- const theme = useCustomTheme();
return (
@@ -940,78 +922,9 @@ function TokenSelectedLayout(props: {
disabled={props.disabled}
/>
-
-
- Pay with
- {props.isBuyWithFiatEnabled && props.isBuyWithCryptoEnabled && (
-
-
-
-
-
- )}
-
-
-
-
+
+
+
{props.children}
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
index 2e7a56b0caf..5970ad46fbd 100644
--- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx
+++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx
@@ -33,7 +33,7 @@ import {
isNativeToken,
} from "../nativeToken.js";
import { useTransactionCostAndData } from "./main/useBuyTxStates.js";
-import { WalletRow } from "./swap/TokenSelectorScreen.js";
+import { WalletRow } from "./swap/WalletRow.js";
import type { SupportedChainAndTokens } from "./swap/useSwapSupportedChains.js";
export function TransactionModeScreen(props: {
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
index 6447e65958c..affe26a0156 100644
--- 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
@@ -199,6 +199,7 @@ export function FiatScreenContent(props: {
)}
+ Pay with credit card
- Pay
+ Pay
x.chain.id !== 1) // dont use mainnet as a default source, unless its the only source
- : undefined;
+ // TODO (pay) - auto select token based on connected wallet balances
// Source token and chain selection ---------------------------------------------------
const [fromChain_, setFromChain] = useState();
@@ -95,12 +90,7 @@ export function useFromTokenSelectionStates(options: {
(payOptions.mode === "transaction" && payOptions.transaction?.chain) ||
(payOptions.mode === "direct_payment" && payOptions.paymentInfo?.chain);
- const fromChainFromApi = firstSupportedSource?.chain
- ? firstSupportedSource.chain
- : undefined;
-
- const fromChain =
- fromChain_ || fromChainDevSpecified || fromChainFromApi || polygon;
+ const fromChain = fromChain_ || fromChainDevSpecified || undefined;
const [fromToken_, setFromToken] = useState();
@@ -110,12 +100,8 @@ export function useFromTokenSelectionStates(options: {
payOptions.buyWithCrypto?.prefillSource?.token) ||
(payOptions.mode === "direct_payment" && payOptions.paymentInfo.token);
- // May be updated in the future
- const fromTokenFromApi = NATIVE_TOKEN;
-
// supported tokens query in here
- const fromToken =
- fromToken_ || fromTokenDevSpecified || fromTokenFromApi || NATIVE_TOKEN;
+ const fromToken = fromToken_ || fromTokenDevSpecified || undefined;
return {
fromChain,
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
index 89d0777e772..cd930784600 100644
--- 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
@@ -12,29 +12,20 @@ import {
type WaitForReceiptOptions,
waitForReceipt,
} from "../../../../../../../transaction/actions/wait-for-tx-receipt.js";
-import { shortenAddress } from "../../../../../../../utils/address.js";
-import { formatNumber } from "../../../../../../../utils/formatNumber.js";
import { useCustomTheme } from "../../../../../../core/design-system/CustomThemeProvider.js";
-import {
- fontSize,
- iconSize,
-} from "../../../../../../core/design-system/index.js";
-import { useChainName } from "../../../../../../core/hooks/others/useChainQuery.js";
-import { useEnsName } from "../../../../../../core/utils/wallet.js";
-import { Skeleton } from "../../../../components/Skeleton.js";
+import { iconSize } from "../../../../../../core/design-system/index.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 { Container, Line, ModalHeader } from "../../../../components/basic.js";
+import { Container, ModalHeader } from "../../../../components/basic.js";
import { Button } from "../../../../components/buttons.js";
import { Text } from "../../../../components/text.js";
import { StyledDiv } from "../../../../design-system/elements.js";
import type { ERC20OrNativeToken } from "../../nativeToken.js";
-import { PayTokenIcon } from "../PayTokenIcon.js";
import { Step } from "../Stepper.js";
import type { PayerInfo } from "../types.js";
-import { formatSeconds } from "./formatSeconds.js";
+import { SwapSummary } from "./SwapSummary.js";
import { addPendingTx } from "./pendingSwapTx.js";
/**
@@ -74,9 +65,6 @@ export function SwapConfirmationScreen(props: {
const receiver = props.quote.swapDetails.toAddress;
const sender = props.quote.swapDetails.fromAddress;
- const isDifferentRecipient = receiver.toLowerCase() !== sender.toLowerCase();
-
- const ensName = useEnsName({ client: props.client, address: receiver });
return (
@@ -94,58 +82,26 @@ export function SwapConfirmationScreen(props: {
>
) : (
-
- )}
-
- {/* Pay */}
-
-
-
-
- {/* Receive */}
- {!isDifferentRecipient && (
-
-
-
+ <>
+
+ Confirm payment
+
+ >
)}
- {/* Fees */}
-
-
-
-
- {/* Time */}
-
-
- ~
- {formatSeconds(
- props.quote.swapDetails.estimated.durationSeconds || 0,
- )}
-
-
-
- {/* Send to */}
- {isDifferentRecipient && (
-
-
- {ensName.data || shortenAddress(receiver)}
-
-
- )}
+
-
+
{/* Show 2 steps - Approve and confirm */}
{needsApprovalStep && (
@@ -348,100 +304,3 @@ export const ConnectorLine = /* @__PURE__ */ StyledDiv(() => {
flex: 1,
};
});
-
-function RenderTokenInfo(props: {
- chain: Chain;
- token: ERC20OrNativeToken;
- amount: string;
- symbol: string;
- client: ThirdwebClient;
-}) {
- const { name } = useChainName(props.chain);
- return (
-
-
-
- {props.amount} {props.symbol}
-
-
-
-
- {name ? (
- {name}
- ) : (
-
- )}
-
- );
-}
-
-function ConfirmItem(props: {
- label: string;
- children: React.ReactNode;
-}) {
- return (
- <>
-
-
- {props.label}
-
- {props.children}
-
-
- >
- );
-}
-
-/**
- * @internal
- */
-function SwapFeesRightAligned(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)})
-
-
- );
- })}
-
- );
-}
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
index ca32442f9d8..8a535e2e5ab 100644
--- 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
@@ -16,7 +16,7 @@ import { TokenRow } from "../../../../components/token/TokenRow.js";
import { TokenSymbol } from "../../../../components/token/TokenSymbol.js";
import { formatTokenBalance } from "../../formatTokenBalance.js";
import { type NativeToken, isNativeToken } from "../../nativeToken.js";
-import { WalletRow } from "./TokenSelectorScreen.js";
+import { WalletRow } from "./WalletRow.js";
/**
* Shows an amount "value" and renders the selected token and chain
@@ -26,8 +26,8 @@ import { WalletRow } from "./TokenSelectorScreen.js";
*/
export function PayWithCryptoQuoteInfo(props: {
value: string;
- chain: Chain;
- token: TokenInfo | NativeToken;
+ chain: Chain | undefined;
+ token: TokenInfo | NativeToken | undefined;
isLoading: boolean;
client: ThirdwebClient;
freezeChainAndTokenSelection?: boolean;
@@ -36,12 +36,19 @@ export function PayWithCryptoQuoteInfo(props: {
onSelectToken: () => void;
}) {
const theme = useCustomTheme();
- const balanceQuery = useWalletBalance({
- address: props.payerAccount.address,
- chain: props.chain,
- tokenAddress: isNativeToken(props.token) ? undefined : props.token.address,
- client: props.client,
- });
+ const balanceQuery = useWalletBalance(
+ {
+ address: props.payerAccount.address,
+ chain: props.chain,
+ tokenAddress: isNativeToken(props.token)
+ ? undefined
+ : props.token?.address,
+ client: props.client,
+ },
+ {
+ enabled: !!props.chain && !!props.token,
+ },
+ );
return (
- {balanceQuery.data ? (
+ {props.token && props.chain && balanceQuery.data ? (
- {formatTokenBalance(balanceQuery.data, false)}
+ {formatTokenBalance(balanceQuery.data, false, 4)}
- ) : (
+ ) : props.token && props.chain && balanceQuery.isLoading ? (
- )}
+ ) : null}
- {/* Quoted price */}
+ {/* Quoted price & token selector */}
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
index 0c954b4fe48..bdddf776f0a 100644
--- 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
@@ -42,8 +42,8 @@ export function SwapScreenContent(props: {
tokenAmount: string;
toToken: ERC20OrNativeToken;
toChain: Chain;
- fromChain: Chain;
- fromToken: ERC20OrNativeToken;
+ fromChain: Chain | undefined;
+ fromToken: ERC20OrNativeToken | undefined;
showFromTokenSelector: () => void;
payer: PayerInfo;
client: ThirdwebClient;
@@ -81,42 +81,50 @@ export function SwapScreenContent(props: {
"fees" | "receiver" | "payer"
>("fees");
- const fromTokenBalanceQuery = useWalletBalance({
- address: payer.account.address,
- chain: fromChain,
- tokenAddress: isNativeToken(fromToken) ? undefined : fromToken.address,
- client,
- });
+ const fromTokenBalanceQuery = useWalletBalance(
+ {
+ address: payer.account.address,
+ chain: fromChain,
+ tokenAddress: isNativeToken(fromToken) ? undefined : fromToken?.address,
+ client,
+ },
+ {
+ enabled: !!fromChain && !!fromToken,
+ },
+ );
const fromTokenId = isNativeToken(fromToken)
? NATIVE_TOKEN_ADDRESS
- : fromToken.address.toLowerCase();
+ : fromToken?.address?.toLowerCase();
const toTokenId = isNativeToken(toToken)
? NATIVE_TOKEN_ADDRESS
: toToken.address.toLowerCase();
const swapRequired =
!!tokenAmount &&
- !(fromChain.id === toChain.id && fromTokenId === toTokenId);
- const quoteParams: GetBuyWithCryptoQuoteParams | undefined = swapRequired
- ? {
- // wallets
- fromAddress: payer.account.address,
- toAddress: receiverAddress,
- // from
- fromChainId: fromChain.id,
- fromTokenAddress: isNativeToken(fromToken)
- ? NATIVE_TOKEN_ADDRESS
- : fromToken.address,
- // to
- toChainId: toChain.id,
- toTokenAddress: isNativeToken(toToken)
- ? NATIVE_TOKEN_ADDRESS
- : toToken.address,
- toAmount: tokenAmount,
- client,
- purchaseData: payOptions.purchaseData,
- }
- : undefined;
+ !!fromChain &&
+ !!fromTokenId &&
+ !(fromChain?.id === toChain.id && fromTokenId === toTokenId);
+ const quoteParams: GetBuyWithCryptoQuoteParams | undefined =
+ fromChain && fromToken && swapRequired
+ ? {
+ // wallets
+ fromAddress: payer.account.address,
+ toAddress: receiverAddress,
+ // from
+ fromChainId: fromChain.id,
+ fromTokenAddress: isNativeToken(fromToken)
+ ? NATIVE_TOKEN_ADDRESS
+ : fromToken.address,
+ // to
+ toChainId: toChain.id,
+ toTokenAddress: isNativeToken(toToken)
+ ? NATIVE_TOKEN_ADDRESS
+ : toToken.address,
+ toAmount: tokenAmount,
+ client,
+ purchaseData: payOptions.purchaseData,
+ }
+ : undefined;
const quoteQuery = useBuyWithCryptoQuote(quoteParams, {
// refetch every 30 seconds
@@ -159,11 +167,13 @@ export function SwapScreenContent(props: {
Number(fromTokenBalanceQuery.data.displayValue) < Number(sourceTokenAmount);
const disableContinue =
+ !fromChain ||
+ !fromToken ||
(swapRequired && !quoteQuery.data) ||
isNotEnoughBalance ||
allowanceQuery.isLoading;
const switchChainRequired =
- props.payer.wallet.getChain()?.id !== fromChain.id;
+ props.payer.wallet.getChain()?.id !== fromChain?.id;
const errorMsg =
!quoteQuery.isLoading && quoteQuery.error
@@ -240,6 +250,19 @@ export function SwapScreenContent(props: {
{/* Quote info */}
+
+ Pay with
+ {fromToken && fromChain ? (
+
+ ) : (
+ "crypto"
+ )}
+
- {swapRequired && (
+ {swapRequired && fromChain && fromToken && (
- Not enough funds.
+ Insufficient funds
)}
@@ -317,9 +340,10 @@ export function SwapScreenContent(props: {
fullWidth
onClick={() => props.showFromTokenSelector()}
>
- Select another token
+ Pay with another token
) : switchChainRequired &&
+ fromChain &&
!quoteQuery.isLoading &&
!allowanceQuery.isLoading &&
!isNotEnoughBalance &&
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
new file mode 100644
index 00000000000..a4af2b4c07c
--- /dev/null
+++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapSummary.tsx
@@ -0,0 +1,128 @@
+import { ChevronDownIcon } from "@radix-ui/react-icons";
+import type { Chain } from "../../../../../../../chains/types.js";
+import type { ThirdwebClient } from "../../../../../../../client/client.js";
+import { useCustomTheme } from "../../../../../../core/design-system/CustomThemeProvider.js";
+import {
+ iconSize,
+ 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 { 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 */}
+
+ {isDifferentRecipient && (
+
+
+
+ )}
+ {}}
+ style={{
+ background: "transparent",
+ borderRadius: 0,
+ border: "none",
+ }}
+ />
+
+ {/* Connector Icon */}
+
+
+
+
+
+
+ {/* Buy */}
+
+ {isDifferentRecipient && (
+
+
+
+ )}
+ {}}
+ style={{
+ background: "transparent",
+ borderRadius: 0,
+ border: "none",
+ borderTop: `1px solid ${theme.colors.borderColor}`,
+ }}
+ />
+
+
+ );
+}
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
index 853576cc1de..a7c0a0efdbc 100644
--- 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
@@ -1,10 +1,14 @@
import styled from "@emotion/styled";
+import {
+ CardStackIcon,
+ ChevronRightIcon,
+ Cross2Icon,
+} from "@radix-ui/react-icons";
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 { shortenAddress } from "../../../../../../../utils/address.js";
import type { Wallet } from "../../../../../../../wallets/interfaces/wallet.js";
import {
type GetWalletBalanceResult,
@@ -13,7 +17,6 @@ import {
import type { WalletId } from "../../../../../../../wallets/wallet-types.js";
import { useCustomTheme } from "../../../../../../core/design-system/CustomThemeProvider.js";
import {
- type fontSize,
iconSize,
radius,
spacing,
@@ -25,28 +28,23 @@ import {
} from "../../../../../../core/hooks/others/useChainQuery.js";
import { useActiveAccount } from "../../../../../../core/hooks/wallets/useActiveAccount.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 {
- useEnsAvatar,
- useEnsName,
-} from "../../../../../../core/utils/wallet.js";
import { LoadingScreen } from "../../../../../wallets/shared/LoadingScreen.js";
-import { Img } from "../../../../components/Img.js";
+import { Spacer } from "../../../../components/Spacer.js";
import { TextDivider } from "../../../../components/TextDivider.js";
import { TokenIcon } from "../../../../components/TokenIcon.js";
-import { WalletImage } from "../../../../components/WalletImage.js";
-import { Container, Line, ModalHeader } from "../../../../components/basic.js";
+import { Container } from "../../../../components/basic.js";
import { Button } from "../../../../components/buttons.js";
import { Text } from "../../../../components/text.js";
-import { Blobbie } from "../../../Blobbie.js";
import { OutlineWalletIcon } from "../../../icons/OutlineWalletIcon.js";
-import type { ConnectLocale } from "../../../locale/types.js";
import { formatTokenBalance } from "../../formatTokenBalance.js";
import { type ERC20OrNativeToken, isNativeToken } from "../../nativeToken.js";
import { FiatValue } from "./FiatValue.js";
+import { WalletRow } from "./WalletRow.js";
type TokenBalance = {
balance: GetWalletBalanceResult;
@@ -54,28 +52,28 @@ type TokenBalance = {
token: TokenInfo;
};
+type WalletKey = {
+ id: WalletId;
+ address: string;
+};
+
export function TokenSelectorScreen(props: {
client: ThirdwebClient;
sourceTokens: SupportedTokens | undefined;
sourceSupportedTokens: SupportedTokens | undefined;
toChain: Chain;
toToken: ERC20OrNativeToken;
- fromToken: ERC20OrNativeToken;
- fromChain: Chain;
tokenAmount: string;
mode: PayUIOptions["mode"];
hiddenWallets?: WalletId[];
- onSelect: (wallet: Wallet, token: TokenInfo, chain: Chain) => void;
- onBack: () => void;
+ onSelectToken: (wallet: Wallet, token: TokenInfo, chain: Chain) => void;
onConnect: () => void;
- modalTitle?: string;
- connectLocale: ConnectLocale;
+ onPayWithFiat: () => void;
}) {
const connectedWallets = useConnectedWallets();
const activeAccount = useActiveAccount();
const chainInfo = useChainMetadata(props.toChain);
const theme = useCustomTheme();
- const locale = props.connectLocale.sendFundsScreen;
const walletsAndBalances = useQuery({
queryKey: [
@@ -90,12 +88,16 @@ export function TokenSelectorScreen(props: {
],
queryFn: async () => {
// in parallel, get the balances of all the wallets on each of the sourceSupportedTokens
- const walletBalanceMap = new Map();
+ const walletBalanceMap = new Map();
const balancePromises = connectedWallets.flatMap((wallet) => {
const account = wallet.getAccount();
if (!account) return [];
- walletBalanceMap.set(wallet, []);
+ const walletKey: WalletKey = {
+ id: wallet.id,
+ address: account.address,
+ };
+ walletBalanceMap.set(walletKey, []);
// inject the destination token too since it can be used as well to pay/transfer
const toToken = isNativeToken(props.toToken)
@@ -139,7 +141,7 @@ export function TokenSelectorScreen(props: {
: balance.value > 0n;
if (shouldInclude) {
- const existingBalances = walletBalanceMap.get(wallet) || [];
+ const existingBalances = walletBalanceMap.get(walletKey) || [];
existingBalances.push({ balance, chain, token });
existingBalances.sort((a, b) => {
if (
@@ -188,47 +190,44 @@ export function TokenSelectorScreen(props: {
-
-
-
-
-
-
+ {filteredWallets.length === 0 ? (
+
+
+ No suitable payment token found
+
+ in connected wallets
+
+
+ ) : (
+
+ Select payment token
+
+
+ )}
-
- {filteredWallets.length === 0 && (
-
-
-
- No suitable payment token found
-
- in connected wallets
-
-
-
- )}
+
{filteredWallets.map(([w, balances]) => {
- const address = w.getAccount()?.address;
- if (!address) return null;
+ const address = w.address;
+ const wallet = connectedWallets.find(
+ (w) => w.getAccount()?.address === address,
+ );
+ if (!wallet) return null;
return (
);
})}
@@ -237,10 +236,8 @@ export function TokenSelectorScreen(props: {
variant="secondary"
fullWidth
onClick={props.onConnect}
- gap="xs"
bg="tertiaryBg"
style={{
- borderRadius: radius.md,
border: `1px solid ${theme.colors.borderColor}`,
padding: spacing.sm,
}}
@@ -254,7 +251,30 @@ export function TokenSelectorScreen(props: {
>
- Connect another wallet
+ Pay with another wallet
+
+
+
+
@@ -272,28 +292,74 @@ function WalletRowWithBalances(props: {
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) => (
+ displayedBalances.map((b, idx) => (
props.onClick(props.wallet, b.token, b.chain)}
key={`${b.token.address}-${b.chain.id}`}
tokenBalance={b}
wallet={props.wallet}
+ style={{
+ borderTopLeftRadius: 0,
+ borderTopRightRadius: 0,
+ borderBottomRightRadius:
+ idx === displayedBalances.length - 1 ? radius.lg : 0,
+ borderBottomLeftRadius:
+ idx === displayedBalances.length - 1 ? radius.lg : 0,
+ borderBottom:
+ idx === displayedBalances.length - 1
+ ? "none"
+ : `1px solid ${theme.colors.borderColor}`,
+ }}
/>
))
) : (
- Not enough funds
+ Insufficient funds
)}
@@ -307,33 +373,35 @@ function TokenBalanceRow(props: {
tokenBalance: TokenBalance;
wallet: Wallet;
onClick: (token: TokenInfo, wallet: Wallet) => void;
+ style?: React.CSSProperties;
}) {
- const { tokenBalance, wallet, onClick, client } = props;
+ const { tokenBalance, wallet, onClick, client, style } = props;
const chainInfo = useChainName(tokenBalance.chain);
return (
onClick(tokenBalance.token, wallet)}
variant="secondary"
+ style={style}
>
-
+
-
+
{tokenBalance.token.symbol}
{chainInfo && {chainInfo.name}}
-
+
- {/* */}
+
);
}
-export function WalletRow(props: {
- client: ThirdwebClient;
- address: string;
- iconSize?: keyof typeof iconSize;
- textSize?: keyof typeof fontSize;
- walletId?: WalletId;
- wallet?: Wallet;
-}) {
- const { client, address } = props;
- const walletId = props.walletId;
- const theme = useCustomTheme();
- const ensNameQuery = useEnsName({
- client,
- address,
- });
- const addressOrENS = ensNameQuery.data || shortenAddress(address);
- const ensAvatarQuery = useEnsAvatar({
- client,
- ensName: ensNameQuery.data,
- });
- return (
-
-
- {ensAvatarQuery.data ? (
-
- ) : walletId ? (
-
- ) : (
-
-
-
- )}
-
-
- {addressOrENS || shortenAddress(props.address)}
-
-
-
- );
-}
-
-const StyledButton = /* @__PURE__ */ styled(Button)((_) => {
+const StyledButton = /* @__PURE__ */ styled(Button)((props) => {
const theme = useCustomTheme();
return {
- background: theme.colors.tertiaryBg,
+ background: "transparent",
justifyContent: "space-between",
flexDirection: "row",
padding: spacing.sm,
- border: `1px solid ${theme.colors.borderColor}`,
+ paddingRight: spacing.xs,
gap: spacing.sm,
"&:hover": {
background: theme.colors.secondaryButtonBg,
transform: "scale(1.01)",
},
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
index f54095afb59..1f40f2662eb 100644
--- 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
@@ -16,20 +16,18 @@ 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 { useChainSymbol } from "../../../../../../core/hooks/others/useChainQuery.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 { Container, Line, ModalHeader } from "../../../../components/basic.js";
+import { Container, ModalHeader } from "../../../../components/basic.js";
import { Button } from "../../../../components/buttons.js";
import { Text } from "../../../../components/text.js";
import { type ERC20OrNativeToken, isNativeToken } from "../../nativeToken.js";
import { Step } from "../Stepper.js";
-import { TokenInfoRow } from "../pay-transactions/TokenInfoRow.js";
import type { PayerInfo } from "../types.js";
import { ConnectorLine } from "./ConfirmationScreen.js";
-import { WalletRow } from "./TokenSelectorScreen.js";
+import { SwapSummary } from "./SwapSummary.js";
type TransferConfirmationScreenProps = {
title: string;
@@ -72,14 +70,13 @@ export function TransferConfirmationScreen(
| { id: "error"; error: string }
| { id: "done" }
>({ id: "idle" });
- const { symbol } = useChainSymbol(chain);
return (
- {transactionMode && (
+ {transactionMode ? (
<>
@@ -88,52 +85,25 @@ export function TransferConfirmationScreen(
? "Step 1 of 2 - Transfer funds"
: "Step 2 of 2 - Finalize transaction"}
-
+
+ >
+ ) : (
+ <>
+ Confirm payment
+
>
)}
- {/* Sender Address */}
-
- From
-
-
-
-
-
-
-
- {/* Receiver Address */}
-
- To
-
-
-
-
-
-
-
- {/* Token Info */}
-
diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/WalletRow.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/WalletRow.tsx
new file mode 100644
index 00000000000..6aac9909188
--- /dev/null
+++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/WalletRow.tsx
@@ -0,0 +1,70 @@
+import type { ThirdwebClient } from "../../../../../../../client/client.js";
+import { shortenAddress } from "../../../../../../../utils/address.js";
+import { isEcosystemWallet } from "../../../../../../../wallets/ecosystem/is-ecosystem-wallet.js";
+import { isSmartWallet } from "../../../../../../../wallets/smart/index.js";
+import {
+ fontSize,
+ iconSize,
+} from "../../../../../../core/design-system/index.js";
+import { useConnectedWallets } from "../../../../../../core/hooks/wallets/useConnectedWallets.js";
+import {
+ useEnsName,
+ useWalletInfo,
+} from "../../../../../../core/utils/wallet.js";
+import { useProfiles } from "../../../../../hooks/wallets/useProfiles.js";
+import { Skeleton } from "../../../../components/Skeleton.js";
+import { WalletImage } from "../../../../components/WalletImage.js";
+import { Container } from "../../../../components/basic.js";
+import { Text } from "../../../../components/text.js";
+
+export function WalletRow(props: {
+ client: ThirdwebClient;
+ address: string;
+ iconSize?: keyof typeof iconSize;
+ textSize?: keyof typeof fontSize;
+}) {
+ const { client, address } = props;
+ const connectedWallets = useConnectedWallets();
+ const profile = useProfiles({ client });
+ const wallet = connectedWallets.find(
+ (w) => w.getAccount()?.address?.toLowerCase() === address.toLowerCase(),
+ );
+ const email =
+ wallet &&
+ (wallet.id === "inApp" ||
+ isEcosystemWallet(wallet) ||
+ isSmartWallet(wallet))
+ ? profile.data?.find((p) => !!p.details.email)?.details.email
+ : undefined;
+ const walletInfo = useWalletInfo(wallet?.id);
+ const ensNameQuery = useEnsName({
+ client,
+ address,
+ });
+ const addressOrENS = ensNameQuery.data || shortenAddress(address);
+ return (
+
+
+ {wallet ? (
+
+ ) : null}
+
+
+ {addressOrENS || shortenAddress(props.address)}
+
+ {profile.isLoading ? (
+
+ ) : email || walletInfo?.data?.name ? (
+
+ {email || walletInfo?.data?.name}
+
+ ) : null}
+
+
+
+ );
+}
diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/TokenSelector.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/TokenSelector.tsx
index d79f40babae..afc0ebf8293 100644
--- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/TokenSelector.tsx
+++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/TokenSelector.tsx
@@ -214,7 +214,6 @@ export function TokenSelector(props: {
style={{
paddingTop: 0,
paddingBottom: spacing.lg,
- // maxHeight: props.chainSelection ? "300px" : "400px",
}}
>
{!input && (
@@ -284,7 +283,6 @@ export function TokenSelector(props: {
}
function SelectTokenButton(props: {
- // token?: TokenInfo;
token: ERC20OrNativeToken;
chain: Chain;
onClick: () => void;
@@ -310,7 +308,7 @@ function SelectTokenButton(props: {
client={props.client}
/>
-
+
{tokenName ? (
{tokenName}
diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/nativeToken.ts b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/nativeToken.ts
index 087e5f86ea8..dc4b9295a12 100644
--- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/nativeToken.ts
+++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/nativeToken.ts
@@ -10,11 +10,13 @@ export const NATIVE_TOKEN: NativeToken = { nativeToken: true };
* @internal
*/
export function isNativeToken(
- token: Partial | NativeToken,
+ token?: Partial | NativeToken,
): token is NativeToken {
return (
- "nativeToken" in token ||
- token.address?.toLowerCase() === NATIVE_TOKEN_ADDRESS.toLowerCase()
+ (token &&
+ ("nativeToken" in token ||
+ token.address?.toLowerCase() === NATIVE_TOKEN_ADDRESS.toLowerCase())) ||
+ false
);
}
diff --git a/packages/thirdweb/src/react/web/ui/components/token/TokenRow.tsx b/packages/thirdweb/src/react/web/ui/components/token/TokenRow.tsx
index c96dba4dd44..131790627a7 100644
--- a/packages/thirdweb/src/react/web/ui/components/token/TokenRow.tsx
+++ b/packages/thirdweb/src/react/web/ui/components/token/TokenRow.tsx
@@ -10,17 +10,17 @@ import {
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 { Skeleton } from "../Skeleton.js";
-import { TokenIcon } from "../TokenIcon.js";
import { Container } from "../basic.js";
import { Button } from "../buttons.js";
import { Text } from "../text.js";
import { TokenSymbol } from "./TokenSymbol.js";
export function TokenRow(props: {
- token: ERC20OrNativeToken;
- chain: Chain;
+ token: ERC20OrNativeToken | undefined;
+ chain: Chain | undefined;
client: ThirdwebClient;
onSelectToken: () => void;
freezeChainAndToken?: boolean;
@@ -29,6 +29,32 @@ export function TokenRow(props: {
style?: React.CSSProperties;
}) {
const { name } = useChainName(props.chain);
+
+ if (!props.token || !props.chain) {
+ return (
+
+ );
+ }
+
return (
-
-
+
{/* Token Symbol */}
-
+
{props.isLoading ? (
@@ -92,9 +114,11 @@ export function TokenRow(props: {
)}
-
-
-
+ {!props.freezeChainAndToken && (
+
+
+
+ )}
);
}