Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shaggy-flowers-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": patch
---

Fiat onramp UI revamp in PayEmbed and support multi hop onramp flows
2 changes: 1 addition & 1 deletion apps/playground-web/src/app/connect/pay/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function StyledPayEmbed() {
<>
<div className="space-y-2">
<h2 className="font-semibold text-2xl tracking-tight sm:text-3xl">
Top Up
Fund Wallet
</h2>
<p className="max-w-[600px]">
Inline component that allows users to buy any currency.
Expand Down
2 changes: 1 addition & 1 deletion apps/playground-web/src/app/navLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const staticSidebarLinks: SidebarLink[] = [
expanded: false,
links: [
{
name: "Top up",
name: "Fund Wallet",
href: "/connect/pay",
},
{
Expand Down
68 changes: 64 additions & 4 deletions apps/playground-web/src/components/pay/embed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,78 @@

import { THIRDWEB_CLIENT } from "@/lib/client";
import { useTheme } from "next-themes";
import { base } from "thirdweb/chains";
import { PayEmbed } from "thirdweb/react";
import {
arbitrum,
arbitrumNova,
base,
defineChain,
sepolia,
treasure,
} from "thirdweb/chains";
import { PayEmbed, getDefaultToken } from "thirdweb/react";
import { StyledConnectButton } from "../styled-connect-button";

export function StyledPayEmbedPreview() {
const { theme } = useTheme();

return (
<div className="flex flex-col items-center justify-center">
<StyledConnectButton />
<StyledConnectButton
chains={[
base,
defineChain(466),
arbitrum,
treasure,
arbitrumNova,
sepolia,
]}
accountAbstraction={{
chain: base,
sponsorGas: true,
}}
supportedTokens={{
466: [
{
address: "0x675C3ce7F43b00045a4Dab954AF36160fb57cB45",
name: "USDC",
symbol: "USDC",
icon: getDefaultToken(base, "USDC")?.icon,
},
],
// biome-ignore lint/style/noNonNullAssertion: <explanation>
8453: [getDefaultToken(base, "USDC")!],
42161: [
{
address: "0x539bde0d7dbd336b79148aa742883198bbf60342",
name: "MAGIC",
symbol: "MAGIC",
},
],
[arbitrumNova.id]: [
{
name: "Godcoin",
symbol: "GOD",
address: "0xb5130f4767ab0acc579f25a76e8f9e977cb3f948",
icon: "https://assets.coingecko.com/coins/images/53848/standard/GodcoinTickerIcon_02.png",
},
],
}}
detailsButton={{
displayBalanceToken: {
466: "0x675C3ce7F43b00045a4Dab954AF36160fb57cB45",
8453: getDefaultToken(base, "USDC")?.address ?? "",
42161: "0x539bde0d7dbd336b79148aa742883198bbf60342",
[arbitrumNova.id]: "0xb5130f4767ab0acc579f25a76e8f9e977cb3f948",
},
}}
/>
<div className="h-10" />
<PayEmbed
connectOptions={{
accountAbstraction: {
chain: base,
sponsorGas: true,
},
}}
client={THIRDWEB_CLIENT}
theme={theme === "light" ? "light" : "dark"}
payOptions={{
Expand Down
42 changes: 9 additions & 33 deletions packages/thirdweb/src/pay/buyWithFiat/getQuote.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { ThirdwebClient } from "../../client/client.js";
import type { CurrencyMeta } from "../../react/web/ui/ConnectWallet/screens/Buy/fiat/currencies.js";
import { getClientFetch } from "../../utils/fetch.js";
import { stringify } from "../../utils/json.js";
import type { FiatProvider } from "../utils/commonTypes.js";
import type { FiatProvider, PayTokenInfo } from "../utils/commonTypes.js";
import { getPayBuyWithFiatQuoteEndpoint } from "../utils/definitions.js";

/**
* Parameters for [`getBuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatQuote) function
* @buyCrypto
Expand Down Expand Up @@ -40,7 +40,7 @@ export type GetBuyWithFiatQuoteParams = {
/**
* Symbol of the fiat currency to buy the token with.
*/
fromCurrencySymbol: "USD" | "CAD" | "GBP" | "EUR" | "JPY";
fromCurrencySymbol: CurrencyMeta["shorthand"];

/**
* The maximum slippage in basis points (bps) allowed for the transaction.
Expand Down Expand Up @@ -150,14 +150,7 @@ export type BuyWithFiatQuote = {
/**
* Token information for the desired token. (token the user wants to buy)
*/
toToken: {
symbol?: string | undefined;
priceUSDCents?: number | undefined;
name?: string | undefined;
chainId: number;
tokenAddress: string;
decimals: number;
};
toToken: PayTokenInfo;
/**
* Address of the wallet to which the tokens will be sent.
*/
Expand Down Expand Up @@ -196,35 +189,17 @@ export type BuyWithFiatQuote = {
amount: string;
amountWei: string;
amountUSDCents: number;
token: {
chainId: number;
decimals: number;
name: string;
priceUSDCents: number;
symbol: string;
tokenAddress: string;
};
token: PayTokenInfo;
};

/**
* Gas Token that will be sent to the user's wallet address by the on-ramp provider.
*
* Only used for ERC20 + Gas on-ramp flow. This will hold the details of the gas token and amount sent for gas.
*
* In Native Currency case, extra for gas will be added to the output amount of the onramp.
* Routing token that will be swapped from the on-ramp token, so that it can be bridged to the destination token.
*/
gasToken?: {
routingToken?: {
amount: string;
amountWei: string;
amountUSDCents: number;
token: {
chainId: number;
decimals: number;
name: string;
priceUSDCents: number;
symbol: string;
tokenAddress: string;
};
token: PayTokenInfo;
};

/**
Expand Down Expand Up @@ -318,6 +293,7 @@ export async function getBuyWithFiatQuote(
fromAddress: params.fromAddress,
toGasAmountWei: params.toGasAmountWei,
preferredProvider: params.preferredProvider,
multiHopSupported: true,
}),
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getAddress } from "../../utils/address.js";
import type { PayTokenInfo } from "../utils/commonTypes.js";
import type { BuyWithFiatQuote } from "./getQuote.js";

/**
Expand Down Expand Up @@ -26,3 +27,58 @@ export function isSwapRequiredPostOnramp(

return !(sameChain && sameToken);
}

export function getOnRampSteps(
buyWithFiatQuote: BuyWithFiatQuote,
): OnRampStep[] {
const isSwapRequired = isSwapRequiredPostOnramp(buyWithFiatQuote);

if (!isSwapRequired) {
return [
{
action: "buy",
token: buyWithFiatQuote.toToken,
amount: buyWithFiatQuote.estimatedToAmountMin,
},
];
}

if (buyWithFiatQuote.routingToken) {
return [
{
action: "buy",
token: buyWithFiatQuote.onRampToken.token,
amount: buyWithFiatQuote.onRampToken.amount,
},
{
action: "swap",
token: buyWithFiatQuote.routingToken.token,
amount: buyWithFiatQuote.routingToken.amount,
},
{
action: "bridge",
token: buyWithFiatQuote.toToken,
amount: buyWithFiatQuote.estimatedToAmountMin,
},
];
}

return [
{
action: "buy",
token: buyWithFiatQuote.onRampToken.token,
amount: buyWithFiatQuote.onRampToken.amount,
},
{
action: "swap",
token: buyWithFiatQuote.toToken,
amount: buyWithFiatQuote.estimatedToAmountMin,
},
];
}

export type OnRampStep = {
action: "buy" | "swap" | "bridge";
token: PayTokenInfo;
amount: string;
};
31 changes: 30 additions & 1 deletion packages/thirdweb/src/pay/convert/type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
const SUPPORTED_FIAT_CURRENCIES = ["USD"] as const;
const SUPPORTED_FIAT_CURRENCIES = [
"USD",
"CAD",
"GBP",
"EUR",
"JPY",
"AUD",
"NZD",
] as const;
/**
* @internal
*/
export type SupportedFiatCurrency = (typeof SUPPORTED_FIAT_CURRENCIES)[number];

export function getFiatSymbol(showBalanceInFiat: SupportedFiatCurrency) {
switch (showBalanceInFiat) {
case "USD":
return "$";
case "CAD":
return "$";
case "GBP":
return "£";
case "EUR":
return "€";
case "JPY":
return "¥";
case "AUD":
return "$";
case "NZD":
return "$";
default:
return "$";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { SmartWalletOptions } from "../../../../wallets/smart/types.js";
import type { AppMetadata } from "../../../../wallets/types.js";
import type { WalletId } from "../../../../wallets/wallet-types.js";
import type { NetworkSelectorProps } from "../../../web/ui/ConnectWallet/NetworkSelector.js";
import type { CurrencyMeta } from "../../../web/ui/ConnectWallet/screens/Buy/fiat/currencies.js";
import type { WelcomeScreen } from "../../../web/ui/ConnectWallet/screens/types.js";
import type { LocaleId } from "../../../web/ui/types.js";
import type { Theme } from "../../design-system/index.js";
Expand Down Expand Up @@ -90,7 +91,7 @@ export type PayUIOptions = Prettify<
| {
testMode?: boolean;
prefillSource?: {
currency?: "USD" | "CAD" | "GBP" | "EUR" | "JPY";
currency?: CurrencyMeta["shorthand"];
};
preferredProvider?: FiatProvider;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
type GetBuyWithFiatStatusParams,
getBuyWithFiatStatus,
} from "../../../../pay/buyWithFiat/getStatus.js";
import type { WithPickedOnceQueryOptions } from "../types.js";

/**
* A hook to get a status of a "Buy with Fiat" transaction to determine if the transaction is completed, failed or pending.
Expand Down Expand Up @@ -34,7 +35,7 @@ import {
* @buyCrypto
*/
export function useBuyWithFiatStatus(
params?: GetBuyWithFiatStatusParams,
params?: WithPickedOnceQueryOptions<GetBuyWithFiatStatusParams>,
): UseQueryResult<BuyWithFiatStatus> {
return useQuery({
queryKey: ["useBuyWithFiatStatus", params],
Expand Down Expand Up @@ -64,5 +65,6 @@ export function useBuyWithFiatStatus(
},
refetchIntervalInBackground: true,
retry: true,
...params?.queryOptions,
});
}
22 changes: 19 additions & 3 deletions packages/thirdweb/src/react/core/hooks/wallets/useSendToken.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useMutation } from "@tanstack/react-query";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import type { ThirdwebClient } from "../../../../client/client.js";
import { getContract } from "../../../../contract/contract.js";
import { resolveAddress } from "../../../../extensions/ens/resolve-address.js";
import { transfer } from "../../../../extensions/erc20/write/transfer.js";
import { sendTransaction } from "../../../../transaction/actions/send-transaction.js";
import { waitForReceipt } from "../../../../transaction/actions/wait-for-tx-receipt.js";
import { prepareTransaction } from "../../../../transaction/prepare-transaction.js";
import { isAddress } from "../../../../utils/address.js";
import { isValidENSName } from "../../../../utils/ens/isValidENSName.js";
import { toWei } from "../../../../utils/units.js";
import { invalidateWalletBalance } from "../../providers/invalidateWalletBalance.js";
import { useActiveWallet } from "./useActiveWallet.js";

/**
Expand All @@ -33,6 +35,7 @@ import { useActiveWallet } from "./useActiveWallet.js";
*/
export function useSendToken(client: ThirdwebClient) {
const wallet = useActiveWallet();
const queryClient = useQueryClient();
return useMutation({
async mutationFn(option: {
tokenAddress?: string;
Expand Down Expand Up @@ -83,7 +86,7 @@ export function useSendToken(client: ThirdwebClient) {
value: toWei(amount),
});

await sendTransaction({
return sendTransaction({
transaction: sendNativeTokenTx,
account,
});
Expand All @@ -103,11 +106,24 @@ export function useSendToken(client: ThirdwebClient) {
to,
});

await sendTransaction({
return sendTransaction({
transaction: tx,
account,
});
}
},
onSettled: async (data, error) => {
if (error) {
return;
}
if (data?.transactionHash) {
await waitForReceipt({
transactionHash: data.transactionHash,
client,
chain: data.chain,
});
}
invalidateWalletBalance(queryClient);
},
});
}
Loading
Loading