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 ? (