Skip to content

Refactor: UB widget inputs #7390

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 19, 2025
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/quick-foxes-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": patch
---

Use decimal string for BuyWidget amount
35 changes: 3 additions & 32 deletions apps/playground-web/src/app/connect/pay/components/CodeGen.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import { THIRDWEB_CLIENT } from "@/lib/client";
import { useQuery } from "@tanstack/react-query";
import { Suspense, lazy } from "react";
import { defineChain, getContract, toUnits } from "thirdweb";
import { getCurrencyMetadata } from "thirdweb/extensions/erc20";
import { CodeLoading } from "../../../../components/code/code.client";
import type { BridgeComponentsPlaygroundOptions } from "./types";

Expand All @@ -13,36 +9,11 @@ const CodeClient = lazy(
export function CodeGen(props: {
options: BridgeComponentsPlaygroundOptions;
}) {
const { options } = props;
const { data: amount } = useQuery({
queryKey: [
"amount",
options.payOptions.buyTokenAmount,
options.payOptions.buyTokenChain,
options.payOptions.buyTokenAddress,
],
queryFn: async () => {
if (!options.payOptions.buyTokenAmount) {
return;
}
const contract = getContract({
chain: defineChain(options.payOptions.buyTokenChain.id),
address: options.payOptions.buyTokenAddress,
client: THIRDWEB_CLIENT,
});
const token = await getCurrencyMetadata({
contract,
});

return toUnits(options.payOptions.buyTokenAmount, token.decimals);
},
});

return (
<div className="flex w-full grow flex-col">
<Suspense fallback={<CodeLoading />}>
<CodeClient
code={getCode(props.options, amount)}
code={getCode(props.options)}
lang="tsx"
loader={<CodeLoading />}
className="grow"
Expand All @@ -52,7 +23,7 @@ export function CodeGen(props: {
);
}

function getCode(options: BridgeComponentsPlaygroundOptions, amount?: bigint) {
function getCode(options: BridgeComponentsPlaygroundOptions) {
const imports = {
react: ["PayEmbed"] as string[],
thirdweb: [] as string[],
Expand Down Expand Up @@ -104,7 +75,7 @@ function Example() {
<${componentName}
client={client}
chain={defineChain(${options.payOptions.buyTokenChain.id})}
amount={${amount}n}${options.payOptions.buyTokenAddress ? `\n\t token="${options.payOptions.buyTokenAddress}"` : ""}${options.payOptions.sellerAddress ? `\n\t seller="${options.payOptions.sellerAddress}"` : ""}${options.payOptions.title ? `\n\t ${options.payOptions.widget === "checkout" ? "name" : "title"}="${options.payOptions.title}"` : ""}${options.payOptions.image ? `\n\t image="${options.payOptions.image}"` : ""}${options.payOptions.description ? `\n\t description="${options.payOptions.description}"` : ""}${
amount="${options.payOptions.buyTokenAmount}"${options.payOptions.buyTokenAddress ? `\n\t token="${options.payOptions.buyTokenAddress}"` : ""}${options.payOptions.sellerAddress ? `\n\t seller="${options.payOptions.sellerAddress}"` : ""}${options.payOptions.title ? `\n\t ${options.payOptions.widget === "checkout" ? "name" : "title"}="${options.payOptions.title}"` : ""}${options.payOptions.image ? `\n\t image="${options.payOptions.image}"` : ""}${options.payOptions.description ? `\n\t description="${options.payOptions.description}"` : ""}${
options.payOptions.widget === "transaction"
? `\n\t transaction={claimTo({
contract: nftContract,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type BridgeComponentsPlaygroundOptions = {
image: string | undefined;
description: string | undefined;

buyTokenAddress: Address;
buyTokenAddress?: Address;
buyTokenAmount: string;
buyTokenChain: Chain;

Expand Down
26 changes: 3 additions & 23 deletions apps/playground-web/src/app/connect/pay/embed/RightSection.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { getContract, toUnits } from "thirdweb";
import { getContract } from "thirdweb";
import { base } from "thirdweb/chains";
import { getCurrencyMetadata } from "thirdweb/extensions/erc20";
import { claimTo } from "thirdweb/extensions/erc1155";
import {
BuyWidget,
Expand Down Expand Up @@ -43,18 +41,6 @@ export function RightSection(props: {
}

const account = useActiveAccount();
const { data: tokenData } = useQuery({
queryKey: ["token", props.options.payOptions.buyTokenAddress],
queryFn: () =>
getCurrencyMetadata({
contract: getContract({
address: props.options.payOptions.buyTokenAddress,
chain: props.options.payOptions.buyTokenChain,
client: THIRDWEB_CLIENT,
}),
}),
enabled: !!props.options.payOptions.buyTokenAddress,
});

const themeObj =
props.options.theme.type === "dark"
Expand All @@ -74,10 +60,7 @@ export function RightSection(props: {
title={props.options.payOptions.title}
tokenAddress={props.options.payOptions.buyTokenAddress}
chain={props.options.payOptions.buyTokenChain}
amount={toUnits(
props.options.payOptions.buyTokenAmount,
tokenData?.decimals || 18,
)}
amount={props.options.payOptions.buyTokenAmount}
/>
);
}
Expand All @@ -90,10 +73,7 @@ export function RightSection(props: {
name={props.options.payOptions.title}
tokenAddress={props.options.payOptions.buyTokenAddress}
chain={props.options.payOptions.buyTokenChain}
amount={toUnits(
props.options.payOptions.buyTokenAmount,
tokenData?.decimals || 18,
)}
amount={props.options.payOptions.buyTokenAmount}
seller={props.options.payOptions.sellerAddress}
presetOptions={[1, 2, 3]}
purchaseData={{
Expand Down
4 changes: 1 addition & 3 deletions apps/playground-web/src/app/connect/pay/embed/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"use client";
import { use, useState } from "react";
import { NATIVE_TOKEN_ADDRESS } from "thirdweb";
import { arbitrum } from "thirdweb/chains";
import { checksumAddress } from "thirdweb/utils";
import type { BridgeComponentsPlaygroundOptions } from "../components/types";
import { LeftSection } from "./LeftSection";
import { RightSection } from "./RightSection";
Expand All @@ -19,7 +17,7 @@ const defaultConnectOptions: BridgeComponentsPlaygroundOptions = {
title: "",
image: "",
description: "",
buyTokenAddress: checksumAddress(NATIVE_TOKEN_ADDRESS),
buyTokenAddress: undefined,
buyTokenAmount: "0.002",
buyTokenChain: arbitrum,
sellerAddress: "0x0000000000000000000000000000000000000000",
Expand Down
3 changes: 1 addition & 2 deletions apps/playground-web/src/components/pay/direct-payment.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"use client";
import { toUnits } from "thirdweb";
import { base } from "thirdweb/chains";
import { CheckoutWidget } from "thirdweb/react";
import { THIRDWEB_CLIENT } from "../../lib/client";
Expand All @@ -11,7 +10,7 @@ export function BuyMerchPreview() {
client={THIRDWEB_CLIENT}
theme="light"
chain={base}
amount={toUnits("2", 6)}
amount={"2"}
tokenAddress="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
seller="0xEb0effdFB4dC5b3d5d3aC6ce29F3ED213E95d675"
feePayer="seller"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function PayTransactionPreview() {
tokenId: 2n,
to: account?.address || "",
})}
amount={100n}
amount={"0.1"}
title={nft?.metadata?.name}
description={nft?.metadata?.description}
image={nft?.metadata?.image}
Expand Down
4 changes: 2 additions & 2 deletions apps/playground-web/src/components/universal-bridge/buy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { THIRDWEB_CLIENT } from "@/lib/client";
import { useTheme } from "next-themes";
import { NATIVE_TOKEN_ADDRESS, toWei } from "thirdweb";
import { NATIVE_TOKEN_ADDRESS } from "thirdweb";
import { arbitrum } from "thirdweb/chains";
import { BuyWidget } from "thirdweb/react";

Expand All @@ -18,7 +18,7 @@ export function StyledBuyWidgetPreview() {
title="Get Funds"
tokenAddress={NATIVE_TOKEN_ADDRESS}
chain={arbitrum}
amount={toWei("0.002")}
amount={"0.1"}
/>
</div>
);
Expand Down
20 changes: 9 additions & 11 deletions packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
isAddress,
} from "../../../../utils/address.js";
import { stringify } from "../../../../utils/json.js";
import { toTokens } from "../../../../utils/units.js";
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
import type { SmartWalletOptions } from "../../../../wallets/smart/types.js";
import type { AppMetadata } from "../../../../wallets/types.js";
Expand Down Expand Up @@ -112,9 +111,9 @@ export type BuyWidgetProps = {
tokenAddress?: Address;

/**
* The amount to buy **(in wei)**.
* The amount to buy **(as a decimal string)**, e.g. "1.5" for 1.5 tokens.
*/
amount: bigint;
amount: string;

/**
* The title to display in the widget.
Expand Down Expand Up @@ -178,12 +177,11 @@ type UIOptionsResult =
*
* ```tsx
* import { ethereum } from "thirdweb/chains";
* import { toWei } from "thirdweb";
*
* <BuyWidget
* client={client}
* chain={ethereum}
* amount={toWei("0.1")}
* amount="0.1"
* />
* ```
*
Expand All @@ -195,7 +193,7 @@ type UIOptionsResult =
* <BuyWidget
* client={client}
* chain={ethereum}
* amount={toWei("100")}
* amount="100"
* tokenAddress="0xA0b86a33E6417E4df2057B2d3C6d9F7cc11b0a70"
* />
* ```
Expand All @@ -208,7 +206,7 @@ type UIOptionsResult =
* <BuyWidget
* client={client}
* chain={ethereum}
* amount={toWei("0.1")}
* amount="0.1"
* theme={darkTheme({
* colors: {
* modalBg: "red",
Expand All @@ -227,7 +225,7 @@ type UIOptionsResult =
* <BuyWidget
* client={client}
* chain={ethereum}
* amount={toWei("0.1")}
* amount="0.1"
* title="Buy ETH"
* />
* ```
Expand All @@ -240,7 +238,7 @@ type UIOptionsResult =
* <BuyWidget
* client={client}
* chain={ethereum}
* amount={toWei("0.1")}
* amount="0.1"
* connectOptions={{
* connectModal: {
* size: 'compact',
Expand Down Expand Up @@ -279,7 +277,7 @@ export function BuyWidget(props: BuyWidgetProps) {
data: {
mode: "fund_wallet",
destinationToken: ETH,
initialAmount: toTokens(props.amount, ETH.decimals),
initialAmount: props.amount,
},
};
}
Expand All @@ -303,7 +301,7 @@ export function BuyWidget(props: BuyWidgetProps) {
data: {
mode: "fund_wallet",
destinationToken: token,
initialAmount: toTokens(props.amount, token.decimals),
initialAmount: props.amount,
metadata: {
title: props.title,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { NATIVE_TOKEN_ADDRESS } from "../../../../constants/addresses.js";
import { getToken } from "../../../../pay/convert/get-token.js";
import { type Address, checksumAddress } from "../../../../utils/address.js";
import { stringify } from "../../../../utils/json.js";
import { toTokens } from "../../../../utils/units.js";
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
import type { SmartWalletOptions } from "../../../../wallets/smart/types.js";
import type { AppMetadata } from "../../../../wallets/types.js";
Expand Down Expand Up @@ -108,9 +107,9 @@ export type CheckoutWidgetProps = {
tokenAddress?: Address;

/**
* The price of the item **(in wei)**.
* The price of the item **(as a decimal string)**, e.g. "1.5" for 1.5 tokens.
*/
amount: bigint;
amount: string;

/**
* The wallet address or ENS funds will be paid to.
Expand Down Expand Up @@ -282,7 +281,7 @@ export function CheckoutWidget(props: CheckoutWidgetProps) {
},
paymentInfo: {
token,
amount: toTokens(props.amount, token.decimals),
amount: props.amount,
sellerAddress: props.seller,
feePayer: props.feePayer === "seller" ? "receiver" : "sender", // User is sender, seller is receiver
},
Expand Down
34 changes: 23 additions & 11 deletions packages/thirdweb/src/react/web/ui/Bridge/TransactionWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import type { Token } from "../../../../bridge/index.js";
import type { Chain } from "../../../../chains/types.js";
import type { ThirdwebClient } from "../../../../client/client.js";
import { NATIVE_TOKEN_ADDRESS } from "../../../../constants/addresses.js";
import { getToken } from "../../../../pay/convert/get-token.js";
import {
type PreparedTransaction,
prepareTransaction,
} from "../../../../transaction/prepare-transaction.js";
import { type Address, checksumAddress } from "../../../../utils/address.js";
import { stringify } from "../../../../utils/json.js";
import { toUnits } from "../../../../utils/units.js";
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
import type { SmartWalletOptions } from "../../../../wallets/smart/types.js";
import type { AppMetadata } from "../../../../wallets/types.js";
Expand Down Expand Up @@ -105,9 +107,9 @@ export type TransactionWidgetProps = {
tokenAddress?: Address;

/**
* The price of the item **(in wei)**.
* The price of the item **(as a decimal string)**, e.g. "1.5" for 1.5 tokens.
*/
amount?: bigint;
amount?: string;

/**
* A title for the transaction.
Expand Down Expand Up @@ -196,8 +198,8 @@ type UIOptionsResult =
* to: "0x...",
* chain: ethereum,
* client: client,
* value: toUnits("0.001", 18),
* })}
* amount="0.1"
* />
* ```
*
Expand Down Expand Up @@ -276,16 +278,26 @@ export function TransactionWidget(props: TransactionWidgetProps) {
const bridgeDataQuery = useQuery({
queryKey: ["bridgeData", stringify(props)],
queryFn: async (): Promise<UIOptionsResult> => {
let erc20Value = props.transaction.erc20Value;

if (props.amount) {
// Get token decimals for conversion
const tokenAddress = props.tokenAddress || NATIVE_TOKEN_ADDRESS;
const token = await getToken(
props.client,
checksumAddress(tokenAddress),
props.transaction.chain.id,
);

erc20Value = {
amountWei: toUnits(props.amount, token.decimals),
tokenAddress: checksumAddress(tokenAddress),
};
}

const transaction = prepareTransaction({
...props.transaction,
erc20Value: props.amount
? {
amountWei: props.amount,
tokenAddress: checksumAddress(
props.tokenAddress || NATIVE_TOKEN_ADDRESS,
),
}
: props.transaction.erc20Value,
erc20Value,
});

return {
Expand Down
Loading