From f77165e2d1dd13a1887604c3431bd49b9bd67f28 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Thu, 6 Feb 2025 00:36:31 -0800 Subject: [PATCH] [SDK] fix: Skip swap approvals and use local gas prices (#6182) --- .changeset/cuddly-wombats-boil.md | 5 + .../src/components/pay/embed.tsx | 33 +++-- .../src/pay/buyWithCrypto/getQuote.ts | 40 ++++-- .../thirdweb/src/pay/utils/commonTypes.ts | 2 +- .../ConnectWallet/screens/Buy/BuyScreen.tsx | 127 +++++++----------- .../screens/Buy/fiat/FiatStatusScreen.tsx | 8 +- .../screens/Buy/swap/ConfirmationScreen.tsx | 14 +- .../screens/Buy/swap/SwapStatusScreen.tsx | 8 +- 8 files changed, 111 insertions(+), 126 deletions(-) create mode 100644 .changeset/cuddly-wombats-boil.md diff --git a/.changeset/cuddly-wombats-boil.md b/.changeset/cuddly-wombats-boil.md new file mode 100644 index 00000000000..681dfedd460 --- /dev/null +++ b/.changeset/cuddly-wombats-boil.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Skip swap approvals if already approved and always calculate gas prices locally diff --git a/apps/playground-web/src/components/pay/embed.tsx b/apps/playground-web/src/components/pay/embed.tsx index d84bbb0172e..a67bd47f6bc 100644 --- a/apps/playground-web/src/components/pay/embed.tsx +++ b/apps/playground-web/src/components/pay/embed.tsx @@ -4,24 +4,29 @@ import { THIRDWEB_CLIENT } from "@/lib/client"; import { useTheme } from "next-themes"; import { base } from "thirdweb/chains"; import { PayEmbed } from "thirdweb/react"; +import { StyledConnectButton } from "../styled-connect-button"; export function StyledPayEmbedPreview() { const { theme } = useTheme(); return ( - + <> + +
+ + ); } diff --git a/packages/thirdweb/src/pay/buyWithCrypto/getQuote.ts b/packages/thirdweb/src/pay/buyWithCrypto/getQuote.ts index 1d3d5a7877d..21ef74b8223 100644 --- a/packages/thirdweb/src/pay/buyWithCrypto/getQuote.ts +++ b/packages/thirdweb/src/pay/buyWithCrypto/getQuote.ts @@ -2,6 +2,7 @@ import type { Hash } from "viem"; 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 type { PrepareTransactionOptions } from "../../transaction/prepare-transaction.js"; import { getClientFetch } from "../../utils/fetch.js"; @@ -251,6 +252,31 @@ export async function getBuyWithCryptoQuote( const data: BuyWithCryptoQuoteRouteResponse = (await response.json()) .result; + // check if the fromAddress already has approval for the given amount + const approvalData = data.approval; + let approval = undefined; + if (approvalData) { + const contract = getContract({ + client: params.client, + address: approvalData.tokenAddress, + chain: getCachedChain(approvalData.chainId), + }); + + const approvedAmount = await allowance({ + contract, + spender: approvalData.spenderAddress, + owner: params.fromAddress, + }); + + if (approvedAmount < BigInt(approvalData.amountWei)) { + approval = approve({ + contract, + spender: approvalData.spenderAddress, + amountWei: BigInt(approvalData.amountWei), + }); + } + } + const swapRoute: BuyWithCryptoQuote = { transactionRequest: { chain: getCachedChain(data.transactionRequest.chainId), @@ -259,19 +285,9 @@ export async function getBuyWithCryptoQuote( to: data.transactionRequest.to, value: BigInt(data.transactionRequest.value), gas: BigInt(data.transactionRequest.gasLimit), - gasPrice: BigInt(data.transactionRequest.gasPrice), + gasPrice: undefined, // ignore gas price returned by the quote, we handle it ourselves }, - approval: data.approval - ? approve({ - contract: getContract({ - client: params.client, - address: data.approval.tokenAddress, - chain: getCachedChain(data.approval.chainId), - }), - spender: data.approval?.spenderAddress, - amountWei: BigInt(data.approval.amountWei), - }) - : undefined, + approval: approval, swapDetails: { fromAddress: data.fromAddress, toAddress: data.toAddress, diff --git a/packages/thirdweb/src/pay/utils/commonTypes.ts b/packages/thirdweb/src/pay/utils/commonTypes.ts index 12cb7137179..ad6f80cdc2f 100644 --- a/packages/thirdweb/src/pay/utils/commonTypes.ts +++ b/packages/thirdweb/src/pay/utils/commonTypes.ts @@ -17,4 +17,4 @@ export type PayOnChainTransactionDetails = { explorerLink?: string; }; -export type FiatProvider = "STRIPE" | "TRANSAK" | "KADO"; +export type FiatProvider = "STRIPE" | "TRANSAK" | "KADO" | "COINBASE"; 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 82e80b8038c..a762ed2426f 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 @@ -1006,34 +1006,6 @@ function SwapScreenContent(props: { const switchChainRequired = props.payer.wallet.getChain()?.id !== fromChain.id; - // biome-ignore lint/suspicious/noExplicitAny: - function getErrorMessage(err: any) { - type AmountTooLowError = { - code: "MINIMUM_PURCHASE_AMOUNT"; - data: { - minimumAmountUSDCents: number; - requestedAmountUSDCents: number; - minimumAmountWei: string; - minimumAmountEth: string; - }; - }; - - const defaultMessage = "Unable to get price quote"; - try { - if (err.error.code === "MINIMUM_PURCHASE_AMOUNT") { - const obj = err.error as AmountTooLowError; - const minAmountToken = obj.data.minimumAmountEth; - return { - minAmount: formatNumber(Number(minAmountToken), 6), - }; - } - } catch {} - - return { - msg: [defaultMessage], - }; - } - const errorMsg = !quoteQuery.isLoading && quoteQuery.error ? getErrorMessage(quoteQuery.error) @@ -1133,9 +1105,10 @@ function SwapScreenContent(props: { {/* Error message */} {errorMsg && (
- {errorMsg.minAmount && ( + {errorMsg.data?.minimumAmountEth ? ( - Minimum amount is {errorMsg.minAmount}{" "} + Minimum amount is{" "} + {formatNumber(Number(errorMsg.data.minimumAmountEth), 6)}{" "} - )} - - {errorMsg.msg?.map((msg) => ( - - {msg} + ) : ( + + {errorMsg.message || defaultMessage} - ))} + )}
)} @@ -1166,12 +1137,17 @@ function SwapScreenContent(props: { )} {/* Button */} - {errorMsg?.minAmount ? ( + {errorMsg?.data?.minimumAmountEth ? ( - )} + )} 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 ab388368c5e..f807aacbbde 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 @@ -236,19 +236,7 @@ export function SwapConfirmationScreen(props: { if (step === "swap") { setStatus("pending"); try { - let tx = props.quote.transactionRequest; - - // Fix for inApp wallet - // Ideally - the pay server sends a non-legacy transaction to avoid this issue - if ( - props.payer.wallet.id === "inApp" || - props.payer.wallet.id === "embedded" - ) { - tx = { - ...props.quote.transactionRequest, - gasPrice: undefined, - }; - } + const tx = props.quote.transactionRequest; trackPayEvent({ event: "prompt_swap_execution", 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 index bc83296a07c..7032502d9b6 100644 --- 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 @@ -113,11 +113,9 @@ export function SwapStatusScreen(props: { {swapDetails} - {!props.isEmbed && ( - - )} + )}