Skip to content

Commit 4b5784d

Browse files
[SDK] fix: Requery allowances on quote screen return
1 parent 18f0e57 commit 4b5784d

File tree

7 files changed

+100
-37
lines changed

7 files changed

+100
-37
lines changed

.changeset/stupid-experts-shake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Requery allowances when getting back to quote screen

packages/thirdweb/src/pay/buyWithCrypto/getQuote.ts

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import type { Hash } from "viem";
22
import { getCachedChain } from "../../chains/utils.js";
33
import type { ThirdwebClient } from "../../client/client.js";
4-
import { getContract } from "../../contract/contract.js";
5-
import { allowance } from "../../extensions/erc20/__generated__/IERC20/read/allowance.js";
6-
import { approve } from "../../extensions/erc20/write/approve.js";
74
import type { PrepareTransactionOptions } from "../../transaction/prepare-transaction.js";
85
import { getClientFetch } from "../../utils/fetch.js";
96
import { stringify } from "../../utils/json.js";
@@ -151,7 +148,7 @@ type BuyWithCryptoQuoteRouteResponse = {
151148
*/
152149
export type BuyWithCryptoQuote = {
153150
transactionRequest: PrepareTransactionOptions;
154-
approval?: PrepareTransactionOptions;
151+
approvalData?: QuoteApprovalInfo;
155152

156153
swapDetails: {
157154
fromAddress: string;
@@ -254,28 +251,6 @@ export async function getBuyWithCryptoQuote(
254251

255252
// check if the fromAddress already has approval for the given amount
256253
const approvalData = data.approval;
257-
let approval = undefined;
258-
if (approvalData) {
259-
const contract = getContract({
260-
client: params.client,
261-
address: approvalData.tokenAddress,
262-
chain: getCachedChain(approvalData.chainId),
263-
});
264-
265-
const approvedAmount = await allowance({
266-
contract,
267-
spender: approvalData.spenderAddress,
268-
owner: params.fromAddress,
269-
});
270-
271-
if (approvedAmount < BigInt(approvalData.amountWei)) {
272-
approval = approve({
273-
contract,
274-
spender: approvalData.spenderAddress,
275-
amountWei: BigInt(approvalData.amountWei),
276-
});
277-
}
278-
}
279254

280255
const swapRoute: BuyWithCryptoQuote = {
281256
transactionRequest: {
@@ -287,7 +262,7 @@ export async function getBuyWithCryptoQuote(
287262
gas: BigInt(data.transactionRequest.gasLimit),
288263
gasPrice: undefined, // ignore gas price returned by the quote, we handle it ourselves
289264
},
290-
approval: approval,
265+
approvalData,
291266
swapDetails: {
292267
fromAddress: data.fromAddress,
293268
toAddress: data.toAddress,

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import { useQueryClient } from "@tanstack/react-query";
1+
import { useQuery, useQueryClient } from "@tanstack/react-query";
22
import { useCallback, useMemo, useState } from "react";
33
import type { Chain } from "../../../../../../chains/types.js";
4+
import { getCachedChain } from "../../../../../../chains/utils.js";
45
import type { ThirdwebClient } from "../../../../../../client/client.js";
56
import { NATIVE_TOKEN_ADDRESS } from "../../../../../../constants/addresses.js";
7+
import { getContract } from "../../../../../../contract/contract.js";
8+
import { allowance } from "../../../../../../extensions/erc20/__generated__/IERC20/read/allowance.js";
69
import type { GetBuyWithCryptoQuoteParams } from "../../../../../../pay/buyWithCrypto/getQuote.js";
710
import type { BuyWithCryptoStatus } from "../../../../../../pay/buyWithCrypto/getStatus.js";
811
import type { BuyWithFiatStatus } from "../../../../../../pay/buyWithFiat/getStatus.js";
@@ -322,6 +325,7 @@ function BuyScreenContent(props: BuyScreenContentProps) {
322325
});
323326
}}
324327
onSuccess={onSwapSuccess}
328+
approvalAmount={screen.approvalAmount}
325329
/>
326330
);
327331
}
@@ -992,6 +996,30 @@ function SwapScreenContent(props: {
992996
gcTime: 30 * 1000,
993997
});
994998

999+
const allowanceQuery = useQuery({
1000+
queryKey: [
1001+
"allowance",
1002+
payer.account.address,
1003+
quoteQuery.data?.approvalData,
1004+
],
1005+
queryFn: () => {
1006+
if (!quoteQuery.data?.approvalData) {
1007+
return null;
1008+
}
1009+
return allowance({
1010+
contract: getContract({
1011+
client: props.client,
1012+
address: quoteQuery.data.swapDetails.fromToken.tokenAddress,
1013+
chain: getCachedChain(quoteQuery.data.swapDetails.fromToken.chainId),
1014+
}),
1015+
spender: quoteQuery.data.approvalData.spenderAddress,
1016+
owner: props.payer.account.address,
1017+
});
1018+
},
1019+
enabled: !!quoteQuery.data?.approvalData,
1020+
refetchOnMount: true,
1021+
});
1022+
9951023
const sourceTokenAmount = swapRequired
9961024
? quoteQuery.data?.swapDetails.fromAmount
9971025
: tokenAmount;
@@ -1002,7 +1030,9 @@ function SwapScreenContent(props: {
10021030
Number(fromTokenBalanceQuery.data.displayValue) < Number(sourceTokenAmount);
10031031

10041032
const disableContinue =
1005-
(swapRequired && !quoteQuery.data) || isNotEnoughBalance;
1033+
(swapRequired && !quoteQuery.data) ||
1034+
isNotEnoughBalance ||
1035+
allowanceQuery.isLoading;
10061036
const switchChainRequired =
10071037
props.payer.wallet.getChain()?.id !== fromChain.id;
10081038

@@ -1047,6 +1077,7 @@ function SwapScreenContent(props: {
10471077
setScreen({
10481078
id: "swap-flow",
10491079
quote: quoteQuery.data,
1080+
approvalAmount: allowanceQuery.data ?? undefined,
10501081
});
10511082
}
10521083

@@ -1155,6 +1186,7 @@ function SwapScreenContent(props: {
11551186
</Button>
11561187
) : switchChainRequired &&
11571188
!quoteQuery.isLoading &&
1189+
!allowanceQuery.isLoading &&
11581190
!isNotEnoughBalance &&
11591191
!quoteQuery.error ? (
11601192
<SwitchNetworkButton
@@ -1493,7 +1525,7 @@ type ApiError = {
14931525
const defaultMessage = "Unable to get price quote";
14941526
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
14951527
function getErrorMessage(err: any): ApiError {
1496-
if (typeof err.error === "object") {
1528+
if (typeof err.error === "object" && err.error.code) {
14971529
return err.error;
14981530
}
14991531
return {

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/PostOnRampSwap.tsx

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { useQuery } from "@tanstack/react-query";
22
import { useEffect, useState } from "react";
3+
import { getCachedChain } from "../../../../../../../chains/utils.js";
34
import type { ThirdwebClient } from "../../../../../../../client/client.js";
5+
import { getContract } from "../../../../../../../contract/contract.js";
6+
import { allowance } from "../../../../../../../extensions/erc20/__generated__/IERC20/read/allowance.js";
47
import type { BuyWithCryptoQuote } from "../../../../../../../pay/buyWithCrypto/getQuote.js";
58
import type { BuyWithCryptoStatus } from "../../../../../../../pay/buyWithCrypto/getStatus.js";
69
import { getPostOnRampQuote } from "../../../../../../../pay/buyWithFiat/getPostOnRampQuote.js";
@@ -43,18 +46,46 @@ export function PostOnRampSwap(props: {
4346
refetchOnWindowFocus: false,
4447
});
4548

49+
const allowanceQuery = useQuery({
50+
queryKey: [
51+
"allowance",
52+
props.payer.account.address,
53+
postOnRampQuoteQuery.data?.approvalData,
54+
],
55+
queryFn: () => {
56+
if (!postOnRampQuoteQuery.data?.approvalData) {
57+
return null;
58+
}
59+
return allowance({
60+
contract: getContract({
61+
client: props.client,
62+
address: postOnRampQuoteQuery.data.swapDetails.fromToken.tokenAddress,
63+
chain: getCachedChain(
64+
postOnRampQuoteQuery.data.swapDetails.fromToken.chainId,
65+
),
66+
}),
67+
spender: postOnRampQuoteQuery.data.approvalData.spenderAddress,
68+
owner: props.payer.account.address,
69+
});
70+
},
71+
enabled: !!postOnRampQuoteQuery.data?.approvalData,
72+
refetchOnMount: true,
73+
});
74+
4675
useEffect(() => {
4776
if (
4877
postOnRampQuoteQuery.data &&
4978
!lockedOnRampQuote &&
50-
!postOnRampQuoteQuery.isRefetching
79+
!postOnRampQuoteQuery.isRefetching &&
80+
!allowanceQuery.isLoading
5181
) {
5282
setLockedOnRampQuote(postOnRampQuoteQuery.data);
5383
}
5484
}, [
5585
postOnRampQuoteQuery.data,
5686
lockedOnRampQuote,
5787
postOnRampQuoteQuery.isRefetching,
88+
allowanceQuery.isLoading,
5889
]);
5990

6091
if (postOnRampQuoteQuery.isError) {
@@ -133,6 +164,7 @@ export function PostOnRampSwap(props: {
133164
transactionMode={props.transactionMode}
134165
isEmbed={props.isEmbed}
135166
onSuccess={props.onSuccess}
167+
approvalAmount={allowanceQuery.data ?? undefined}
136168
/>
137169
);
138170
}

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export type SelectedScreen =
3434
| {
3535
id: "swap-flow";
3636
quote: BuyWithCryptoQuote;
37+
approvalAmount?: bigint;
3738
}
3839
| {
3940
id: "fiat-flow";

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/ConfirmationScreen.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { useState } from "react";
33
import { trackPayEvent } from "../../../../../../../analytics/track/pay.js";
44
import type { Chain } from "../../../../../../../chains/types.js";
55
import type { ThirdwebClient } from "../../../../../../../client/client.js";
6+
import { getContract } from "../../../../../../../contract/contract.js";
7+
import { approve } from "../../../../../../../extensions/erc20/write/approve.js";
68
import type { BuyWithCryptoQuote } from "../../../../../../../pay/buyWithCrypto/getQuote.js";
79
import { sendTransaction } from "../../../../../../../transaction/actions/send-transaction.js";
810
import { waitForReceipt } from "../../../../../../../transaction/actions/wait-for-tx-receipt.js";
@@ -51,9 +53,14 @@ export function SwapConfirmationScreen(props: {
5153
fromTokenSymbol: string;
5254
isFiatFlow: boolean;
5355
payer: PayerInfo;
56+
preApprovedAmount?: bigint;
5457
}) {
55-
const isApprovalRequired = props.quote.approval !== undefined;
56-
const initialStep = isApprovalRequired ? "approval" : "swap";
58+
const needsApproval =
59+
props.quote.approvalData &&
60+
props.preApprovedAmount !== undefined &&
61+
props.preApprovedAmount < BigInt(props.quote.approvalData.amountWei);
62+
console.log("needsApproval", needsApproval);
63+
const initialStep = needsApproval ? "approval" : "swap";
5764

5865
const [step, setStep] = useState<"approval" | "swap">(initialStep);
5966
const [status, setStatus] = useState<
@@ -136,7 +143,7 @@ export function SwapConfirmationScreen(props: {
136143
<Spacer y="xl" />
137144

138145
{/* Show 2 steps - Approve and confirm */}
139-
{isApprovalRequired && (
146+
{needsApproval && (
140147
<>
141148
<Spacer y="sm" />
142149
<Container
@@ -187,7 +194,7 @@ export function SwapConfirmationScreen(props: {
187194
fullWidth
188195
disabled={status === "pending"}
189196
onClick={async () => {
190-
if (step === "approval" && props.quote.approval) {
197+
if (step === "approval" && props.quote.approvalData) {
191198
try {
192199
setStatus("pending");
193200

@@ -204,13 +211,22 @@ export function SwapConfirmationScreen(props: {
204211
dstChainId: props.quote.swapDetails.toToken.chainId,
205212
});
206213

214+
const transaction = approve({
215+
contract: getContract({
216+
client: props.client,
217+
address: props.quote.swapDetails.fromToken.tokenAddress,
218+
chain: props.fromChain,
219+
}),
220+
spender: props.quote.approvalData.spenderAddress,
221+
amountWei: BigInt(props.quote.approvalData.amountWei),
222+
});
223+
207224
const tx = await sendTransaction({
208225
account: props.payer.account,
209-
transaction: props.quote.approval,
226+
transaction,
210227
});
211228

212229
await waitForReceipt({ ...tx, maxBlocksWaitTime: 50 });
213-
// props.onQuoteFinalized(props.quote);
214230

215231
trackPayEvent({
216232
event: "swap_approval_success",

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapFlow.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type SwapFlowProps = {
2222
transactionMode: boolean;
2323
isEmbed: boolean;
2424
onSuccess: ((status: BuyWithCryptoStatus) => void) | undefined;
25+
approvalAmount?: bigint;
2526
};
2627

2728
export function SwapFlow(props: SwapFlowProps) {
@@ -109,6 +110,7 @@ export function SwapFlow(props: SwapFlowProps) {
109110
quote={quote}
110111
isFiatFlow={props.isFiatFlow}
111112
payer={props.payer}
113+
preApprovedAmount={props.approvalAmount}
112114
/>
113115
);
114116
}

0 commit comments

Comments
 (0)