Skip to content

Commit b88f8ba

Browse files
committed
refactor: refresh quote on step runner
1 parent 484a3e1 commit b88f8ba

File tree

12 files changed

+201
-198
lines changed

12 files changed

+201
-198
lines changed

packages/thirdweb/src/react/core/hooks/useBridgeQuote.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@ import * as Buy from "../../../bridge/Buy.js";
44
import * as Transfer from "../../../bridge/Transfer.js";
55
import type { Token } from "../../../bridge/types/Token.js";
66
import type { ThirdwebClient } from "../../../client/client.js";
7-
import { toUnits } from "../../../utils/units.js";
7+
import { checksumAddress } from "../../../utils/address.js";
88

99
export interface UseBridgeQuoteParams {
1010
originToken: Token;
1111
destinationToken: Token;
12-
destinationAmount: string;
12+
destinationAmount: bigint;
1313
client: ThirdwebClient;
1414
enabled?: boolean;
1515
}
1616

17+
export type BridgeQuoteResult = NonNullable<
18+
ReturnType<typeof useBridgeQuote>["data"]
19+
>;
20+
1721
export function useBridgeQuote({
1822
originToken,
1923
destinationToken,
@@ -28,18 +32,13 @@ export function useBridgeQuote({
2832
originToken.address,
2933
destinationToken.chainId,
3034
destinationToken.address,
31-
destinationAmount,
35+
destinationAmount.toString(),
3236
],
3337
queryFn: async () => {
34-
const destinationAmountWei = toUnits(
35-
destinationAmount,
36-
destinationToken.decimals,
37-
);
38-
3938
// if ssame token and chain, use transfer
4039
if (
41-
originToken.address.toLowerCase() ===
42-
destinationToken.address.toLowerCase() &&
40+
checksumAddress(originToken.address) ===
41+
checksumAddress(destinationToken.address) &&
4342
originToken.chainId === destinationToken.chainId
4443
) {
4544
const transfer = await Transfer.prepare({
@@ -48,17 +47,18 @@ export function useBridgeQuote({
4847
tokenAddress: originToken.address,
4948
sender: originToken.address,
5049
receiver: destinationToken.address,
51-
amount: destinationAmountWei,
50+
amount: destinationAmount,
5251
});
5352
return transfer;
5453
}
5554

55+
console.log("AMOUNT", destinationAmount);
5656
const quote = await Buy.quote({
5757
originChainId: originToken.chainId,
5858
originTokenAddress: originToken.address,
5959
destinationChainId: destinationToken.chainId,
6060
destinationTokenAddress: destinationToken.address,
61-
amount: destinationAmountWei,
61+
amount: destinationAmount,
6262
client,
6363
});
6464

packages/thirdweb/src/react/core/hooks/useStepExecutor.ts

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ import type { ThirdwebClient } from "../../../client/client.js";
1111
import { waitForReceipt } from "../../../transaction/actions/wait-for-tx-receipt.js";
1212
import type { Account, Wallet } from "../../../wallets/interfaces/wallet.js";
1313
import type { WindowAdapter } from "../adapters/WindowAdapter.js";
14-
import type { BridgePrepareResult } from "./useBridgePrepare.js";
14+
import {
15+
useBridgePrepare,
16+
type BridgePrepareRequest,
17+
type BridgePrepareResult,
18+
} from "./useBridgePrepare.js";
19+
import { useQuery } from "@tanstack/react-query";
20+
import { stringify } from "../../../utils/json.js";
1521

1622
/**
1723
* Type for completed status results from Bridge.status and Onramp.status
@@ -21,16 +27,16 @@ export type CompletedStatusResult =
2127
| ({ type: "sell" } & Extract<Status, { status: "COMPLETED" }>)
2228
| ({ type: "transfer" } & Extract<Status, { status: "COMPLETED" }>)
2329
| ({ type: "onramp" } & Extract<
24-
OnrampStatus.Result,
25-
{ status: "COMPLETED" }
26-
>);
30+
OnrampStatus.Result,
31+
{ status: "COMPLETED" }
32+
>);
2733

2834
/**
2935
* Options for the step executor hook
3036
*/
3137
export interface StepExecutorOptions {
3238
/** Prepared quote returned by Bridge.prepare */
33-
preparedQuote: BridgePrepareResult;
39+
request: BridgePrepareRequest;
3440
/** Wallet instance providing getAccount() & sendTransaction */
3541
wallet: Wallet;
3642
/** Window adapter for opening on-ramp URLs (web / RN) */
@@ -61,7 +67,8 @@ export interface StepExecutorResult {
6167
currentTxIndex?: number;
6268
progress: number; // 0–100
6369
onrampStatus?: "pending" | "executing" | "completed" | "failed";
64-
executionState: "idle" | "executing" | "auto-starting";
70+
executionState: "fetching" | "idle" | "executing" | "auto-starting";
71+
steps?: RouteStep[];
6572
error?: ApiError;
6673
start: () => void;
6774
cancel: () => void;
@@ -93,47 +100,67 @@ export function useStepExecutor(
93100
options: StepExecutorOptions,
94101
): StepExecutorResult {
95102
const {
96-
preparedQuote,
103+
request,
97104
wallet,
98105
windowAdapter,
99106
client,
100107
autoStart = false,
101108
onComplete,
102109
} = options;
103110

111+
const { data: preparedQuote, isLoading } = useBridgePrepare(request);
112+
104113
// Flatten all transactions upfront
105114
const flatTxs = useMemo(
106-
() => flattenRouteSteps(preparedQuote.steps),
107-
[preparedQuote.steps],
115+
() => (preparedQuote?.steps ? flattenRouteSteps(preparedQuote.steps) : []),
116+
[preparedQuote?.steps],
108117
);
109118

110119
// State management
111120
const [currentTxIndex, setCurrentTxIndex] = useState<number | undefined>(
112121
undefined,
113122
);
114123
const [executionState, setExecutionState] = useState<
115-
"idle" | "executing" | "auto-starting"
124+
"fetching" | "idle" | "executing" | "auto-starting"
116125
>("idle");
117126
const [error, setError] = useState<ApiError | undefined>(undefined);
118127
const [completedTxs, setCompletedTxs] = useState<Set<number>>(new Set());
119128
const [onrampStatus, setOnrampStatus] = useState<
120129
"pending" | "executing" | "completed" | "failed" | undefined
121-
>(preparedQuote.type === "onramp" ? "pending" : undefined);
130+
>(preparedQuote?.type === "onramp" ? "pending" : undefined);
131+
132+
useQuery({
133+
queryKey: [
134+
"bridge-quote-execution-state",
135+
stringify(preparedQuote?.steps),
136+
isLoading,
137+
],
138+
queryFn: async () => {
139+
if (!isLoading) {
140+
setExecutionState("idle");
141+
} else {
142+
setExecutionState("fetching");
143+
}
144+
return executionState;
145+
},
146+
});
122147

123148
// Cancellation tracking
124149
const abortControllerRef = useRef<AbortController | null>(null);
125150

126151
// Get current step based on current tx index
127152
const currentStep = useMemo(() => {
153+
if (typeof preparedQuote?.steps === "undefined") return undefined;
128154
if (currentTxIndex === undefined) {
129155
return undefined;
130156
}
131157
const tx = flatTxs[currentTxIndex];
132158
return tx ? preparedQuote.steps[tx._stepIndex] : undefined;
133-
}, [currentTxIndex, flatTxs, preparedQuote.steps]);
159+
}, [currentTxIndex, flatTxs, preparedQuote?.steps]);
134160

135161
// Calculate progress including onramp step
136162
const progress = useMemo(() => {
163+
if (typeof preparedQuote?.type === "undefined") return 0;
137164
const totalSteps =
138165
flatTxs.length + (preparedQuote.type === "onramp" ? 1 : 0);
139166
if (totalSteps === 0) {
@@ -142,7 +169,7 @@ export function useStepExecutor(
142169
const completedSteps =
143170
completedTxs.size + (onrampStatus === "completed" ? 1 : 0);
144171
return Math.round((completedSteps / totalSteps) * 100);
145-
}, [completedTxs.size, flatTxs.length, preparedQuote.type, onrampStatus]);
172+
}, [completedTxs.size, flatTxs.length, preparedQuote?.type, onrampStatus]);
146173

147174
// Exponential backoff polling utility
148175
const poller = useCallback(
@@ -181,6 +208,9 @@ export function useStepExecutor(
181208
completedStatusResults: CompletedStatusResult[],
182209
abortSignal: AbortSignal,
183210
) => {
211+
if (typeof preparedQuote?.type === "undefined") {
212+
throw new Error("No quote generated. This is unexpected.");
213+
}
184214
const { prepareTransaction } = await import(
185215
"../../../transaction/prepare-transaction.js"
186216
);
@@ -229,10 +259,14 @@ export function useStepExecutor(
229259
return { completed: true };
230260
}
231261

262+
if (statusResult.status === "FAILED") {
263+
throw new Error("Payment failed");
264+
}
265+
232266
return { completed: false };
233267
}, abortSignal);
234268
},
235-
[poller, preparedQuote.type],
269+
[poller, preparedQuote?.type],
236270
);
237271

238272
// Execute batch transactions
@@ -243,6 +277,9 @@ export function useStepExecutor(
243277
completedStatusResults: CompletedStatusResult[],
244278
abortSignal: AbortSignal,
245279
) => {
280+
if (typeof preparedQuote?.type === "undefined") {
281+
throw new Error("No quote generated. This is unexpected.");
282+
}
246283
if (!account.sendBatchTransaction) {
247284
throw new Error("Account does not support batch transactions");
248285
}
@@ -303,10 +340,14 @@ export function useStepExecutor(
303340
return { completed: true };
304341
}
305342

343+
if (statusResult.status === "FAILED") {
344+
throw new Error("Payment failed");
345+
}
346+
306347
return { completed: false };
307348
}, abortSignal);
308349
},
309-
[poller, preparedQuote.type],
350+
[poller, preparedQuote?.type],
310351
);
311352

312353
// Execute onramp step
@@ -350,6 +391,9 @@ export function useStepExecutor(
350391

351392
// Main execution function
352393
const execute = useCallback(async () => {
394+
if (typeof preparedQuote?.type === "undefined") {
395+
throw new Error("No quote generated. This is unexpected.");
396+
}
353397
if (executionState !== "idle") {
354398
return;
355399
}
@@ -552,6 +596,7 @@ export function useStepExecutor(
552596
currentTxIndex,
553597
progress,
554598
executionState,
599+
steps: preparedQuote?.steps,
555600
onrampStatus,
556601
error,
557602
start,

packages/thirdweb/src/react/core/machines/paymentMachine.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import type { Address } from "../../../utils/address.js";
44
import type { AsyncStorage } from "../../../utils/storage/AsyncStorage.js";
55
import type { Wallet } from "../../../wallets/interfaces/wallet.js";
66
import type { WindowAdapter } from "../adapters/WindowAdapter.js";
7-
import type { BridgePrepareResult } from "../hooks/useBridgePrepare.js";
87
import type { CompletedStatusResult } from "../hooks/useStepExecutor.js";
8+
import type {
9+
BridgePrepareRequest,
10+
BridgePrepareResult,
11+
} from "../hooks/useBridgePrepare.js";
912

1013
/**
1114
* Payment modes supported by BridgeEmbed
@@ -17,17 +20,17 @@ export type PaymentMode = "fund_wallet" | "direct_payment" | "transaction";
1720
*/
1821
export type PaymentMethod =
1922
| {
20-
type: "wallet";
21-
payerWallet: Wallet;
22-
originToken: Token;
23-
balance: bigint;
24-
}
23+
type: "wallet";
24+
payerWallet: Wallet;
25+
originToken: Token;
26+
balance: bigint;
27+
}
2528
| {
26-
type: "fiat";
27-
payerWallet: Wallet;
28-
currency: string;
29-
onramp: "stripe" | "coinbase" | "transak";
30-
};
29+
type: "fiat";
30+
payerWallet: Wallet;
31+
currency: string;
32+
onramp: "stripe" | "coinbase" | "transak";
33+
};
3134

3235
/**
3336
* Payment machine context - holds all flow state data
@@ -45,7 +48,8 @@ export interface PaymentMachineContext {
4548
selectedPaymentMethod?: PaymentMethod;
4649

4750
// Prepared quote data (set in quote state)
48-
preparedQuote?: BridgePrepareResult;
51+
quote?: BridgePrepareResult;
52+
request?: BridgePrepareRequest;
4953

5054
// Execution results (set in execute state on completion)
5155
completedStatuses?: CompletedStatusResult[];
@@ -66,13 +70,17 @@ export interface PaymentMachineContext {
6670
*/
6771
export type PaymentMachineEvent =
6872
| {
69-
type: "DESTINATION_CONFIRMED";
70-
destinationToken: Token;
71-
destinationAmount: string;
72-
receiverAddress: Address;
73-
}
73+
type: "DESTINATION_CONFIRMED";
74+
destinationToken: Token;
75+
destinationAmount: string;
76+
receiverAddress: Address;
77+
}
7478
| { type: "PAYMENT_METHOD_SELECTED"; paymentMethod: PaymentMethod }
75-
| { type: "QUOTE_RECEIVED"; preparedQuote: BridgePrepareResult }
79+
| {
80+
type: "QUOTE_RECEIVED";
81+
quote: BridgePrepareResult;
82+
request: BridgePrepareRequest;
83+
}
7684
| { type: "ROUTE_CONFIRMED" }
7785
| { type: "EXECUTION_COMPLETE"; completedStatuses: CompletedStatusResult[] }
7886
| { type: "ERROR_OCCURRED"; error: Error }
@@ -130,7 +138,7 @@ export function usePaymentMachine(
130138
if (event.type === "PAYMENT_METHOD_SELECTED") {
131139
return {
132140
...ctx,
133-
preparedQuote: undefined, // reset quote when method changes
141+
quote: undefined, // reset quote when method changes
134142
selectedPaymentMethod: event.paymentMethod,
135143
};
136144
} else if (event.type === "ERROR_OCCURRED") {
@@ -146,7 +154,8 @@ export function usePaymentMachine(
146154
if (event.type === "QUOTE_RECEIVED") {
147155
return {
148156
...ctx,
149-
preparedQuote: event.preparedQuote,
157+
quote: event.quote,
158+
request: event.request,
150159
};
151160
} else if (event.type === "ERROR_OCCURRED") {
152161
return {

packages/thirdweb/src/react/core/utils/persist.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,15 @@ export async function saveSnapshot(
3737
destinationToken: context.destinationToken,
3838
destinationAmount: context.destinationAmount,
3939
selectedPaymentMethod: context.selectedPaymentMethod,
40-
preparedQuote: context.preparedQuote,
40+
quote: context.quote,
41+
request: context.request,
4142
completedStatuses: context.completedStatuses,
4243
currentError: context.currentError
4344
? {
44-
name: context.currentError.name,
45-
message: context.currentError.message,
46-
stack: context.currentError.stack,
47-
}
45+
name: context.currentError.name,
46+
message: context.currentError.message,
47+
stack: context.currentError.stack,
48+
}
4849
: undefined,
4950
retryState: context.retryState,
5051
},

0 commit comments

Comments
 (0)