From 96f4ea211d76aa56d91151c7ff2eb18ac4ec8e63 Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Thu, 19 Jun 2025 10:53:51 -0700 Subject: [PATCH 1/3] refactor: accept string for amount --- .../app/connect/pay/components/CodeGen.tsx | 35 +---------- .../src/app/connect/pay/components/types.ts | 2 +- .../app/connect/pay/embed/RightSection.tsx | 26 +------- .../src/app/connect/pay/embed/page.tsx | 4 +- .../src/react/web/ui/Bridge/BuyWidget.tsx | 46 +++++++------- .../react/web/ui/Bridge/CheckoutWidget.tsx | 31 +++++----- .../react/web/ui/Bridge/TransactionWidget.tsx | 60 +++++++++++-------- 7 files changed, 81 insertions(+), 123 deletions(-) diff --git a/apps/playground-web/src/app/connect/pay/components/CodeGen.tsx b/apps/playground-web/src/app/connect/pay/components/CodeGen.tsx index 0f0bce8f133..f0d399cd8a4 100644 --- a/apps/playground-web/src/app/connect/pay/components/CodeGen.tsx +++ b/apps/playground-web/src/app/connect/pay/components/CodeGen.tsx @@ -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"; @@ -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 (
}> } className="grow" @@ -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[], @@ -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, diff --git a/apps/playground-web/src/app/connect/pay/components/types.ts b/apps/playground-web/src/app/connect/pay/components/types.ts index 0a976a699e0..f784cef8b06 100644 --- a/apps/playground-web/src/app/connect/pay/components/types.ts +++ b/apps/playground-web/src/app/connect/pay/components/types.ts @@ -13,7 +13,7 @@ export type BridgeComponentsPlaygroundOptions = { image: string | undefined; description: string | undefined; - buyTokenAddress: Address; + buyTokenAddress?: Address; buyTokenAmount: string; buyTokenChain: Chain; diff --git a/apps/playground-web/src/app/connect/pay/embed/RightSection.tsx b/apps/playground-web/src/app/connect/pay/embed/RightSection.tsx index ab3d57adb6a..c27296bf2e6 100644 --- a/apps/playground-web/src/app/connect/pay/embed/RightSection.tsx +++ b/apps/playground-web/src/app/connect/pay/embed/RightSection.tsx @@ -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, @@ -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" @@ -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} /> ); } @@ -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={{ diff --git a/apps/playground-web/src/app/connect/pay/embed/page.tsx b/apps/playground-web/src/app/connect/pay/embed/page.tsx index 4268cf37732..1224d03e62f 100644 --- a/apps/playground-web/src/app/connect/pay/embed/page.tsx +++ b/apps/playground-web/src/app/connect/pay/embed/page.tsx @@ -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"; @@ -19,7 +17,7 @@ const defaultConnectOptions: BridgeComponentsPlaygroundOptions = { title: "", image: "", description: "", - buyTokenAddress: checksumAddress(NATIVE_TOKEN_ADDRESS), + buyTokenAddress: undefined, buyTokenAmount: "0.002", buyTokenChain: arbitrum, sellerAddress: "0x0000000000000000000000000000000000000000", diff --git a/packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx b/packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx index 0a8841fa07e..11b68b434b6 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx @@ -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"; @@ -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. @@ -156,15 +155,15 @@ export type BuyWidgetProps = { type UIOptionsResult = | { type: "success"; data: UIOptions } | { - type: "indexing_token"; - token: Token; - chain: Chain; - } + type: "indexing_token"; + token: Token; + chain: Chain; + } | { - type: "unsupported_token"; - tokenAddress: Address; - chain: Chain; - }; + type: "unsupported_token"; + tokenAddress: Address; + chain: Chain; + }; /** * Widget is a prebuilt UI for purchasing a specific token. @@ -178,12 +177,11 @@ type UIOptionsResult = * * ```tsx * import { ethereum } from "thirdweb/chains"; - * import { toWei } from "thirdweb"; * * * ``` * @@ -195,7 +193,7 @@ type UIOptionsResult = * * ``` @@ -208,7 +206,7 @@ type UIOptionsResult = * * ``` @@ -240,7 +238,7 @@ type UIOptionsResult = * * ``` * @@ -276,16 +278,26 @@ export function TransactionWidget(props: TransactionWidgetProps) { const bridgeDataQuery = useQuery({ queryKey: ["bridgeData", stringify(props)], queryFn: async (): Promise => { + 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 { @@ -414,10 +426,10 @@ type TransactionWidgetConnectOptions = { * ``` */ autoConnect?: - | { - timeout: number; - } - | boolean; + | { + timeout: number; + } + | boolean; /** * Metadata of the app that will be passed to connected wallet. Setting this is highly recommended. From 015c1d34397835fd8a113f4a9d807e6b3d5b39b5 Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Thu, 19 Jun 2025 15:54:11 -0700 Subject: [PATCH 2/3] changeset --- .changeset/quick-foxes-raise.md | 5 +++++ apps/playground-web/src/components/pay/direct-payment.tsx | 3 +-- .../playground-web/src/components/pay/transaction-button.tsx | 2 +- apps/playground-web/src/components/universal-bridge/buy.tsx | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 .changeset/quick-foxes-raise.md diff --git a/.changeset/quick-foxes-raise.md b/.changeset/quick-foxes-raise.md new file mode 100644 index 00000000000..0c7b5f5157c --- /dev/null +++ b/.changeset/quick-foxes-raise.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Use decimal string for BuyWidget amount diff --git a/apps/playground-web/src/components/pay/direct-payment.tsx b/apps/playground-web/src/components/pay/direct-payment.tsx index 59bb3d22c52..a502ccf3a27 100644 --- a/apps/playground-web/src/components/pay/direct-payment.tsx +++ b/apps/playground-web/src/components/pay/direct-payment.tsx @@ -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"; @@ -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" diff --git a/apps/playground-web/src/components/pay/transaction-button.tsx b/apps/playground-web/src/components/pay/transaction-button.tsx index d080ca77d76..6975b08c1ff 100644 --- a/apps/playground-web/src/components/pay/transaction-button.tsx +++ b/apps/playground-web/src/components/pay/transaction-button.tsx @@ -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} diff --git a/apps/playground-web/src/components/universal-bridge/buy.tsx b/apps/playground-web/src/components/universal-bridge/buy.tsx index df6d4ca34a3..14ae27ea67a 100644 --- a/apps/playground-web/src/components/universal-bridge/buy.tsx +++ b/apps/playground-web/src/components/universal-bridge/buy.tsx @@ -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"; @@ -18,7 +18,7 @@ export function StyledBuyWidgetPreview() { title="Get Funds" tokenAddress={NATIVE_TOKEN_ADDRESS} chain={arbitrum} - amount={toWei("0.002")} + amount={"0.1"} />
); From a3943845a98d52faa58eae59580a5ac452cf3682 Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Thu, 19 Jun 2025 16:37:35 -0700 Subject: [PATCH 3/3] lint --- .../src/react/web/ui/Bridge/BuyWidget.tsx | 26 ++++++++--------- .../react/web/ui/Bridge/CheckoutWidget.tsx | 24 ++++++++-------- .../react/web/ui/Bridge/TransactionWidget.tsx | 28 +++++++++---------- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx b/packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx index 11b68b434b6..53f4db8469d 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx @@ -155,15 +155,15 @@ export type BuyWidgetProps = { type UIOptionsResult = | { type: "success"; data: UIOptions } | { - type: "indexing_token"; - token: Token; - chain: Chain; - } + type: "indexing_token"; + token: Token; + chain: Chain; + } | { - type: "unsupported_token"; - tokenAddress: Address; - chain: Chain; - }; + type: "unsupported_token"; + tokenAddress: Address; + chain: Chain; + }; /** * Widget is a prebuilt UI for purchasing a specific token. @@ -265,7 +265,7 @@ export function BuyWidget(props: BuyWidgetProps) { !props.tokenAddress || (isAddress(props.tokenAddress) && checksumAddress(props.tokenAddress) === - checksumAddress(NATIVE_TOKEN_ADDRESS)) + checksumAddress(NATIVE_TOKEN_ADDRESS)) ) { const ETH = await getToken( props.client, @@ -421,10 +421,10 @@ type BuyWidgetConnectOptions = { * ``` */ autoConnect?: - | { - timeout: number; - } - | boolean; + | { + timeout: number; + } + | boolean; /** * Metadata of the app that will be passed to connected wallet. Setting this is highly recommended. diff --git a/packages/thirdweb/src/react/web/ui/Bridge/CheckoutWidget.tsx b/packages/thirdweb/src/react/web/ui/Bridge/CheckoutWidget.tsx index fbf28297720..d5e32621eec 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/CheckoutWidget.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/CheckoutWidget.tsx @@ -171,15 +171,15 @@ export type CheckoutWidgetProps = { type UIOptionsResult = | { type: "success"; data: UIOptions } | { - type: "indexing_token"; - token: Token; - chain: Chain; - } + type: "indexing_token"; + token: Token; + chain: Chain; + } | { - type: "unsupported_token"; - tokenAddress: Address; - chain: Chain; - }; + type: "unsupported_token"; + tokenAddress: Address; + chain: Chain; + }; /** * Widget a prebuilt UI for purchasing a specific token. @@ -401,10 +401,10 @@ type CheckoutWidgetConnectOptions = { * ``` */ autoConnect?: - | { - timeout: number; - } - | boolean; + | { + timeout: number; + } + | boolean; /** * Metadata of the app that will be passed to connected wallet. Setting this is highly recommended. diff --git a/packages/thirdweb/src/react/web/ui/Bridge/TransactionWidget.tsx b/packages/thirdweb/src/react/web/ui/Bridge/TransactionWidget.tsx index a89d197d241..1630b6bd416 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/TransactionWidget.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/TransactionWidget.tsx @@ -4,6 +4,8 @@ import { useQuery } from "@tanstack/react-query"; 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, @@ -11,7 +13,6 @@ import { import { type Address, checksumAddress } from "../../../../utils/address.js"; import { stringify } from "../../../../utils/json.js"; import { toUnits } from "../../../../utils/units.js"; -import { getToken } from "../../../../pay/convert/get-token.js"; import type { Wallet } from "../../../../wallets/interfaces/wallet.js"; import type { SmartWalletOptions } from "../../../../wallets/smart/types.js"; import type { AppMetadata } from "../../../../wallets/types.js"; @@ -28,7 +29,6 @@ import { Spinner } from "../components/Spinner.js"; import type { LocaleId } from "../types.js"; import { BridgeOrchestrator, type UIOptions } from "./BridgeOrchestrator.js"; import { UnsupportedTokenScreen } from "./UnsupportedTokenScreen.js"; -import { NATIVE_TOKEN_ADDRESS } from "../../../../constants/addresses.js"; export type TransactionWidgetProps = { supportedTokens?: SupportedTokens; @@ -171,15 +171,15 @@ export type TransactionWidgetProps = { type UIOptionsResult = | { type: "success"; data: UIOptions } | { - type: "indexing_token"; - token: Token; - chain: Chain; - } + type: "indexing_token"; + token: Token; + chain: Chain; + } | { - type: "unsupported_token"; - tokenAddress: Address; - chain: Chain; - }; + type: "unsupported_token"; + tokenAddress: Address; + chain: Chain; + }; /** * Widget a prebuilt UI for purchasing a specific token. @@ -426,10 +426,10 @@ type TransactionWidgetConnectOptions = { * ``` */ autoConnect?: - | { - timeout: number; - } - | boolean; + | { + timeout: number; + } + | boolean; /** * Metadata of the app that will be passed to connected wallet. Setting this is highly recommended.