@aori/mega-swap-widget 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +404 -0
- package/dist/AssetSelectionMenu-Y3EB32BT.cjs +13 -0
- package/dist/AssetSelectionMenu-Y3EB32BT.cjs.map +1 -0
- package/dist/AssetSelectionMenu-ZRG42UCZ.js +13 -0
- package/dist/AssetSelectionMenu-ZRG42UCZ.js.map +1 -0
- package/dist/ChainSelectionMenu-FBAPPFKI.cjs +11 -0
- package/dist/ChainSelectionMenu-FBAPPFKI.cjs.map +1 -0
- package/dist/ChainSelectionMenu-QO3H4TNR.js +11 -0
- package/dist/ChainSelectionMenu-QO3H4TNR.js.map +1 -0
- package/dist/SwapFormHorizontal-JDJUDFNX.js +573 -0
- package/dist/SwapFormHorizontal-JDJUDFNX.js.map +1 -0
- package/dist/SwapFormHorizontal-WG3Z3CFT.cjs +573 -0
- package/dist/SwapFormHorizontal-WG3Z3CFT.cjs.map +1 -0
- package/dist/SwapFormSplit-7CHTPLEQ.js +441 -0
- package/dist/SwapFormSplit-7CHTPLEQ.js.map +1 -0
- package/dist/SwapFormSplit-VDDIRQUQ.cjs +441 -0
- package/dist/SwapFormSplit-VDDIRQUQ.cjs.map +1 -0
- package/dist/WalletPlaceholderPanel-7YDQ4FT6.js +57 -0
- package/dist/WalletPlaceholderPanel-7YDQ4FT6.js.map +1 -0
- package/dist/WalletPlaceholderPanel-FZ6XIAMF.cjs +57 -0
- package/dist/WalletPlaceholderPanel-FZ6XIAMF.cjs.map +1 -0
- package/dist/WidgetWalletPanel-D7I5TAU3.js +789 -0
- package/dist/WidgetWalletPanel-D7I5TAU3.js.map +1 -0
- package/dist/WidgetWalletPanel-T7H6FGVN.cjs +789 -0
- package/dist/WidgetWalletPanel-T7H6FGVN.cjs.map +1 -0
- package/dist/chunk-3E6RNP2D.cjs +389 -0
- package/dist/chunk-3E6RNP2D.cjs.map +1 -0
- package/dist/chunk-5TH6MFQD.cjs +122 -0
- package/dist/chunk-5TH6MFQD.cjs.map +1 -0
- package/dist/chunk-5XSCUUOW.js +101 -0
- package/dist/chunk-5XSCUUOW.js.map +1 -0
- package/dist/chunk-6Q7MSCKS.js +2199 -0
- package/dist/chunk-6Q7MSCKS.js.map +1 -0
- package/dist/chunk-6XB5R4GF.cjs +368 -0
- package/dist/chunk-6XB5R4GF.cjs.map +1 -0
- package/dist/chunk-6YLNOZ7P.js +389 -0
- package/dist/chunk-6YLNOZ7P.js.map +1 -0
- package/dist/chunk-7AWG6OWF.js +27 -0
- package/dist/chunk-7AWG6OWF.js.map +1 -0
- package/dist/chunk-ARMW5POL.js +3082 -0
- package/dist/chunk-ARMW5POL.js.map +1 -0
- package/dist/chunk-B3ILUJ7G.cjs +101 -0
- package/dist/chunk-B3ILUJ7G.cjs.map +1 -0
- package/dist/chunk-GGM3MDFM.js +32 -0
- package/dist/chunk-GGM3MDFM.js.map +1 -0
- package/dist/chunk-GZUTUD5O.cjs +2199 -0
- package/dist/chunk-GZUTUD5O.cjs.map +1 -0
- package/dist/chunk-HXOGJSAI.cjs +3082 -0
- package/dist/chunk-HXOGJSAI.cjs.map +1 -0
- package/dist/chunk-LTA7IG3J.js +122 -0
- package/dist/chunk-LTA7IG3J.js.map +1 -0
- package/dist/chunk-NBJPKJBC.cjs +32 -0
- package/dist/chunk-NBJPKJBC.cjs.map +1 -0
- package/dist/chunk-PGYOJ5RB.cjs +27 -0
- package/dist/chunk-PGYOJ5RB.cjs.map +1 -0
- package/dist/chunk-QHW27RMH.js +199 -0
- package/dist/chunk-QHW27RMH.js.map +1 -0
- package/dist/chunk-TMC4SUEV.js +368 -0
- package/dist/chunk-TMC4SUEV.js.map +1 -0
- package/dist/chunk-XQINW7QP.cjs +199 -0
- package/dist/chunk-XQINW7QP.cjs.map +1 -0
- package/dist/index.cjs +1780 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1424 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +555 -0
- package/dist/index.d.ts +555 -0
- package/dist/index.js +1780 -0
- package/dist/index.js.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,3082 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
require_main
|
|
4
|
+
} from "./chunk-LTA7IG3J.js";
|
|
5
|
+
import {
|
|
6
|
+
useSwapFormContext,
|
|
7
|
+
useWidgetSwapUIStore
|
|
8
|
+
} from "./chunk-TMC4SUEV.js";
|
|
9
|
+
import {
|
|
10
|
+
ChainIcon_default,
|
|
11
|
+
Checkmark,
|
|
12
|
+
DropdownIcon,
|
|
13
|
+
LoadingSpinner_default,
|
|
14
|
+
NATIVE_ASSET_ADDRESS,
|
|
15
|
+
RedoAnimation,
|
|
16
|
+
RedoIcon,
|
|
17
|
+
TokenImage_default,
|
|
18
|
+
UserIcon,
|
|
19
|
+
XAnimation,
|
|
20
|
+
balanceKeys,
|
|
21
|
+
checkedAddress,
|
|
22
|
+
fetchSwapBalances,
|
|
23
|
+
formatNumber,
|
|
24
|
+
getChainConfig,
|
|
25
|
+
getChainIdForKey,
|
|
26
|
+
getKeyForChainId,
|
|
27
|
+
getNextReviewStep,
|
|
28
|
+
getViemChainById,
|
|
29
|
+
getVtApiUrl,
|
|
30
|
+
getVtHeaders,
|
|
31
|
+
isAddress,
|
|
32
|
+
isReviewStepPast,
|
|
33
|
+
sleep,
|
|
34
|
+
toBigInt,
|
|
35
|
+
useDebounce,
|
|
36
|
+
useEnabledChainIds,
|
|
37
|
+
useWidgetConfig
|
|
38
|
+
} from "./chunk-6Q7MSCKS.js";
|
|
39
|
+
import {
|
|
40
|
+
useWalletState
|
|
41
|
+
} from "./chunk-7AWG6OWF.js";
|
|
42
|
+
import {
|
|
43
|
+
__toESM
|
|
44
|
+
} from "./chunk-GGM3MDFM.js";
|
|
45
|
+
|
|
46
|
+
// src/hooks/useBalanceEventListener.ts
|
|
47
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
48
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
49
|
+
function getRefetchDelay(chainIds) {
|
|
50
|
+
const MIN_DELAY = 1e3;
|
|
51
|
+
const MAX_DELAY = 5e3;
|
|
52
|
+
const BUFFER = 1e3;
|
|
53
|
+
if (chainIds.length === 0) return 5e3;
|
|
54
|
+
const maxBlockTime = Math.max(
|
|
55
|
+
...chainIds.map((id) => getChainConfig(id)?.blockTimeMs ?? 2e3)
|
|
56
|
+
);
|
|
57
|
+
return Math.min(MAX_DELAY, Math.max(MIN_DELAY, maxBlockTime * 2 + BUFFER));
|
|
58
|
+
}
|
|
59
|
+
var useBalanceEventListener = () => {
|
|
60
|
+
const { address: userAddress } = useWalletState();
|
|
61
|
+
const availableChainIds = useEnabledChainIds();
|
|
62
|
+
const queryClient = useQueryClient();
|
|
63
|
+
const pendingTimerRef = useRef(null);
|
|
64
|
+
const mountedRef = useRef(true);
|
|
65
|
+
const triggerBalanceUpdate = useCallback(
|
|
66
|
+
(event) => {
|
|
67
|
+
if (!userAddress) return;
|
|
68
|
+
const chainIds = [...new Set(event.tokens.map((t) => t.asset.chainId))];
|
|
69
|
+
const delay = getRefetchDelay(chainIds);
|
|
70
|
+
if (pendingTimerRef.current) {
|
|
71
|
+
clearTimeout(pendingTimerRef.current);
|
|
72
|
+
pendingTimerRef.current = null;
|
|
73
|
+
}
|
|
74
|
+
pendingTimerRef.current = setTimeout(async () => {
|
|
75
|
+
pendingTimerRef.current = null;
|
|
76
|
+
if (!mountedRef.current) return;
|
|
77
|
+
try {
|
|
78
|
+
const tokens = event.tokens.map((t) => ({
|
|
79
|
+
chainId: t.asset.chainId,
|
|
80
|
+
tokenAddress: t.asset.address
|
|
81
|
+
}));
|
|
82
|
+
const swapResult = await fetchSwapBalances(userAddress, tokens);
|
|
83
|
+
if (swapResult.balances.length === 0) return;
|
|
84
|
+
if (event.tokens.length >= 2) {
|
|
85
|
+
const base = event.tokens[0].asset;
|
|
86
|
+
const quote = event.tokens[1].asset;
|
|
87
|
+
const swapKey = balanceKeys.swap(
|
|
88
|
+
userAddress,
|
|
89
|
+
base.chainId,
|
|
90
|
+
base.address,
|
|
91
|
+
quote.chainId,
|
|
92
|
+
quote.address
|
|
93
|
+
);
|
|
94
|
+
queryClient.setQueryData(swapKey, swapResult);
|
|
95
|
+
}
|
|
96
|
+
const bulkKey = balanceKeys.bulk(userAddress, availableChainIds);
|
|
97
|
+
const existingBulk = queryClient.getQueryData(bulkKey);
|
|
98
|
+
if (existingBulk) {
|
|
99
|
+
const updatedBalances = existingBulk.balances.map((existing) => {
|
|
100
|
+
const updated = swapResult.balances.find(
|
|
101
|
+
(b) => b.chainId === existing.chainId && b.token.toLowerCase() === existing.token.toLowerCase()
|
|
102
|
+
);
|
|
103
|
+
return updated ?? existing;
|
|
104
|
+
});
|
|
105
|
+
for (const newBalance of swapResult.balances) {
|
|
106
|
+
if (!updatedBalances.some(
|
|
107
|
+
(b) => b.chainId === newBalance.chainId && b.token.toLowerCase() === newBalance.token.toLowerCase()
|
|
108
|
+
)) updatedBalances.push(newBalance);
|
|
109
|
+
}
|
|
110
|
+
for (const eventToken of event.tokens) {
|
|
111
|
+
const inResult = swapResult.balances.some(
|
|
112
|
+
(b) => b.chainId === eventToken.asset.chainId && b.token.toLowerCase() === eventToken.asset.address.toLowerCase()
|
|
113
|
+
);
|
|
114
|
+
if (!inResult) {
|
|
115
|
+
const idx = updatedBalances.findIndex(
|
|
116
|
+
(b) => b.chainId === eventToken.asset.chainId && b.token.toLowerCase() === eventToken.asset.address.toLowerCase()
|
|
117
|
+
);
|
|
118
|
+
if (idx !== -1) {
|
|
119
|
+
updatedBalances[idx] = { ...updatedBalances[idx], balance: "0", shiftedBalance: "0" };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
queryClient.setQueryData(bulkKey, { balances: updatedBalances });
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
}, delay);
|
|
128
|
+
},
|
|
129
|
+
[userAddress, availableChainIds, queryClient]
|
|
130
|
+
);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
mountedRef.current = true;
|
|
133
|
+
return () => {
|
|
134
|
+
mountedRef.current = false;
|
|
135
|
+
if (pendingTimerRef.current) {
|
|
136
|
+
clearTimeout(pendingTimerRef.current);
|
|
137
|
+
pendingTimerRef.current = null;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}, []);
|
|
141
|
+
return { triggerBalanceUpdate };
|
|
142
|
+
};
|
|
143
|
+
var BalanceEventEmitter = class extends EventTarget {
|
|
144
|
+
emit(event) {
|
|
145
|
+
this.dispatchEvent(new CustomEvent("balance-update", { detail: event }));
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
var balanceEventEmitter = new BalanceEventEmitter();
|
|
149
|
+
var useEmitBalanceEvent = () => {
|
|
150
|
+
return (event) => balanceEventEmitter.emit(event);
|
|
151
|
+
};
|
|
152
|
+
var useBalanceEventSubscription = () => {
|
|
153
|
+
const { triggerBalanceUpdate } = useBalanceEventListener();
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
const handler = (event) => {
|
|
156
|
+
triggerBalanceUpdate(event.detail);
|
|
157
|
+
};
|
|
158
|
+
balanceEventEmitter.addEventListener("balance-update", handler);
|
|
159
|
+
return () => balanceEventEmitter.removeEventListener("balance-update", handler);
|
|
160
|
+
}, [triggerBalanceUpdate]);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// src/providers/RfqProvider.tsx
|
|
164
|
+
import {
|
|
165
|
+
createContext,
|
|
166
|
+
useCallback as useCallback2,
|
|
167
|
+
useContext,
|
|
168
|
+
useEffect as useEffect2,
|
|
169
|
+
useMemo,
|
|
170
|
+
useRef as useRef2,
|
|
171
|
+
useState
|
|
172
|
+
} from "react";
|
|
173
|
+
import { formatUnits, parseUnits } from "viem";
|
|
174
|
+
import { jsx } from "react/jsx-runtime";
|
|
175
|
+
var RfqContext = createContext(void 0);
|
|
176
|
+
var POLLING_INTERVAL_MS = 1e4;
|
|
177
|
+
var STALE_WINDOW_MS = 3e4;
|
|
178
|
+
var TYPING_SETTLE_DELAY_MS = 1e3;
|
|
179
|
+
var ROUTING_ERROR_THRESHOLD = 2;
|
|
180
|
+
function selectQuote(quotes) {
|
|
181
|
+
const aori = quotes.find((q) => q.routeSteps?.[0]?.type === "AORI_V1");
|
|
182
|
+
return aori ?? quotes[0];
|
|
183
|
+
}
|
|
184
|
+
var RfqProvider = ({
|
|
185
|
+
children,
|
|
186
|
+
recipient: recipientProp
|
|
187
|
+
}) => {
|
|
188
|
+
const { address: userAddress } = useWalletState();
|
|
189
|
+
const recipient = recipientProp || userAddress;
|
|
190
|
+
const [status, setStatus] = useState("idle");
|
|
191
|
+
const [inputState, setInputState] = useState("idle");
|
|
192
|
+
const [rfqQuote, setRfqQuote] = useState(null);
|
|
193
|
+
const [error, setError] = useState(null);
|
|
194
|
+
const [liquidityError, setLiquidityError] = useState(false);
|
|
195
|
+
const [routingError, setRoutingError] = useState(false);
|
|
196
|
+
const [sizeCapError, setSizeCapError] = useState(false);
|
|
197
|
+
const sessionIdRef = useRef2(null);
|
|
198
|
+
const lastParamsRef = useRef2(null);
|
|
199
|
+
const pollingIntervalRef = useRef2(null);
|
|
200
|
+
const staleTimerRef = useRef2(null);
|
|
201
|
+
const typingTimerRef = useRef2(null);
|
|
202
|
+
const latestRequestId = useRef2(0);
|
|
203
|
+
const lastRequestAtRef = useRef2(0);
|
|
204
|
+
const lastStartAtRef = useRef2(0);
|
|
205
|
+
const hasQuoteRef = useRef2(false);
|
|
206
|
+
const routingErrorsRef = useRef2(/* @__PURE__ */ new Map());
|
|
207
|
+
const clearTimers = useCallback2(() => {
|
|
208
|
+
if (pollingIntervalRef.current) clearTimeout(pollingIntervalRef.current);
|
|
209
|
+
if (staleTimerRef.current) clearTimeout(staleTimerRef.current);
|
|
210
|
+
if (typingTimerRef.current) clearTimeout(typingTimerRef.current);
|
|
211
|
+
pollingIntervalRef.current = null;
|
|
212
|
+
staleTimerRef.current = null;
|
|
213
|
+
typingTimerRef.current = null;
|
|
214
|
+
}, []);
|
|
215
|
+
const stop = useCallback2(() => {
|
|
216
|
+
clearTimers();
|
|
217
|
+
sessionIdRef.current = null;
|
|
218
|
+
latestRequestId.current = 0;
|
|
219
|
+
setStatus(() => hasQuoteRef.current ? "fresh" : "idle");
|
|
220
|
+
}, [clearTimers]);
|
|
221
|
+
const clear = useCallback2(() => {
|
|
222
|
+
stop();
|
|
223
|
+
lastParamsRef.current = null;
|
|
224
|
+
setRfqQuote(null);
|
|
225
|
+
setError(null);
|
|
226
|
+
setLiquidityError(false);
|
|
227
|
+
setRoutingError(false);
|
|
228
|
+
setSizeCapError(false);
|
|
229
|
+
routingErrorsRef.current.clear();
|
|
230
|
+
setStatus("idle");
|
|
231
|
+
setInputState("idle");
|
|
232
|
+
}, [stop]);
|
|
233
|
+
const scheduleStale = useCallback2((receivedAt) => {
|
|
234
|
+
if (!receivedAt) return;
|
|
235
|
+
if (staleTimerRef.current) clearTimeout(staleTimerRef.current);
|
|
236
|
+
const delay = Math.max(0, receivedAt + STALE_WINDOW_MS - Date.now());
|
|
237
|
+
staleTimerRef.current = setTimeout(() => {
|
|
238
|
+
setStatus((prev) => prev === "idle" ? "idle" : "stale");
|
|
239
|
+
}, delay);
|
|
240
|
+
}, []);
|
|
241
|
+
const requestQuoteOnce = useCallback2(
|
|
242
|
+
async (params, sessionId) => {
|
|
243
|
+
const { inputToken, outputToken, inputAmount, setOutputAmount } = params;
|
|
244
|
+
if (!inputToken || !outputToken || !inputAmount || parseFloat(inputAmount) <= 0)
|
|
245
|
+
return;
|
|
246
|
+
const now = Date.now();
|
|
247
|
+
if (now - (lastRequestAtRef.current || 0) < POLLING_INTERVAL_MS - 10)
|
|
248
|
+
return;
|
|
249
|
+
const requestId = ++latestRequestId.current;
|
|
250
|
+
try {
|
|
251
|
+
if (!inputToken?.decimals || !outputToken?.decimals) return;
|
|
252
|
+
const srcChainKey = getKeyForChainId(inputToken.chainId) || getChainConfig(inputToken.chainId)?.key || "";
|
|
253
|
+
const dstChainKey = getKeyForChainId(outputToken.chainId) || getChainConfig(outputToken.chainId)?.key || "";
|
|
254
|
+
if (!srcChainKey || !dstChainKey) throw new Error("Unknown chain");
|
|
255
|
+
const normalizedAmount = parseUnits(
|
|
256
|
+
inputAmount.toString(),
|
|
257
|
+
inputToken.decimals
|
|
258
|
+
).toString();
|
|
259
|
+
const body = {
|
|
260
|
+
srcChainKey,
|
|
261
|
+
dstChainKey,
|
|
262
|
+
srcTokenAddress: checkedAddress(inputToken.address),
|
|
263
|
+
dstTokenAddress: checkedAddress(outputToken.address),
|
|
264
|
+
amount: normalizedAmount,
|
|
265
|
+
srcWalletAddress: userAddress || "0x0000000000000000000000000000000000000000",
|
|
266
|
+
dstWalletAddress: recipient || userAddress || "0x0000000000000000000000000000000000000000",
|
|
267
|
+
options: {
|
|
268
|
+
amountType: "EXACT_SRC_AMOUNT",
|
|
269
|
+
feeTolerance: { type: "PERCENT", amount: 2 }
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
lastRequestAtRef.current = Date.now();
|
|
273
|
+
const res = await fetch(`${getVtApiUrl()}/quotes`, {
|
|
274
|
+
method: "POST",
|
|
275
|
+
headers: getVtHeaders(),
|
|
276
|
+
body: JSON.stringify(body),
|
|
277
|
+
signal: AbortSignal.timeout(15e3)
|
|
278
|
+
});
|
|
279
|
+
if (!res.ok) {
|
|
280
|
+
const errBody = await res.json().catch(() => ({}));
|
|
281
|
+
const msg = errBody?.message || res.statusText;
|
|
282
|
+
throw Object.assign(new Error(msg), { status: res.status });
|
|
283
|
+
}
|
|
284
|
+
const data = await res.json();
|
|
285
|
+
if (requestId !== latestRequestId.current || sessionIdRef.current !== sessionId)
|
|
286
|
+
return;
|
|
287
|
+
const quotes = data.quotes ?? [];
|
|
288
|
+
const selected = selectQuote(quotes);
|
|
289
|
+
if (!selected) throw Object.assign(new Error("No quotes returned"), { emptyQuotes: true });
|
|
290
|
+
const formattedOutput = Number(
|
|
291
|
+
formatUnits(toBigInt(selected.dstAmount), outputToken.decimals)
|
|
292
|
+
);
|
|
293
|
+
if (typeof setOutputAmount === "function") setOutputAmount(formattedOutput);
|
|
294
|
+
selected._receivedAt = Date.now();
|
|
295
|
+
setRfqQuote(selected);
|
|
296
|
+
setError(null);
|
|
297
|
+
setLiquidityError(false);
|
|
298
|
+
setStatus("fresh");
|
|
299
|
+
scheduleStale(selected._receivedAt);
|
|
300
|
+
if (sessionId) routingErrorsRef.current.delete(sessionId);
|
|
301
|
+
} catch (e) {
|
|
302
|
+
if (requestId !== latestRequestId.current || sessionIdRef.current !== sessionId)
|
|
303
|
+
return;
|
|
304
|
+
const statusCode = e?.status;
|
|
305
|
+
const errorMessage = e instanceof Error ? e.message : "";
|
|
306
|
+
const isEmptyQuotes = !!e?.emptyQuotes;
|
|
307
|
+
if ((statusCode === 400 || isEmptyQuotes || errorMessage.includes("Quote request failed")) && sessionId) {
|
|
308
|
+
const current = routingErrorsRef.current.get(sessionId) || 0;
|
|
309
|
+
const newCount = current + 1;
|
|
310
|
+
routingErrorsRef.current.set(sessionId, newCount);
|
|
311
|
+
if (newCount >= ROUTING_ERROR_THRESHOLD) {
|
|
312
|
+
setRoutingError(true);
|
|
313
|
+
clearTimers();
|
|
314
|
+
sessionIdRef.current = null;
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (errorMessage.toLowerCase().includes("order cap exceeded")) {
|
|
319
|
+
setSizeCapError(true);
|
|
320
|
+
clearTimers();
|
|
321
|
+
sessionIdRef.current = null;
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (errorMessage.toLowerCase().includes("insufficient executor balance")) {
|
|
325
|
+
setLiquidityError(true);
|
|
326
|
+
clearTimers();
|
|
327
|
+
sessionIdRef.current = null;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
setError(e instanceof Error ? e.message : "Unknown error");
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
[userAddress, recipient, scheduleStale, clearTimers]
|
|
334
|
+
);
|
|
335
|
+
const startPolling = useCallback2(
|
|
336
|
+
(params, sessionId) => {
|
|
337
|
+
clearTimers();
|
|
338
|
+
lastParamsRef.current = params;
|
|
339
|
+
lastRequestAtRef.current = 0;
|
|
340
|
+
routingErrorsRef.current.delete(sessionId);
|
|
341
|
+
setLiquidityError(false);
|
|
342
|
+
setRoutingError(false);
|
|
343
|
+
setSizeCapError(false);
|
|
344
|
+
setError(null);
|
|
345
|
+
setStatus("polling");
|
|
346
|
+
const poll = async () => {
|
|
347
|
+
if (sessionIdRef.current !== sessionId) return;
|
|
348
|
+
await requestQuoteOnce(params, sessionId);
|
|
349
|
+
if (sessionIdRef.current !== sessionId) return;
|
|
350
|
+
pollingIntervalRef.current = setTimeout(poll, POLLING_INTERVAL_MS);
|
|
351
|
+
};
|
|
352
|
+
poll();
|
|
353
|
+
},
|
|
354
|
+
[clearTimers, requestQuoteOnce]
|
|
355
|
+
);
|
|
356
|
+
const ensureForParams = useCallback2(
|
|
357
|
+
(params) => {
|
|
358
|
+
if (!params?.inputToken || !params?.outputToken || !params?.inputAmount || parseFloat(params.inputAmount) <= 0) {
|
|
359
|
+
stop();
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const prev = lastParamsRef.current;
|
|
363
|
+
const sameParams = prev && prev.inputToken?.address === params.inputToken?.address && prev.inputToken?.chainId === params.inputToken?.chainId && prev.outputToken?.address === params.outputToken?.address && prev.outputToken?.chainId === params.outputToken?.chainId && prev.inputAmount === params.inputAmount;
|
|
364
|
+
if (sameParams && sessionIdRef.current) return;
|
|
365
|
+
const now = Date.now();
|
|
366
|
+
if (now - lastStartAtRef.current < 100) return;
|
|
367
|
+
lastStartAtRef.current = now;
|
|
368
|
+
const newSessionId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
369
|
+
sessionIdRef.current = newSessionId;
|
|
370
|
+
startPolling(params, newSessionId);
|
|
371
|
+
},
|
|
372
|
+
[startPolling, stop]
|
|
373
|
+
);
|
|
374
|
+
const handleInputChange = useCallback2(
|
|
375
|
+
({ amount, inputToken, outputToken, setOutputAmount }) => {
|
|
376
|
+
setOutputAmount(null);
|
|
377
|
+
if (typingTimerRef.current) clearTimeout(typingTimerRef.current);
|
|
378
|
+
if (!amount || amount === 0) {
|
|
379
|
+
setInputState("idle");
|
|
380
|
+
stop();
|
|
381
|
+
setRfqQuote(null);
|
|
382
|
+
setError(null);
|
|
383
|
+
setLiquidityError(false);
|
|
384
|
+
setRoutingError(false);
|
|
385
|
+
setSizeCapError(false);
|
|
386
|
+
} else {
|
|
387
|
+
setInputState("typing");
|
|
388
|
+
stop();
|
|
389
|
+
setRfqQuote(null);
|
|
390
|
+
setLiquidityError(false);
|
|
391
|
+
setRoutingError(false);
|
|
392
|
+
setSizeCapError(false);
|
|
393
|
+
setError(null);
|
|
394
|
+
typingTimerRef.current = setTimeout(() => {
|
|
395
|
+
setInputState("settled");
|
|
396
|
+
if (inputToken && outputToken && amount > 0) {
|
|
397
|
+
ensureForParams({
|
|
398
|
+
inputToken,
|
|
399
|
+
outputToken,
|
|
400
|
+
inputAmount: amount.toString(),
|
|
401
|
+
setOutputAmount
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}, TYPING_SETTLE_DELAY_MS);
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
[stop, ensureForParams]
|
|
408
|
+
);
|
|
409
|
+
const refresh = useCallback2(() => {
|
|
410
|
+
const params = lastParamsRef.current;
|
|
411
|
+
if (!params) return;
|
|
412
|
+
setRfqQuote(null);
|
|
413
|
+
const newSessionId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
414
|
+
sessionIdRef.current = newSessionId;
|
|
415
|
+
setStatus("refreshing");
|
|
416
|
+
if (typeof params.setOutputAmount === "function")
|
|
417
|
+
params.setOutputAmount(null);
|
|
418
|
+
startPolling(params, newSessionId);
|
|
419
|
+
}, [startPolling]);
|
|
420
|
+
useEffect2(() => {
|
|
421
|
+
hasQuoteRef.current = !!rfqQuote;
|
|
422
|
+
}, [rfqQuote]);
|
|
423
|
+
useEffect2(() => () => clearTimers(), [clearTimers]);
|
|
424
|
+
const contextValue = useMemo(
|
|
425
|
+
() => ({
|
|
426
|
+
status,
|
|
427
|
+
inputState,
|
|
428
|
+
rfqQuote,
|
|
429
|
+
error,
|
|
430
|
+
liquidityError,
|
|
431
|
+
routingError,
|
|
432
|
+
sizeCapError,
|
|
433
|
+
ensureForParams,
|
|
434
|
+
stop,
|
|
435
|
+
refresh,
|
|
436
|
+
clear,
|
|
437
|
+
handleInputChange
|
|
438
|
+
}),
|
|
439
|
+
[
|
|
440
|
+
status,
|
|
441
|
+
inputState,
|
|
442
|
+
rfqQuote,
|
|
443
|
+
error,
|
|
444
|
+
liquidityError,
|
|
445
|
+
routingError,
|
|
446
|
+
sizeCapError,
|
|
447
|
+
ensureForParams,
|
|
448
|
+
stop,
|
|
449
|
+
refresh,
|
|
450
|
+
clear,
|
|
451
|
+
handleInputChange
|
|
452
|
+
]
|
|
453
|
+
);
|
|
454
|
+
return /* @__PURE__ */ jsx(RfqContext.Provider, { value: contextValue, children });
|
|
455
|
+
};
|
|
456
|
+
var useRfq = () => {
|
|
457
|
+
const context = useContext(RfqContext);
|
|
458
|
+
if (context === void 0)
|
|
459
|
+
throw new Error("useRfq must be used within an RfqProvider");
|
|
460
|
+
return context;
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// src/wallet/WalletModalContext.tsx
|
|
464
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
465
|
+
var WalletModalContext = createContext2({
|
|
466
|
+
openConnectModal: () => {
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
var useWalletModal = () => useContext2(WalletModalContext);
|
|
470
|
+
|
|
471
|
+
// src/components/AssetAmountInput.tsx
|
|
472
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
|
|
473
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
474
|
+
var MAX_INPUT_LENGTH = 20;
|
|
475
|
+
var AssetAmountInput = ({
|
|
476
|
+
side,
|
|
477
|
+
asset,
|
|
478
|
+
otherAsset,
|
|
479
|
+
isPlacingOrder,
|
|
480
|
+
isWrappingPair,
|
|
481
|
+
isUnwrappingPair
|
|
482
|
+
}) => {
|
|
483
|
+
const { baseAmount, quoteAmount, setBaseAmount, setQuoteAmount } = useSwapFormContext();
|
|
484
|
+
const { amountInputVariant, hideAmountInputSymbol, widgetType } = useWidgetConfig();
|
|
485
|
+
const isCompactMode = widgetType === "compact";
|
|
486
|
+
const [setAssetAmount, setOtherAssetAmount] = side === "base" ? [setBaseAmount, setQuoteAmount] : [setQuoteAmount, setBaseAmount];
|
|
487
|
+
const disabled = !asset || side === "quote" || isPlacingOrder;
|
|
488
|
+
const maxFontSize = isCompactMode ? 32 : amountInputVariant === "default" ? 64 : 32;
|
|
489
|
+
const [fontSize, setFontSize] = useState2(maxFontSize);
|
|
490
|
+
const [inputValue, setInputValue] = useState2("");
|
|
491
|
+
const containerRef = useRef3(null);
|
|
492
|
+
const measureRef = useRef3(null);
|
|
493
|
+
const symbolRef = useRef3(null);
|
|
494
|
+
const inputRef = useRef3(null);
|
|
495
|
+
const placeholderText = "USD";
|
|
496
|
+
const assetAmount = side === "base" ? baseAmount : quoteAmount;
|
|
497
|
+
const [isMounted, setIsMounted] = useState2(false);
|
|
498
|
+
const adjustFontSize = useCallback3(() => {
|
|
499
|
+
if (amountInputVariant !== "default") {
|
|
500
|
+
setFontSize(maxFontSize);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (!containerRef.current || !measureRef.current || !symbolRef.current)
|
|
504
|
+
return;
|
|
505
|
+
const containerWidth = containerRef.current.offsetWidth;
|
|
506
|
+
const padding = 20;
|
|
507
|
+
let newFontSize = maxFontSize;
|
|
508
|
+
while (newFontSize > 20) {
|
|
509
|
+
measureRef.current.style.fontSize = `${newFontSize}px`;
|
|
510
|
+
symbolRef.current.style.fontSize = `${newFontSize}px`;
|
|
511
|
+
const totalWidth = measureRef.current.offsetWidth + symbolRef.current.offsetWidth + padding;
|
|
512
|
+
if (totalWidth <= containerWidth) break;
|
|
513
|
+
newFontSize -= 1;
|
|
514
|
+
}
|
|
515
|
+
setFontSize(newFontSize);
|
|
516
|
+
}, [amountInputVariant, maxFontSize]);
|
|
517
|
+
const handleKeyDown = useCallback3(
|
|
518
|
+
(e) => {
|
|
519
|
+
if (e.key === "." && inputValue.includes(".")) e.preventDefault();
|
|
520
|
+
},
|
|
521
|
+
[inputValue]
|
|
522
|
+
);
|
|
523
|
+
const handleAmountChange = useCallback3(
|
|
524
|
+
(e) => {
|
|
525
|
+
let newValue = e.target.value;
|
|
526
|
+
if (newValue === ".") newValue = "0.";
|
|
527
|
+
if (!/^\d*\.?\d*$/.test(newValue)) return;
|
|
528
|
+
if ((newValue.match(/\./g) || []).length > 1) return;
|
|
529
|
+
if (newValue.length > 1 && newValue[0] === "0" && newValue[1] !== ".") {
|
|
530
|
+
newValue = newValue.replace(/^0+/, "") || "0";
|
|
531
|
+
}
|
|
532
|
+
if (asset?.decimals !== void 0 && newValue.includes(".")) {
|
|
533
|
+
const [, fracPart] = newValue.split(".");
|
|
534
|
+
if (fracPart && fracPart.length > asset.decimals) return;
|
|
535
|
+
}
|
|
536
|
+
if (newValue.length > MAX_INPUT_LENGTH) return;
|
|
537
|
+
setInputValue(newValue);
|
|
538
|
+
const newAmount = parseFloat(newValue);
|
|
539
|
+
if (newValue !== inputValue) {
|
|
540
|
+
if (newValue === "" || newValue === "0." || newValue.endsWith(".")) {
|
|
541
|
+
setAssetAmount(null);
|
|
542
|
+
if (side === "base") setOtherAssetAmount(null);
|
|
543
|
+
} else if (!Number.isNaN(newAmount) && newAmount >= 0) {
|
|
544
|
+
setAssetAmount(newAmount);
|
|
545
|
+
}
|
|
546
|
+
if (side === "base") setQuoteAmount(null);
|
|
547
|
+
if (isUnwrappingPair || isWrappingPair) setQuoteAmount(newAmount || 0);
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
[
|
|
551
|
+
inputValue,
|
|
552
|
+
setAssetAmount,
|
|
553
|
+
side,
|
|
554
|
+
setQuoteAmount,
|
|
555
|
+
asset?.decimals,
|
|
556
|
+
setOtherAssetAmount,
|
|
557
|
+
isUnwrappingPair,
|
|
558
|
+
isWrappingPair
|
|
559
|
+
]
|
|
560
|
+
);
|
|
561
|
+
useEffect3(() => {
|
|
562
|
+
const formatAmount = (amount) => {
|
|
563
|
+
if (amount === null) return "";
|
|
564
|
+
const decimals = asset?.decimals;
|
|
565
|
+
if (decimals === void 0) return "";
|
|
566
|
+
let asString = amount.toString();
|
|
567
|
+
if (/e/i.test(asString)) {
|
|
568
|
+
asString = Number(amount).toLocaleString("en-US", {
|
|
569
|
+
useGrouping: false,
|
|
570
|
+
maximumFractionDigits: decimals
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
if (asString.includes(".")) {
|
|
574
|
+
const [intPart, fracPartRaw] = asString.split(".");
|
|
575
|
+
const fracPart = fracPartRaw.slice(0, decimals);
|
|
576
|
+
return fracPart ? `${intPart}.${fracPart}` : intPart;
|
|
577
|
+
}
|
|
578
|
+
return asString;
|
|
579
|
+
};
|
|
580
|
+
if (assetAmount === null) {
|
|
581
|
+
const isTypingDecimal = inputValue === "0." || inputValue !== "" && inputValue.endsWith(".");
|
|
582
|
+
if (!isTypingDecimal) setInputValue("");
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const isUserTyping = !disabled && (inputValue.endsWith(".") || inputValue.match(/\.\d*0$/) || inputValue === "0" || inputValue === "0.");
|
|
586
|
+
if (isUserTyping && inputValue !== "") return;
|
|
587
|
+
const currentNumericValue = parseFloat(inputValue) || 0;
|
|
588
|
+
if (inputValue === "" || Math.abs(currentNumericValue - assetAmount) > 1e-10) {
|
|
589
|
+
setInputValue(formatAmount(assetAmount));
|
|
590
|
+
}
|
|
591
|
+
}, [assetAmount, asset?.decimals, inputValue, disabled]);
|
|
592
|
+
useEffect3(() => {
|
|
593
|
+
adjustFontSize();
|
|
594
|
+
}, [inputValue, adjustFontSize]);
|
|
595
|
+
useEffect3(() => {
|
|
596
|
+
setIsMounted(true);
|
|
597
|
+
}, []);
|
|
598
|
+
useEffect3(() => {
|
|
599
|
+
if (isMounted) adjustFontSize();
|
|
600
|
+
}, [isMounted, adjustFontSize]);
|
|
601
|
+
useEffect3(() => {
|
|
602
|
+
if (measureRef.current && inputRef.current) {
|
|
603
|
+
inputRef.current.style.width = `${measureRef.current.offsetWidth}px`;
|
|
604
|
+
}
|
|
605
|
+
}, [inputValue]);
|
|
606
|
+
useEffect3(() => {
|
|
607
|
+
if (!isMounted) return;
|
|
608
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
609
|
+
if (isMounted) adjustFontSize();
|
|
610
|
+
});
|
|
611
|
+
if (containerRef.current) resizeObserver.observe(containerRef.current);
|
|
612
|
+
return () => resizeObserver.disconnect();
|
|
613
|
+
}, [isMounted, adjustFontSize]);
|
|
614
|
+
return /* @__PURE__ */ jsx2(
|
|
615
|
+
"div",
|
|
616
|
+
{
|
|
617
|
+
ref: containerRef,
|
|
618
|
+
style: {
|
|
619
|
+
fontSize: `${fontSize}px`,
|
|
620
|
+
transition: "font-size 0.05s ease-in-out"
|
|
621
|
+
},
|
|
622
|
+
className: `mt-2 box-border flex ${isCompactMode ? amountInputVariant === "normal" ? "h-12 my-1" : "h-10 my-0.5" : amountInputVariant === "normal" ? "h-12 my-1" : "h-20 my-1"} w-full flex-row items-center ${disabled ? "cursor-default" : "cursor-text"}`,
|
|
623
|
+
onClick: () => inputRef.current?.focus(),
|
|
624
|
+
children: /* @__PURE__ */ jsxs("div", { className: "relative flex w-full items-center", children: [
|
|
625
|
+
/* @__PURE__ */ jsx2("label", { htmlFor: `${side}Amount`, className: "sr-only", children: side === "base" ? "Input token amount" : "Output token amount" }),
|
|
626
|
+
/* @__PURE__ */ jsx2(
|
|
627
|
+
"input",
|
|
628
|
+
{
|
|
629
|
+
ref: inputRef,
|
|
630
|
+
type: "text",
|
|
631
|
+
inputMode: "decimal",
|
|
632
|
+
placeholder: "0",
|
|
633
|
+
autoComplete: "off",
|
|
634
|
+
id: `${side}Amount`,
|
|
635
|
+
value: inputValue,
|
|
636
|
+
onWheel: (e) => e.target.blur(),
|
|
637
|
+
onKeyDown: handleKeyDown,
|
|
638
|
+
onChange: handleAmountChange,
|
|
639
|
+
style: {
|
|
640
|
+
transition: "width 0.15s ease-in-out",
|
|
641
|
+
color: disabled ? "var(--widget-foreground)" : "var(--widget-foreground)",
|
|
642
|
+
WebkitTextFillColor: disabled ? "var(--widget-foreground)" : "var(--widget-foreground)",
|
|
643
|
+
opacity: disabled ? 0.4 : 1
|
|
644
|
+
},
|
|
645
|
+
className: "flex min-w-[45px] whitespace-nowrap bg-transparent placeholder:opacity-50",
|
|
646
|
+
disabled
|
|
647
|
+
}
|
|
648
|
+
),
|
|
649
|
+
/* @__PURE__ */ jsx2(
|
|
650
|
+
"span",
|
|
651
|
+
{
|
|
652
|
+
style: { transition: "width 0.15s ease-in-out" },
|
|
653
|
+
ref: measureRef,
|
|
654
|
+
className: "pointer-events-none absolute flex whitespace-nowrap invisible",
|
|
655
|
+
children: inputValue || 0
|
|
656
|
+
}
|
|
657
|
+
),
|
|
658
|
+
!hideAmountInputSymbol && amountInputVariant !== "normal" && /* @__PURE__ */ jsx2(
|
|
659
|
+
"span",
|
|
660
|
+
{
|
|
661
|
+
ref: symbolRef,
|
|
662
|
+
className: "ml-2 font-light uppercase",
|
|
663
|
+
style: {
|
|
664
|
+
color: "var(--widget-foreground)",
|
|
665
|
+
opacity: 0.5,
|
|
666
|
+
fontSize: `${fontSize}px`
|
|
667
|
+
},
|
|
668
|
+
children: asset?.symbol ? asset.symbol.length > 6 ? `${asset.symbol.slice(0, 6)}...` : asset.symbol : placeholderText
|
|
669
|
+
}
|
|
670
|
+
),
|
|
671
|
+
(hideAmountInputSymbol || amountInputVariant === "normal") && /* @__PURE__ */ jsx2("span", { ref: symbolRef, className: "hidden" })
|
|
672
|
+
] })
|
|
673
|
+
}
|
|
674
|
+
);
|
|
675
|
+
};
|
|
676
|
+
var AssetAmountInput_default = AssetAmountInput;
|
|
677
|
+
|
|
678
|
+
// src/components/AssetSelection.tsx
|
|
679
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
680
|
+
var AssetSelection = ({
|
|
681
|
+
toggle,
|
|
682
|
+
side,
|
|
683
|
+
asset,
|
|
684
|
+
isPlacingOrder
|
|
685
|
+
}) => {
|
|
686
|
+
const { baseBalance, quoteBalance } = useSwapFormContext();
|
|
687
|
+
const { tokenDisplay, tokenBadgeOrientation, widgetType } = useWidgetConfig();
|
|
688
|
+
const isCompactMode = widgetType === "compact";
|
|
689
|
+
const cardHeight = isCompactMode ? "h-10" : "h-12";
|
|
690
|
+
const iconSize = isCompactMode ? "h-10 w-10" : "h-12 w-12";
|
|
691
|
+
const userBalance = side === "base" ? baseBalance.formatted ? (() => {
|
|
692
|
+
const value = parseFloat(baseBalance.formatted);
|
|
693
|
+
return value >= 1e8 ? formatNumber(value) : value.toFixed(3);
|
|
694
|
+
})() : void 0 : quoteBalance.formatted ? (() => {
|
|
695
|
+
const value = parseFloat(quoteBalance.formatted);
|
|
696
|
+
return value >= 1e8 ? formatNumber(value) : value.toFixed(3);
|
|
697
|
+
})() : void 0;
|
|
698
|
+
const sharedProps = {
|
|
699
|
+
role: "button",
|
|
700
|
+
tabIndex: isPlacingOrder ? -1 : 0,
|
|
701
|
+
"aria-disabled": isPlacingOrder,
|
|
702
|
+
"aria-label": `Select ${side === "base" ? "input" : "output"} token${asset ? `: ${asset.symbol}` : ""}`,
|
|
703
|
+
onClick: !isPlacingOrder ? toggle : void 0,
|
|
704
|
+
onKeyDown: !isPlacingOrder ? (e) => {
|
|
705
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
706
|
+
e.preventDefault();
|
|
707
|
+
toggle();
|
|
708
|
+
}
|
|
709
|
+
} : void 0
|
|
710
|
+
};
|
|
711
|
+
if (tokenDisplay === "pill") {
|
|
712
|
+
const isRight = tokenBadgeOrientation === "right";
|
|
713
|
+
return /* @__PURE__ */ jsx3(
|
|
714
|
+
"div",
|
|
715
|
+
{
|
|
716
|
+
...sharedProps,
|
|
717
|
+
className: `inline-flex items-center gap-1.5 h-7 px-2.5 rounded-full cursor-pointer transition-colors ${isRight ? "ml-auto" : ""}`,
|
|
718
|
+
style: {
|
|
719
|
+
border: "1px solid var(--widget-border)",
|
|
720
|
+
backgroundColor: "var(--widget-secondary)",
|
|
721
|
+
color: "var(--widget-secondary-foreground)"
|
|
722
|
+
},
|
|
723
|
+
children: asset ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
724
|
+
/* @__PURE__ */ jsx3(ChainIcon_default, { chain: asset.chainId, size: "xs" }),
|
|
725
|
+
/* @__PURE__ */ jsx3(
|
|
726
|
+
TokenImage_default,
|
|
727
|
+
{
|
|
728
|
+
className: "rounded-full",
|
|
729
|
+
asset,
|
|
730
|
+
size: "xxs",
|
|
731
|
+
noChain: true
|
|
732
|
+
}
|
|
733
|
+
),
|
|
734
|
+
/* @__PURE__ */ jsx3("span", { className: "text-xs font-medium uppercase", children: asset.symbol }),
|
|
735
|
+
/* @__PURE__ */ jsx3(
|
|
736
|
+
DropdownIcon,
|
|
737
|
+
{
|
|
738
|
+
className: "w-1.5 opacity-60",
|
|
739
|
+
style: { color: "var(--widget-secondary-foreground)" }
|
|
740
|
+
}
|
|
741
|
+
)
|
|
742
|
+
] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
743
|
+
/* @__PURE__ */ jsx3("span", { className: "text-xs opacity-70", children: "Select" }),
|
|
744
|
+
/* @__PURE__ */ jsx3(
|
|
745
|
+
DropdownIcon,
|
|
746
|
+
{
|
|
747
|
+
className: "w-1.5 opacity-60",
|
|
748
|
+
style: { color: "var(--widget-secondary-foreground)" }
|
|
749
|
+
}
|
|
750
|
+
)
|
|
751
|
+
] })
|
|
752
|
+
}
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
if (tokenDisplay === "ghost") {
|
|
756
|
+
return /* @__PURE__ */ jsxs2(
|
|
757
|
+
"div",
|
|
758
|
+
{
|
|
759
|
+
...sharedProps,
|
|
760
|
+
className: "inline-flex items-center gap-1.5 cursor-pointer transition-opacity hover:opacity-70 py-1",
|
|
761
|
+
style: { color: "var(--widget-foreground)" },
|
|
762
|
+
children: [
|
|
763
|
+
asset ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
764
|
+
/* @__PURE__ */ jsx3(ChainIcon_default, { chain: asset.chainId, size: "xs" }),
|
|
765
|
+
/* @__PURE__ */ jsx3(
|
|
766
|
+
TokenImage_default,
|
|
767
|
+
{
|
|
768
|
+
className: "rounded-full",
|
|
769
|
+
asset,
|
|
770
|
+
size: "xxs",
|
|
771
|
+
noChain: true
|
|
772
|
+
}
|
|
773
|
+
),
|
|
774
|
+
/* @__PURE__ */ jsx3("span", { className: "text-sm font-medium uppercase", children: asset.symbol })
|
|
775
|
+
] }) : /* @__PURE__ */ jsx3(
|
|
776
|
+
"span",
|
|
777
|
+
{
|
|
778
|
+
className: "text-sm",
|
|
779
|
+
style: { color: "var(--widget-muted-foreground)" },
|
|
780
|
+
children: "Select token"
|
|
781
|
+
}
|
|
782
|
+
),
|
|
783
|
+
/* @__PURE__ */ jsx3(DropdownIcon, { className: "w-1.5 opacity-50" })
|
|
784
|
+
]
|
|
785
|
+
}
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
return /* @__PURE__ */ jsx3(
|
|
789
|
+
"div",
|
|
790
|
+
{
|
|
791
|
+
...sharedProps,
|
|
792
|
+
className: `relative ${cardHeight} w-full cursor-pointer text-xl duration-100 ease-linear`,
|
|
793
|
+
style: {
|
|
794
|
+
border: "1px solid var(--widget-border)",
|
|
795
|
+
backgroundColor: "var(--widget-secondary)",
|
|
796
|
+
borderTopLeftRadius: "9999px",
|
|
797
|
+
borderBottomLeftRadius: "9999px",
|
|
798
|
+
borderTopRightRadius: "var(--widget-radius)",
|
|
799
|
+
borderBottomRightRadius: "var(--widget-radius)"
|
|
800
|
+
},
|
|
801
|
+
children: /* @__PURE__ */ jsx3("div", { className: "relative flex h-full w-full flex-row items-center", children: asset ? /* @__PURE__ */ jsxs2("div", { className: "relative flex h-full w-full flex-row items-center", children: [
|
|
802
|
+
/* @__PURE__ */ jsx3(
|
|
803
|
+
"div",
|
|
804
|
+
{
|
|
805
|
+
className: `${iconSize} pl-px flex items-center justify-center`,
|
|
806
|
+
children: /* @__PURE__ */ jsx3(TokenImage_default, { className: "rounded-full", asset, size: "md" })
|
|
807
|
+
}
|
|
808
|
+
),
|
|
809
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex h-full w-full flex-row items-center justify-between pl-1.5 pr-7", children: [
|
|
810
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex h-full flex-col justify-center", children: [
|
|
811
|
+
/* @__PURE__ */ jsx3(
|
|
812
|
+
"p",
|
|
813
|
+
{
|
|
814
|
+
className: "text-sm font-medium uppercase",
|
|
815
|
+
style: { color: "var(--widget-secondary-foreground)" },
|
|
816
|
+
children: asset.symbol
|
|
817
|
+
}
|
|
818
|
+
),
|
|
819
|
+
/* @__PURE__ */ jsxs2(
|
|
820
|
+
"span",
|
|
821
|
+
{
|
|
822
|
+
className: "text-2xs font-mono",
|
|
823
|
+
style: {
|
|
824
|
+
color: "var(--widget-secondary-foreground)",
|
|
825
|
+
opacity: 0.6
|
|
826
|
+
},
|
|
827
|
+
children: [
|
|
828
|
+
asset.address?.slice(0, 6),
|
|
829
|
+
"...",
|
|
830
|
+
asset.address?.slice(-4)
|
|
831
|
+
]
|
|
832
|
+
}
|
|
833
|
+
)
|
|
834
|
+
] }),
|
|
835
|
+
userBalance !== void 0 && /* @__PURE__ */ jsxs2("div", { className: "flex flex-col text-right", children: [
|
|
836
|
+
/* @__PURE__ */ jsx3(
|
|
837
|
+
"p",
|
|
838
|
+
{
|
|
839
|
+
className: "text-2xs",
|
|
840
|
+
style: {
|
|
841
|
+
color: "var(--widget-secondary-foreground)",
|
|
842
|
+
opacity: 0.5
|
|
843
|
+
},
|
|
844
|
+
children: "Balance"
|
|
845
|
+
}
|
|
846
|
+
),
|
|
847
|
+
/* @__PURE__ */ jsx3(
|
|
848
|
+
"span",
|
|
849
|
+
{
|
|
850
|
+
className: "font-mono text-xs tabular-nums",
|
|
851
|
+
style: { color: "var(--widget-secondary-foreground)" },
|
|
852
|
+
children: userBalance
|
|
853
|
+
}
|
|
854
|
+
)
|
|
855
|
+
] })
|
|
856
|
+
] }),
|
|
857
|
+
/* @__PURE__ */ jsx3(
|
|
858
|
+
DropdownIcon,
|
|
859
|
+
{
|
|
860
|
+
className: "absolute right-3 top-1/2 w-1.5 translate-y-[-50%]",
|
|
861
|
+
style: {
|
|
862
|
+
color: "var(--widget-secondary-foreground)",
|
|
863
|
+
opacity: 0.6
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
)
|
|
867
|
+
] }) : /* @__PURE__ */ jsx3("div", { className: "flex h-full w-full items-center justify-center", children: /* @__PURE__ */ jsx3(
|
|
868
|
+
"span",
|
|
869
|
+
{
|
|
870
|
+
className: "text-sm",
|
|
871
|
+
style: {
|
|
872
|
+
color: "var(--widget-secondary-foreground)",
|
|
873
|
+
opacity: 0.7
|
|
874
|
+
},
|
|
875
|
+
children: "Select token"
|
|
876
|
+
}
|
|
877
|
+
) }) })
|
|
878
|
+
}
|
|
879
|
+
);
|
|
880
|
+
};
|
|
881
|
+
var AssetSelection_default = AssetSelection;
|
|
882
|
+
|
|
883
|
+
// src/components/RecipientForm.tsx
|
|
884
|
+
var import_ethereum_gradient_base64 = __toESM(require_main(), 1);
|
|
885
|
+
import { useState as useState3, useEffect as useEffect4 } from "react";
|
|
886
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
887
|
+
var RecipientForm = () => {
|
|
888
|
+
const [recipient, setRecipient] = useState3(null);
|
|
889
|
+
const [inputValue, setInputValue] = useState3("");
|
|
890
|
+
const debouncedValue = useDebounce(inputValue, 1e3);
|
|
891
|
+
const [isError, setIsError] = useState3(false);
|
|
892
|
+
const [errorMessage, setErrorMessage] = useState3("");
|
|
893
|
+
useEffect4(() => {
|
|
894
|
+
useWidgetSwapUIStore.getState().setRecipient(recipient);
|
|
895
|
+
}, [recipient]);
|
|
896
|
+
useEffect4(() => {
|
|
897
|
+
if (recipient) setInputValue(recipient);
|
|
898
|
+
else setInputValue("");
|
|
899
|
+
}, [recipient]);
|
|
900
|
+
const handleInputChange = (e) => {
|
|
901
|
+
const value = e.target.value;
|
|
902
|
+
if (isError) {
|
|
903
|
+
setIsError(false);
|
|
904
|
+
setErrorMessage("");
|
|
905
|
+
setRecipient(null);
|
|
906
|
+
}
|
|
907
|
+
setInputValue(value);
|
|
908
|
+
if (value === "") setRecipient(null);
|
|
909
|
+
};
|
|
910
|
+
useEffect4(() => {
|
|
911
|
+
if (debouncedValue === "" || !debouncedValue) {
|
|
912
|
+
setIsError(false);
|
|
913
|
+
setErrorMessage("");
|
|
914
|
+
setRecipient(null);
|
|
915
|
+
} else if (!isAddress(debouncedValue)) {
|
|
916
|
+
setIsError(true);
|
|
917
|
+
setErrorMessage("Invalid wallet address...");
|
|
918
|
+
setRecipient(null);
|
|
919
|
+
setInputValue("");
|
|
920
|
+
} else {
|
|
921
|
+
setIsError(false);
|
|
922
|
+
setErrorMessage("");
|
|
923
|
+
setRecipient(debouncedValue);
|
|
924
|
+
}
|
|
925
|
+
}, [debouncedValue]);
|
|
926
|
+
useEffect4(() => {
|
|
927
|
+
if (isError) {
|
|
928
|
+
const timeout = setTimeout(() => {
|
|
929
|
+
setRecipient(null);
|
|
930
|
+
setInputValue("");
|
|
931
|
+
setIsError(false);
|
|
932
|
+
setErrorMessage("");
|
|
933
|
+
}, 2e3);
|
|
934
|
+
return () => clearTimeout(timeout);
|
|
935
|
+
}
|
|
936
|
+
}, [isError]);
|
|
937
|
+
const handleClear = () => {
|
|
938
|
+
setRecipient(null);
|
|
939
|
+
setInputValue("");
|
|
940
|
+
setIsError(false);
|
|
941
|
+
setErrorMessage("");
|
|
942
|
+
};
|
|
943
|
+
return /* @__PURE__ */ jsx4("div", { className: "w-full", children: /* @__PURE__ */ jsxs3("div", { className: "relative flex flex-row items-center", children: [
|
|
944
|
+
/* @__PURE__ */ jsx4(
|
|
945
|
+
"input",
|
|
946
|
+
{
|
|
947
|
+
type: "password",
|
|
948
|
+
style: { display: "none" },
|
|
949
|
+
"aria-hidden": "true",
|
|
950
|
+
tabIndex: -1
|
|
951
|
+
}
|
|
952
|
+
),
|
|
953
|
+
/* @__PURE__ */ jsx4("label", { htmlFor: "recipient-address", className: "sr-only", children: "Recipient wallet address" }),
|
|
954
|
+
/* @__PURE__ */ jsx4(
|
|
955
|
+
"div",
|
|
956
|
+
{
|
|
957
|
+
className: "absolute left-3 flex h-6 w-6 items-end justify-center overflow-hidden rounded-full",
|
|
958
|
+
style: { backgroundColor: "var(--widget-secondary)" },
|
|
959
|
+
children: recipient && !isError ? /* @__PURE__ */ jsx4(
|
|
960
|
+
"img",
|
|
961
|
+
{
|
|
962
|
+
className: "h-full w-full rounded-full object-cover",
|
|
963
|
+
src: (0, import_ethereum_gradient_base64.makeGradient)(recipient),
|
|
964
|
+
alt: "Recipient avatar"
|
|
965
|
+
}
|
|
966
|
+
) : /* @__PURE__ */ jsx4(
|
|
967
|
+
UserIcon,
|
|
968
|
+
{
|
|
969
|
+
className: "h-5 w-5",
|
|
970
|
+
style: {
|
|
971
|
+
color: isError ? "var(--widget-destructive)" : "var(--widget-muted-foreground)"
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
)
|
|
975
|
+
}
|
|
976
|
+
),
|
|
977
|
+
/* @__PURE__ */ jsx4(
|
|
978
|
+
"div",
|
|
979
|
+
{
|
|
980
|
+
className: "absolute left-12 top-1.5 -translate-y-1 text-xs font-medium opacity-60",
|
|
981
|
+
style: {
|
|
982
|
+
color: isError ? "var(--widget-destructive)" : "var(--widget-muted-foreground)"
|
|
983
|
+
},
|
|
984
|
+
"aria-hidden": "true",
|
|
985
|
+
children: "To:"
|
|
986
|
+
}
|
|
987
|
+
),
|
|
988
|
+
/* @__PURE__ */ jsx4(
|
|
989
|
+
"input",
|
|
990
|
+
{
|
|
991
|
+
id: "recipient-address",
|
|
992
|
+
className: "mono block h-10 w-full items-center pl-12 pt-4 pr-8 text-xs",
|
|
993
|
+
style: {
|
|
994
|
+
backgroundColor: isError ? `color-mix(in srgb, var(--widget-destructive) 10%, transparent)` : "transparent",
|
|
995
|
+
color: isError ? "var(--widget-destructive)" : "var(--widget-foreground)",
|
|
996
|
+
outline: "none",
|
|
997
|
+
opacity: isError ? 0.9 : 1
|
|
998
|
+
},
|
|
999
|
+
placeholder: isError ? errorMessage : "Enter recipient address...",
|
|
1000
|
+
value: inputValue,
|
|
1001
|
+
onChange: handleInputChange,
|
|
1002
|
+
spellCheck: false,
|
|
1003
|
+
"aria-invalid": isError,
|
|
1004
|
+
"aria-describedby": isError ? "recipient-error" : void 0
|
|
1005
|
+
}
|
|
1006
|
+
),
|
|
1007
|
+
isError && /* @__PURE__ */ jsx4("span", { id: "recipient-error", className: "sr-only", children: errorMessage }),
|
|
1008
|
+
recipient && !isError && /* @__PURE__ */ jsxs3(
|
|
1009
|
+
"button",
|
|
1010
|
+
{
|
|
1011
|
+
type: "button",
|
|
1012
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 cursor-pointer flex items-center justify-center h-4 w-4 rounded-full transition-colors hover:[color:var(--widget-destructive)]",
|
|
1013
|
+
style: { color: "var(--widget-muted-foreground)" },
|
|
1014
|
+
onClick: handleClear,
|
|
1015
|
+
"aria-label": "Clear recipient",
|
|
1016
|
+
children: [
|
|
1017
|
+
/* @__PURE__ */ jsx4("svg", { className: "h-2.5 w-2.5", viewBox: "0 -0.5 21 21", fill: "none", children: /* @__PURE__ */ jsx4("g", { fill: "currentColor", fillRule: "evenodd", children: /* @__PURE__ */ jsx4(
|
|
1018
|
+
"polygon",
|
|
1019
|
+
{
|
|
1020
|
+
points: "375.0183 90 384 98.554 382.48065 100 373.5 91.446 364.5183 100 363 98.554 371.98065 90 363 81.446 364.5183 80 373.5 88.554 382.48065 80 384 81.446",
|
|
1021
|
+
transform: "translate(-363 -80)"
|
|
1022
|
+
}
|
|
1023
|
+
) }) }),
|
|
1024
|
+
/* @__PURE__ */ jsx4("span", { className: "sr-only", children: "Clear recipient" })
|
|
1025
|
+
]
|
|
1026
|
+
}
|
|
1027
|
+
)
|
|
1028
|
+
] }) });
|
|
1029
|
+
};
|
|
1030
|
+
var RecipientForm_default = RecipientForm;
|
|
1031
|
+
|
|
1032
|
+
// src/lib/pollOrderStatus.ts
|
|
1033
|
+
var TERMINAL_STATUSES = ["SUCCEEDED", "FAILED", "COMPLETED", "CANCELLED"];
|
|
1034
|
+
async function pollOrderStatus(quoteId, baseUrl, options = {}) {
|
|
1035
|
+
const {
|
|
1036
|
+
onStatusChange,
|
|
1037
|
+
onComplete,
|
|
1038
|
+
onError,
|
|
1039
|
+
interval = 4e3,
|
|
1040
|
+
timeout = 3e5,
|
|
1041
|
+
txHash,
|
|
1042
|
+
signal
|
|
1043
|
+
} = options;
|
|
1044
|
+
let lastStatus = null;
|
|
1045
|
+
const startTime = Date.now();
|
|
1046
|
+
let consecutiveErrorCount = 0;
|
|
1047
|
+
const MAX_CONSECUTIVE_ERRORS = 8;
|
|
1048
|
+
let timeoutId = null;
|
|
1049
|
+
const queryString = txHash ? `?txHash=${txHash}` : "";
|
|
1050
|
+
return new Promise((resolve, reject) => {
|
|
1051
|
+
const checkStatus = async () => {
|
|
1052
|
+
try {
|
|
1053
|
+
if (signal?.aborted) {
|
|
1054
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1055
|
+
reject(new DOMException("Polling aborted", "AbortError"));
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
if (Date.now() - startTime > timeout) {
|
|
1059
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1060
|
+
const error = new Error("Order status polling timed out");
|
|
1061
|
+
onError?.(error);
|
|
1062
|
+
reject(error);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
const response = await fetch(`${baseUrl}/status/${quoteId}${queryString}`, {
|
|
1066
|
+
headers: getVtHeaders(),
|
|
1067
|
+
signal
|
|
1068
|
+
});
|
|
1069
|
+
if (response.status === 404) {
|
|
1070
|
+
timeoutId = setTimeout(checkStatus, interval);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
if (!response.ok) {
|
|
1074
|
+
throw new Error(`Failed to fetch order status: ${await response.text()}`);
|
|
1075
|
+
}
|
|
1076
|
+
const status = await response.json();
|
|
1077
|
+
if (!status || typeof status !== "object" || !status.status) {
|
|
1078
|
+
throw new Error(`Invalid status response format: ${JSON.stringify(status)}`);
|
|
1079
|
+
}
|
|
1080
|
+
consecutiveErrorCount = 0;
|
|
1081
|
+
const normalized = status.status.toUpperCase();
|
|
1082
|
+
if (normalized !== lastStatus) {
|
|
1083
|
+
lastStatus = normalized;
|
|
1084
|
+
onStatusChange?.(status);
|
|
1085
|
+
}
|
|
1086
|
+
if (TERMINAL_STATUSES.includes(normalized)) {
|
|
1087
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1088
|
+
onComplete?.(status);
|
|
1089
|
+
resolve(status);
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
timeoutId = setTimeout(checkStatus, interval);
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1095
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1096
|
+
reject(error);
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
if (++consecutiveErrorCount >= MAX_CONSECUTIVE_ERRORS) {
|
|
1100
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1101
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1102
|
+
onError?.(err);
|
|
1103
|
+
reject(err);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
timeoutId = setTimeout(checkStatus, interval);
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
checkStatus();
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// src/hooks/useOrderStatusPolling.ts
|
|
1114
|
+
import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef4 } from "react";
|
|
1115
|
+
function normalizeStatus(vtStatus) {
|
|
1116
|
+
const upper = vtStatus.toUpperCase();
|
|
1117
|
+
if (upper === "SUCCEEDED" || upper === "COMPLETED") return "completed";
|
|
1118
|
+
if (upper === "FAILED") return "failed";
|
|
1119
|
+
if (upper === "CANCELLED") return "cancelled";
|
|
1120
|
+
if (upper === "PROCESSING") return "received";
|
|
1121
|
+
return "pending";
|
|
1122
|
+
}
|
|
1123
|
+
var useOrderStatusPolling = (onStatusUpdate) => {
|
|
1124
|
+
const activePolls = useRef4(/* @__PURE__ */ new Map());
|
|
1125
|
+
const orderMetadata = useRef4(/* @__PURE__ */ new Map());
|
|
1126
|
+
const abortControllers = useRef4(/* @__PURE__ */ new Map());
|
|
1127
|
+
const pollingLock = useRef4(0);
|
|
1128
|
+
const incrementPollingLock = useCallback4(() => ++pollingLock.current, []);
|
|
1129
|
+
const { address: userAddress } = useWalletState();
|
|
1130
|
+
const emitBalanceEvent = useEmitBalanceEvent();
|
|
1131
|
+
const startPolling = useCallback4(
|
|
1132
|
+
(quoteId, metadata) => {
|
|
1133
|
+
if (activePolls.current.get(quoteId)) return;
|
|
1134
|
+
if (metadata) orderMetadata.current.set(quoteId, metadata);
|
|
1135
|
+
activePolls.current.set(quoteId, true);
|
|
1136
|
+
const currentLock = pollingLock.current;
|
|
1137
|
+
const abortController = new AbortController();
|
|
1138
|
+
abortControllers.current.set(quoteId, abortController);
|
|
1139
|
+
const isMainnet = metadata?.baseToken?.chainId === 1;
|
|
1140
|
+
pollOrderStatus(
|
|
1141
|
+
quoteId,
|
|
1142
|
+
getVtApiUrl(),
|
|
1143
|
+
{
|
|
1144
|
+
interval: isMainnet ? 1e3 : 500,
|
|
1145
|
+
timeout: 3e5,
|
|
1146
|
+
signal: abortController.signal,
|
|
1147
|
+
onStatusChange: (status) => {
|
|
1148
|
+
if (!status?.status) return;
|
|
1149
|
+
const statusValue = normalizeStatus(status.status);
|
|
1150
|
+
onStatusUpdate?.(quoteId, statusValue);
|
|
1151
|
+
if (statusValue === "completed" && userAddress) {
|
|
1152
|
+
const meta = orderMetadata.current.get(quoteId);
|
|
1153
|
+
if (meta && (meta.baseToken || meta.quoteToken)) {
|
|
1154
|
+
const tokens = [];
|
|
1155
|
+
if (meta.baseToken) tokens.push({ asset: meta.baseToken, userAddress });
|
|
1156
|
+
if (meta.quoteToken) tokens.push({ asset: meta.quoteToken, userAddress });
|
|
1157
|
+
emitBalanceEvent({ type: "swap", tokens });
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
if (["completed", "failed", "cancelled"].includes(statusValue)) {
|
|
1161
|
+
activePolls.current.delete(quoteId);
|
|
1162
|
+
orderMetadata.current.delete(quoteId);
|
|
1163
|
+
}
|
|
1164
|
+
if (currentLock !== pollingLock.current) {
|
|
1165
|
+
activePolls.current.delete(quoteId);
|
|
1166
|
+
orderMetadata.current.delete(quoteId);
|
|
1167
|
+
}
|
|
1168
|
+
},
|
|
1169
|
+
onComplete: (status) => {
|
|
1170
|
+
if (status?.status) {
|
|
1171
|
+
onStatusUpdate?.(quoteId, normalizeStatus(status.status));
|
|
1172
|
+
}
|
|
1173
|
+
activePolls.current.delete(quoteId);
|
|
1174
|
+
orderMetadata.current.delete(quoteId);
|
|
1175
|
+
},
|
|
1176
|
+
onError: () => {
|
|
1177
|
+
activePolls.current.delete(quoteId);
|
|
1178
|
+
orderMetadata.current.delete(quoteId);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
).catch((error) => {
|
|
1182
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
1183
|
+
activePolls.current.delete(quoteId);
|
|
1184
|
+
orderMetadata.current.delete(quoteId);
|
|
1185
|
+
abortControllers.current.delete(quoteId);
|
|
1186
|
+
});
|
|
1187
|
+
},
|
|
1188
|
+
[userAddress, emitBalanceEvent, onStatusUpdate]
|
|
1189
|
+
);
|
|
1190
|
+
const stopPolling = useCallback4(
|
|
1191
|
+
(quoteId) => {
|
|
1192
|
+
const controller = abortControllers.current.get(quoteId);
|
|
1193
|
+
if (controller) {
|
|
1194
|
+
controller.abort();
|
|
1195
|
+
abortControllers.current.delete(quoteId);
|
|
1196
|
+
}
|
|
1197
|
+
incrementPollingLock();
|
|
1198
|
+
activePolls.current.delete(quoteId);
|
|
1199
|
+
orderMetadata.current.delete(quoteId);
|
|
1200
|
+
},
|
|
1201
|
+
[incrementPollingLock]
|
|
1202
|
+
);
|
|
1203
|
+
const stopAllPolling = useCallback4(() => {
|
|
1204
|
+
for (const controller of abortControllers.current.values()) controller.abort();
|
|
1205
|
+
abortControllers.current.clear();
|
|
1206
|
+
incrementPollingLock();
|
|
1207
|
+
activePolls.current.clear();
|
|
1208
|
+
orderMetadata.current.clear();
|
|
1209
|
+
}, [incrementPollingLock]);
|
|
1210
|
+
useEffect5(() => {
|
|
1211
|
+
return () => stopAllPolling();
|
|
1212
|
+
}, [stopAllPolling]);
|
|
1213
|
+
return {
|
|
1214
|
+
startPolling,
|
|
1215
|
+
stopPolling,
|
|
1216
|
+
stopAllPolling,
|
|
1217
|
+
isPolling: (quoteId) => activePolls.current.has(quoteId)
|
|
1218
|
+
};
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1221
|
+
// src/wallet/TransactionRegistryContext.tsx
|
|
1222
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
1223
|
+
var TransactionRegistryContext = createContext3({
|
|
1224
|
+
registerTransaction: () => {
|
|
1225
|
+
},
|
|
1226
|
+
enabled: false
|
|
1227
|
+
});
|
|
1228
|
+
var useTransactionRegistry = () => useContext3(TransactionRegistryContext);
|
|
1229
|
+
|
|
1230
|
+
// src/lib/signSwap.ts
|
|
1231
|
+
import { signTypedData } from "viem/actions";
|
|
1232
|
+
function isUserRejection(error) {
|
|
1233
|
+
if (!(error instanceof Error)) return false;
|
|
1234
|
+
return error.name === "UserRejectedRequestError" || error.message.includes("User rejected") || error.message.includes("rejected") || error.message.includes("denied") || error.message.includes("cancelled") || error.message.includes("canceled");
|
|
1235
|
+
}
|
|
1236
|
+
async function resolveChainId(walletClient) {
|
|
1237
|
+
if (walletClient.chain?.id) return walletClient.chain.id;
|
|
1238
|
+
const result = walletClient.getChainId?.();
|
|
1239
|
+
if (result != null) return await result;
|
|
1240
|
+
return null;
|
|
1241
|
+
}
|
|
1242
|
+
var ChainSwitch = async (walletClient, requiredChainId) => {
|
|
1243
|
+
try {
|
|
1244
|
+
const currentChainId = await resolveChainId(walletClient);
|
|
1245
|
+
if (currentChainId === requiredChainId) return true;
|
|
1246
|
+
if (walletClient.request) {
|
|
1247
|
+
await walletClient.request({
|
|
1248
|
+
method: "wallet_switchEthereumChain",
|
|
1249
|
+
params: [{ chainId: `0x${requiredChainId.toString(16)}` }]
|
|
1250
|
+
});
|
|
1251
|
+
} else if (walletClient.switchChain) {
|
|
1252
|
+
await walletClient.switchChain({ id: requiredChainId });
|
|
1253
|
+
} else if (walletClient.send) {
|
|
1254
|
+
await walletClient.send("wallet_switchEthereumChain", [
|
|
1255
|
+
{ chainId: `0x${requiredChainId.toString(16)}` }
|
|
1256
|
+
]);
|
|
1257
|
+
} else {
|
|
1258
|
+
throw new Error("Wallet doesn't support chain switching");
|
|
1259
|
+
}
|
|
1260
|
+
const newChainId = await resolveChainId(walletClient);
|
|
1261
|
+
return newChainId === requiredChainId;
|
|
1262
|
+
} catch (error) {
|
|
1263
|
+
if (isUserRejection(error)) {
|
|
1264
|
+
throw new Error("User rejected the chain switch request");
|
|
1265
|
+
}
|
|
1266
|
+
throw new Error(
|
|
1267
|
+
`Please switch your wallet to the required network (Chain ID: ${requiredChainId})`
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
var SignSwap = async (params) => {
|
|
1272
|
+
const { quote, signatureStep, userAddress, walletClient } = params;
|
|
1273
|
+
if (!walletClient) throw new Error("Wallet client not available");
|
|
1274
|
+
if (signatureStep.type !== "SIGNATURE" || !signatureStep.signature?.typedData)
|
|
1275
|
+
throw new Error("Invalid signature step");
|
|
1276
|
+
try {
|
|
1277
|
+
const typed = signatureStep.signature.typedData;
|
|
1278
|
+
const normalizedMessage = { ...typed.message };
|
|
1279
|
+
for (const key of ["inputAmount", "outputAmount", "startTime", "endTime"]) {
|
|
1280
|
+
if (normalizedMessage[key] != null) {
|
|
1281
|
+
normalizedMessage[key] = BigInt(normalizedMessage[key]);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
const domain = typed.domain;
|
|
1285
|
+
if (domain.chainId != null) {
|
|
1286
|
+
const chainId = Number(domain.chainId);
|
|
1287
|
+
await ChainSwitch(walletClient, chainId);
|
|
1288
|
+
}
|
|
1289
|
+
const signature = await signTypedData(walletClient, {
|
|
1290
|
+
account: userAddress,
|
|
1291
|
+
domain,
|
|
1292
|
+
types: typed.types,
|
|
1293
|
+
primaryType: typed.primaryType,
|
|
1294
|
+
message: normalizedMessage
|
|
1295
|
+
});
|
|
1296
|
+
const submitRes = await fetch(`${getVtApiUrl()}/submit-signature`, {
|
|
1297
|
+
method: "POST",
|
|
1298
|
+
headers: getVtHeaders(),
|
|
1299
|
+
body: JSON.stringify({
|
|
1300
|
+
quoteId: quote.id,
|
|
1301
|
+
signatures: [signature]
|
|
1302
|
+
})
|
|
1303
|
+
});
|
|
1304
|
+
if (!submitRes.ok) {
|
|
1305
|
+
const errBody = await submitRes.json().catch(() => ({}));
|
|
1306
|
+
throw new Error(errBody?.message || `Submit failed: ${submitRes.status}`);
|
|
1307
|
+
}
|
|
1308
|
+
return { quoteId: quote.id, signature };
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
if (isUserRejection(error)) {
|
|
1311
|
+
throw new Error("User rejected the signing request");
|
|
1312
|
+
}
|
|
1313
|
+
if (error instanceof Error && error.message.includes("chain")) {
|
|
1314
|
+
throw new Error("Chain switching failed. Please manually switch to the correct network.");
|
|
1315
|
+
}
|
|
1316
|
+
throw error;
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
|
|
1320
|
+
// src/lib/submitTracker.ts
|
|
1321
|
+
var STORAGE_KEY = "vt_submitted_quotes";
|
|
1322
|
+
var QUOTE_EXPIRATION_MS = 30 * 1e3;
|
|
1323
|
+
var getSubmittedQuotes = () => {
|
|
1324
|
+
try {
|
|
1325
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
1326
|
+
return stored ? JSON.parse(stored) : [];
|
|
1327
|
+
} catch {
|
|
1328
|
+
return [];
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
var saveSubmittedQuotes = (quotes) => {
|
|
1332
|
+
try {
|
|
1333
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(quotes));
|
|
1334
|
+
} catch {
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
var isQuoteFresh = (createdAt) => {
|
|
1338
|
+
const now = Date.now();
|
|
1339
|
+
const createdMs = typeof createdAt === "string" ? new Date(createdAt).getTime() : createdAt > 9999999999 ? createdAt : createdAt * 1e3;
|
|
1340
|
+
return now < createdMs + QUOTE_EXPIRATION_MS;
|
|
1341
|
+
};
|
|
1342
|
+
var isOrderAlreadySubmitted = (quoteId) => {
|
|
1343
|
+
return getSubmittedQuotes().some((q) => q.quoteId === quoteId);
|
|
1344
|
+
};
|
|
1345
|
+
var canSubmitOrder = (quoteId, createdAt) => {
|
|
1346
|
+
if (isOrderAlreadySubmitted(quoteId)) {
|
|
1347
|
+
return { canSubmit: false, reason: "Order already submitted" };
|
|
1348
|
+
}
|
|
1349
|
+
if (!isQuoteFresh(createdAt)) {
|
|
1350
|
+
return { canSubmit: false, reason: "Quote has expired" };
|
|
1351
|
+
}
|
|
1352
|
+
return { canSubmit: true };
|
|
1353
|
+
};
|
|
1354
|
+
var markOrderAsSubmitted = (quoteId, createdAt) => {
|
|
1355
|
+
const createdMs = typeof createdAt === "string" ? new Date(createdAt).getTime() : createdAt;
|
|
1356
|
+
const submitted = getSubmittedQuotes();
|
|
1357
|
+
submitted.push({ quoteId, submittedAt: Date.now(), createdAt: createdMs });
|
|
1358
|
+
saveSubmittedQuotes(submitted);
|
|
1359
|
+
};
|
|
1360
|
+
var cleanupOldSubmissions = () => {
|
|
1361
|
+
const submitted = getSubmittedQuotes();
|
|
1362
|
+
const now = Date.now();
|
|
1363
|
+
const CLEANUP_THRESHOLD = 5 * 60 * 1e3;
|
|
1364
|
+
const cleaned = submitted.filter((q) => now - q.submittedAt < CLEANUP_THRESHOLD);
|
|
1365
|
+
if (cleaned.length !== submitted.length) saveSubmittedQuotes(cleaned);
|
|
1366
|
+
};
|
|
1367
|
+
|
|
1368
|
+
// src/queries/useUnwrapToken.ts
|
|
1369
|
+
import { useMutation } from "@tanstack/react-query";
|
|
1370
|
+
import { waitForTransactionReceipt } from "@wagmi/core";
|
|
1371
|
+
import { wethAbi } from "abitype/abis";
|
|
1372
|
+
import { useConfig } from "wagmi";
|
|
1373
|
+
function useUnwrapToken() {
|
|
1374
|
+
const wagmiConfig = useConfig();
|
|
1375
|
+
return useMutation({
|
|
1376
|
+
mutationFn: async ({ chainId, accountAddress, amountRaw, writeContractAsync }) => {
|
|
1377
|
+
const chainConfig = getChainConfig(chainId);
|
|
1378
|
+
if (!chainConfig) throw new Error(`No wrapping contract for chain ${chainId}`);
|
|
1379
|
+
const wNativeAddress = chainConfig.wrappedAsset.address;
|
|
1380
|
+
const hash = await writeContractAsync({
|
|
1381
|
+
abi: wethAbi,
|
|
1382
|
+
functionName: "withdraw",
|
|
1383
|
+
address: wNativeAddress,
|
|
1384
|
+
account: accountAddress,
|
|
1385
|
+
args: [amountRaw],
|
|
1386
|
+
chainId
|
|
1387
|
+
});
|
|
1388
|
+
const receipt = await waitForTransactionReceipt(wagmiConfig, {
|
|
1389
|
+
hash,
|
|
1390
|
+
chainId,
|
|
1391
|
+
confirmations: 1,
|
|
1392
|
+
pollingInterval: 4e3
|
|
1393
|
+
});
|
|
1394
|
+
if (receipt.status !== "success") {
|
|
1395
|
+
throw new Error("Unwrap transaction did not get successful receipt");
|
|
1396
|
+
}
|
|
1397
|
+
return { success: true, hash };
|
|
1398
|
+
}
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// src/queries/useWrapToken.ts
|
|
1403
|
+
import { useMutation as useMutation2 } from "@tanstack/react-query";
|
|
1404
|
+
import { waitForTransactionReceipt as waitForTransactionReceipt2 } from "@wagmi/core";
|
|
1405
|
+
import { wethAbi as wethAbi2 } from "abitype/abis";
|
|
1406
|
+
import { useConfig as useConfig2 } from "wagmi";
|
|
1407
|
+
function useWrapToken() {
|
|
1408
|
+
const wagmiConfig = useConfig2();
|
|
1409
|
+
return useMutation2({
|
|
1410
|
+
mutationFn: async ({ chainId, accountAddress, amountRaw, writeContractAsync }) => {
|
|
1411
|
+
const chainConfig = getChainConfig(chainId);
|
|
1412
|
+
if (!chainConfig) throw new Error(`No wrapping contract for chain ${chainId}`);
|
|
1413
|
+
const wNativeAddress = chainConfig.wrappedAsset.address;
|
|
1414
|
+
const hash = await writeContractAsync({
|
|
1415
|
+
abi: wethAbi2,
|
|
1416
|
+
functionName: "deposit",
|
|
1417
|
+
address: wNativeAddress,
|
|
1418
|
+
account: accountAddress,
|
|
1419
|
+
value: amountRaw,
|
|
1420
|
+
chainId
|
|
1421
|
+
});
|
|
1422
|
+
const receipt = await waitForTransactionReceipt2(wagmiConfig, {
|
|
1423
|
+
hash,
|
|
1424
|
+
chainId,
|
|
1425
|
+
confirmations: 1,
|
|
1426
|
+
pollingInterval: 4e3
|
|
1427
|
+
});
|
|
1428
|
+
if (receipt.status !== "success") {
|
|
1429
|
+
throw new Error("Wrap transaction did not get successful receipt");
|
|
1430
|
+
}
|
|
1431
|
+
return { success: true, hash };
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// src/components/states/SwapButton.tsx
|
|
1437
|
+
import { useCallback as useCallback10, useEffect as useEffect12, useRef as useRef11, useState as useState9 } from "react";
|
|
1438
|
+
import { useChainId as useChainId2 } from "wagmi";
|
|
1439
|
+
|
|
1440
|
+
// src/wallet/useTransactionTracker.ts
|
|
1441
|
+
import { useCallback as useCallback5, useRef as useRef5 } from "react";
|
|
1442
|
+
function useTransactionTracker() {
|
|
1443
|
+
const { registerTransaction, enabled } = useTransactionRegistry();
|
|
1444
|
+
const registeredRef = useRef5(/* @__PURE__ */ new Set());
|
|
1445
|
+
const trackOrderTransaction = useCallback5(
|
|
1446
|
+
(quoteId, description) => {
|
|
1447
|
+
if (!enabled || registeredRef.current.has(quoteId)) return;
|
|
1448
|
+
registeredRef.current.add(quoteId);
|
|
1449
|
+
registerTransaction(quoteId, description);
|
|
1450
|
+
},
|
|
1451
|
+
[enabled, registerTransaction]
|
|
1452
|
+
);
|
|
1453
|
+
const trackNativeTransaction = useCallback5(
|
|
1454
|
+
(txHash, description) => {
|
|
1455
|
+
if (!enabled || registeredRef.current.has(txHash)) return;
|
|
1456
|
+
registeredRef.current.add(txHash);
|
|
1457
|
+
registerTransaction(txHash, description);
|
|
1458
|
+
},
|
|
1459
|
+
[enabled, registerTransaction]
|
|
1460
|
+
);
|
|
1461
|
+
return { trackOrderTransaction, trackNativeTransaction, txTrackingEnabled: enabled };
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
// src/components/states/ReviewActionChainSwitchRow.tsx
|
|
1465
|
+
import { useEffect as useEffect6, useState as useState4, useCallback as useCallback6, useRef as useRef6 } from "react";
|
|
1466
|
+
import { useAccount, useChainId, useSwitchChain } from "wagmi";
|
|
1467
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1468
|
+
var WALLET_TIMEOUT_MS = 15e3;
|
|
1469
|
+
var ComponentStep = "chain";
|
|
1470
|
+
var ReviewActionChainSwitchRow = ({
|
|
1471
|
+
needsChainSwitch,
|
|
1472
|
+
requiredChain,
|
|
1473
|
+
reviewState,
|
|
1474
|
+
goToNextStep,
|
|
1475
|
+
cancel
|
|
1476
|
+
}) => {
|
|
1477
|
+
const hasSucceeded = isReviewStepPast(ComponentStep, reviewState);
|
|
1478
|
+
const [chainState, setChainState] = useState4("pending");
|
|
1479
|
+
const isSwitching = useRef6(false);
|
|
1480
|
+
const switchAttempted = useRef6(false);
|
|
1481
|
+
const timeoutRef = useRef6(null);
|
|
1482
|
+
const currentChainId = useChainId();
|
|
1483
|
+
const { switchChainAsync } = useSwitchChain();
|
|
1484
|
+
const { status: wagmiStatus, isReconnecting } = useAccount();
|
|
1485
|
+
const clearPendingTimeout = useCallback6(() => {
|
|
1486
|
+
if (timeoutRef.current) {
|
|
1487
|
+
clearTimeout(timeoutRef.current);
|
|
1488
|
+
timeoutRef.current = null;
|
|
1489
|
+
}
|
|
1490
|
+
}, []);
|
|
1491
|
+
const handleChainSwitch = useCallback6(async () => {
|
|
1492
|
+
if (isSwitching.current || switchAttempted.current || wagmiStatus !== "connected" || isReconnecting) return;
|
|
1493
|
+
isSwitching.current = true;
|
|
1494
|
+
switchAttempted.current = true;
|
|
1495
|
+
setChainState("pending");
|
|
1496
|
+
timeoutRef.current = setTimeout(() => {
|
|
1497
|
+
if (isSwitching.current) {
|
|
1498
|
+
setChainState("error");
|
|
1499
|
+
isSwitching.current = false;
|
|
1500
|
+
}
|
|
1501
|
+
}, WALLET_TIMEOUT_MS);
|
|
1502
|
+
try {
|
|
1503
|
+
await switchChainAsync({ chainId: requiredChain.chainId });
|
|
1504
|
+
clearPendingTimeout();
|
|
1505
|
+
setChainState("success");
|
|
1506
|
+
goToNextStep(ComponentStep, reviewState);
|
|
1507
|
+
} catch {
|
|
1508
|
+
clearPendingTimeout();
|
|
1509
|
+
setChainState("error");
|
|
1510
|
+
} finally {
|
|
1511
|
+
isSwitching.current = false;
|
|
1512
|
+
}
|
|
1513
|
+
}, [switchChainAsync, wagmiStatus, isReconnecting, requiredChain.chainId, goToNextStep, reviewState, clearPendingTimeout]);
|
|
1514
|
+
useEffect6(() => {
|
|
1515
|
+
if (isSwitching.current || switchAttempted.current) return;
|
|
1516
|
+
if (reviewState === ComponentStep && chainState === "pending" && wagmiStatus === "connected" && !isReconnecting) {
|
|
1517
|
+
if (needsChainSwitch && currentChainId !== requiredChain.chainId) handleChainSwitch();
|
|
1518
|
+
else {
|
|
1519
|
+
setChainState("success");
|
|
1520
|
+
goToNextStep(ComponentStep, reviewState);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}, [reviewState, chainState, needsChainSwitch, currentChainId, requiredChain.chainId, wagmiStatus, isReconnecting, handleChainSwitch, goToNextStep]);
|
|
1524
|
+
useEffect6(() => () => clearPendingTimeout(), [clearPendingTimeout]);
|
|
1525
|
+
const handleRetry = () => {
|
|
1526
|
+
if (chainState === "error") {
|
|
1527
|
+
switchAttempted.current = false;
|
|
1528
|
+
setChainState("pending");
|
|
1529
|
+
}
|
|
1530
|
+
};
|
|
1531
|
+
const isError = !hasSucceeded && chainState === "error";
|
|
1532
|
+
const showCancel = !hasSucceeded && (chainState === "pending" || chainState === "error");
|
|
1533
|
+
const statusVar = isError ? "var(--widget-status-failed)" : "var(--widget-status-pending)";
|
|
1534
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex w-full flex-row justify-between space-x-3", children: [
|
|
1535
|
+
/* @__PURE__ */ jsx5(
|
|
1536
|
+
"button",
|
|
1537
|
+
{
|
|
1538
|
+
type: "button",
|
|
1539
|
+
className: `inline-flex h-12 w-full items-center justify-center text-sm font-medium transition duration-150 ${isError ? "cursor-pointer" : "cursor-not-allowed"}`,
|
|
1540
|
+
style: {
|
|
1541
|
+
backgroundColor: `color-mix(in srgb, ${statusVar} 12%, transparent)`,
|
|
1542
|
+
border: `1px solid color-mix(in srgb, ${statusVar} 20%, transparent)`,
|
|
1543
|
+
color: statusVar,
|
|
1544
|
+
borderRadius: "var(--widget-radius)"
|
|
1545
|
+
},
|
|
1546
|
+
onClick: isError ? handleRetry : void 0,
|
|
1547
|
+
children: /* @__PURE__ */ jsxs4("div", { className: "flex h-10 w-full flex-row items-center space-x-3 px-4", children: [
|
|
1548
|
+
/* @__PURE__ */ jsx5(ChainIcon_default, { chain: requiredChain.chainId, size: "xs" }),
|
|
1549
|
+
/* @__PURE__ */ jsx5("p", { className: "whitespace-nowrap", children: isError ? "Retry Switch" : `Switch to ${requiredChain.name}` }),
|
|
1550
|
+
/* @__PURE__ */ jsx5("div", { className: "flex flex-1 border-t", style: { borderColor: "var(--widget-border)" } }),
|
|
1551
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex h-6 w-3 items-center justify-center rounded-full", children: [
|
|
1552
|
+
!hasSucceeded && chainState === "pending" && /* @__PURE__ */ jsx5(LoadingSpinner_default, { size: "5" }),
|
|
1553
|
+
(hasSucceeded || chainState === "success") && /* @__PURE__ */ jsx5(Checkmark, { className: "h-8 w-8" }),
|
|
1554
|
+
isError && /* @__PURE__ */ jsx5(RedoIcon, { className: "h-[12px] w-[12px]" })
|
|
1555
|
+
] })
|
|
1556
|
+
] })
|
|
1557
|
+
}
|
|
1558
|
+
),
|
|
1559
|
+
showCancel && /* @__PURE__ */ jsx5(
|
|
1560
|
+
"button",
|
|
1561
|
+
{
|
|
1562
|
+
type: "button",
|
|
1563
|
+
className: "inline-flex h-12 items-center justify-center px-8 text-sm font-medium transition duration-150 cursor-pointer",
|
|
1564
|
+
style: {
|
|
1565
|
+
backgroundColor: "color-mix(in srgb, var(--widget-status-failed) 12%, transparent)",
|
|
1566
|
+
border: "1px solid color-mix(in srgb, var(--widget-status-failed) 20%, transparent)",
|
|
1567
|
+
color: "var(--widget-status-failed)",
|
|
1568
|
+
borderRadius: "var(--widget-radius)"
|
|
1569
|
+
},
|
|
1570
|
+
onClick: cancel,
|
|
1571
|
+
children: "Cancel"
|
|
1572
|
+
}
|
|
1573
|
+
)
|
|
1574
|
+
] });
|
|
1575
|
+
};
|
|
1576
|
+
var ReviewActionChainSwitchRow_default = ReviewActionChainSwitchRow;
|
|
1577
|
+
|
|
1578
|
+
// src/components/states/ReviewActionSignAndSubmitOrderRow.tsx
|
|
1579
|
+
import { waitForTransactionReceipt as waitForTransactionReceipt3 } from "@wagmi/core";
|
|
1580
|
+
import { useCallback as useCallback7, useEffect as useEffect8, useState as useState6, useRef as useRef8 } from "react";
|
|
1581
|
+
import { useAccount as useAccount2, useConfig as useConfig3, useSendTransaction, useSwitchChain as useSwitchChain2, useWalletClient } from "wagmi";
|
|
1582
|
+
|
|
1583
|
+
// src/components/CountDown.tsx
|
|
1584
|
+
import { useEffect as useEffect7, useRef as useRef7, useState as useState5 } from "react";
|
|
1585
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
1586
|
+
var CountDown = ({ startTime, onExpired }) => {
|
|
1587
|
+
const [timeLeft, setTimeLeft] = useState5(30);
|
|
1588
|
+
const [isExpired, setIsExpired] = useState5(false);
|
|
1589
|
+
const firedRef = useRef7(false);
|
|
1590
|
+
const onExpiredRef = useRef7(onExpired);
|
|
1591
|
+
onExpiredRef.current = onExpired;
|
|
1592
|
+
useEffect7(() => {
|
|
1593
|
+
firedRef.current = false;
|
|
1594
|
+
}, [startTime]);
|
|
1595
|
+
useEffect7(() => {
|
|
1596
|
+
const calculateTimeLeft = () => {
|
|
1597
|
+
const now = Date.now();
|
|
1598
|
+
const startTimeMs = startTime > 9999999999 ? startTime : startTime * 1e3;
|
|
1599
|
+
const expirationTime = startTimeMs + 30 * 1e3;
|
|
1600
|
+
const remaining = Math.max(0, expirationTime - now);
|
|
1601
|
+
const remainingSeconds = Math.floor(remaining / 1e3);
|
|
1602
|
+
if (remainingSeconds <= 0) {
|
|
1603
|
+
setTimeLeft(0);
|
|
1604
|
+
if (!firedRef.current) {
|
|
1605
|
+
firedRef.current = true;
|
|
1606
|
+
setIsExpired(true);
|
|
1607
|
+
onExpiredRef.current();
|
|
1608
|
+
}
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
setTimeLeft(remainingSeconds);
|
|
1612
|
+
setIsExpired(false);
|
|
1613
|
+
};
|
|
1614
|
+
if (isExpired) return;
|
|
1615
|
+
calculateTimeLeft();
|
|
1616
|
+
const interval = setInterval(calculateTimeLeft, 1e3);
|
|
1617
|
+
return () => clearInterval(interval);
|
|
1618
|
+
}, [startTime, isExpired]);
|
|
1619
|
+
const formatTime = (seconds) => {
|
|
1620
|
+
const mins = Math.floor(seconds / 60);
|
|
1621
|
+
const secs = seconds % 60;
|
|
1622
|
+
return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
|
|
1623
|
+
};
|
|
1624
|
+
if (isExpired) return null;
|
|
1625
|
+
return /* @__PURE__ */ jsx6("span", { className: "text-xs font-mono", children: formatTime(timeLeft) });
|
|
1626
|
+
};
|
|
1627
|
+
var CountDown_default = CountDown;
|
|
1628
|
+
|
|
1629
|
+
// src/components/states/ReviewActionSignAndSubmitOrderRow.tsx
|
|
1630
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1631
|
+
function isUserRejectionError(error) {
|
|
1632
|
+
return error.name === "UserRejectedRequestError" || error.message.includes("User rejected") || error.message.includes("rejected") || error.message.includes("denied") || error.message.includes("cancelled") || error.message.includes("canceled");
|
|
1633
|
+
}
|
|
1634
|
+
var ComponentStep2 = "signingOrder";
|
|
1635
|
+
async function executeTransactionStep(step, userAddress, sendTransactionAsync, wagmiConfig, fallbackChainKey) {
|
|
1636
|
+
const encoded = step.transaction?.encoded;
|
|
1637
|
+
const to = encoded?.to ?? step.to;
|
|
1638
|
+
const data = encoded?.data ?? step.data;
|
|
1639
|
+
const value = encoded?.value ?? step.value;
|
|
1640
|
+
if (!to) throw new Error('Transaction step missing "to" address');
|
|
1641
|
+
const encodedChainId = encoded?.chainId;
|
|
1642
|
+
const resolvedChainKey = step.chainKey || fallbackChainKey;
|
|
1643
|
+
const chainId = encodedChainId || (resolvedChainKey ? getChainIdForKey(resolvedChainKey) : void 0);
|
|
1644
|
+
if (!chainId) throw new Error(`Unknown chain: ${resolvedChainKey}`);
|
|
1645
|
+
const viemChain = getViemChainById()[chainId];
|
|
1646
|
+
const hash = await sendTransactionAsync({
|
|
1647
|
+
account: userAddress,
|
|
1648
|
+
chain: viemChain,
|
|
1649
|
+
to,
|
|
1650
|
+
data: data || "0x",
|
|
1651
|
+
value: BigInt(value || "0")
|
|
1652
|
+
});
|
|
1653
|
+
await waitForTransactionReceipt3(wagmiConfig, { hash, chainId });
|
|
1654
|
+
return hash;
|
|
1655
|
+
}
|
|
1656
|
+
var ReviewActionSignAndSubmitOrderRow = ({
|
|
1657
|
+
base,
|
|
1658
|
+
baseAmount,
|
|
1659
|
+
quote,
|
|
1660
|
+
quoteAmount,
|
|
1661
|
+
userAddress,
|
|
1662
|
+
reviewState,
|
|
1663
|
+
goToNextStep,
|
|
1664
|
+
cancel,
|
|
1665
|
+
isWrappingPair,
|
|
1666
|
+
isUnwrappingPair,
|
|
1667
|
+
restartSigningFlow,
|
|
1668
|
+
onSwapInitiated,
|
|
1669
|
+
onOrderSubmitted,
|
|
1670
|
+
onSwapComplete,
|
|
1671
|
+
startPolling,
|
|
1672
|
+
trackNativeTransaction
|
|
1673
|
+
}) => {
|
|
1674
|
+
const hasSucceeded = isReviewStepPast(ComponentStep2, reviewState);
|
|
1675
|
+
const [signState, setSignState] = useState6("idle");
|
|
1676
|
+
const isProcessing = useRef8(false);
|
|
1677
|
+
const hasAutoStartedRef = useRef8(null);
|
|
1678
|
+
const wagmiConfig = useConfig3();
|
|
1679
|
+
const { isReconnecting } = useAccount2();
|
|
1680
|
+
const { rfqQuote, stop: stopRfq, refresh } = useRfq();
|
|
1681
|
+
const { data: walletClient } = useWalletClient({ chainId: base.chainId });
|
|
1682
|
+
const { switchChain } = useSwitchChain2();
|
|
1683
|
+
const { sendTransactionAsync } = useSendTransaction();
|
|
1684
|
+
const quoteReceivedAtMs = rfqQuote?._receivedAt ?? 0;
|
|
1685
|
+
const isQuoteStale = useCallback7(() => {
|
|
1686
|
+
if (!quoteReceivedAtMs) return false;
|
|
1687
|
+
return Date.now() >= quoteReceivedAtMs + 30 * 1e3;
|
|
1688
|
+
}, [quoteReceivedAtMs]);
|
|
1689
|
+
const handleSignAndSubmit = useCallback7(async () => {
|
|
1690
|
+
if (isProcessing.current || !["idle", "error"].includes(signState)) return;
|
|
1691
|
+
if (!rfqQuote?.id || !rfqQuote?._receivedAt) {
|
|
1692
|
+
setSignState("error");
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
const validation = canSubmitOrder(rfqQuote.id, rfqQuote._receivedAt);
|
|
1696
|
+
if (!validation.canSubmit) {
|
|
1697
|
+
setSignState("stale");
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
isProcessing.current = true;
|
|
1701
|
+
try {
|
|
1702
|
+
if (!walletClient) throw new Error("Wallet client not available");
|
|
1703
|
+
cleanupOldSubmissions();
|
|
1704
|
+
const steps = rfqQuote.userSteps ?? [];
|
|
1705
|
+
let lastTxHash;
|
|
1706
|
+
for (const step of steps) {
|
|
1707
|
+
if (step.type === "TRANSACTION") {
|
|
1708
|
+
setSignState("submitting");
|
|
1709
|
+
const resolvedKey = step.chainKey || rfqQuote.srcChainKey;
|
|
1710
|
+
const chainId = resolvedKey ? getChainIdForKey(resolvedKey) : void 0;
|
|
1711
|
+
if (chainId && walletClient.chain?.id !== chainId) {
|
|
1712
|
+
await switchChain({ chainId });
|
|
1713
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
1714
|
+
}
|
|
1715
|
+
lastTxHash = await executeTransactionStep(step, userAddress, sendTransactionAsync, wagmiConfig, rfqQuote.srcChainKey);
|
|
1716
|
+
trackNativeTransaction(lastTxHash, step.description || `Tx on ${resolvedKey}`);
|
|
1717
|
+
} else if (step.type === "SIGNATURE") {
|
|
1718
|
+
setSignState("signing");
|
|
1719
|
+
await SignSwap({
|
|
1720
|
+
quote: rfqQuote,
|
|
1721
|
+
signatureStep: step,
|
|
1722
|
+
userAddress,
|
|
1723
|
+
walletClient
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
markOrderAsSubmitted(rfqQuote.id, rfqQuote._receivedAt);
|
|
1728
|
+
onOrderSubmitted?.(rfqQuote.id);
|
|
1729
|
+
startPolling(rfqQuote.id, { baseToken: base, quoteToken: quote });
|
|
1730
|
+
onSwapInitiated?.();
|
|
1731
|
+
stopRfq();
|
|
1732
|
+
setSignState("success");
|
|
1733
|
+
goToNextStep(ComponentStep2, reviewState);
|
|
1734
|
+
onSwapComplete?.(rfqQuote.id);
|
|
1735
|
+
} catch (error) {
|
|
1736
|
+
if (error instanceof Error && isUserRejectionError(error)) {
|
|
1737
|
+
hasAutoStartedRef.current = null;
|
|
1738
|
+
setSignState("idle");
|
|
1739
|
+
} else {
|
|
1740
|
+
setSignState("error");
|
|
1741
|
+
}
|
|
1742
|
+
} finally {
|
|
1743
|
+
isProcessing.current = false;
|
|
1744
|
+
}
|
|
1745
|
+
}, [signState, rfqQuote, walletClient, base, quote, userAddress, stopRfq, startPolling, goToNextStep, reviewState, onSwapComplete, switchChain, sendTransactionAsync, onSwapInitiated, onOrderSubmitted, trackNativeTransaction, wagmiConfig]);
|
|
1746
|
+
useEffect8(() => {
|
|
1747
|
+
if (reviewState === ComponentStep2) stopRfq();
|
|
1748
|
+
if (hasSucceeded && signState !== "success") {
|
|
1749
|
+
setSignState("success");
|
|
1750
|
+
goToNextStep(ComponentStep2, reviewState);
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
if (signState === "success") {
|
|
1754
|
+
goToNextStep(ComponentStep2, reviewState);
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
if (reviewState === ComponentStep2 && signState === "idle" && !rfqQuote?.id) {
|
|
1758
|
+
setSignState("refreshingQuote");
|
|
1759
|
+
refresh();
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
if (reviewState === ComponentStep2 && signState === "idle" && walletClient && !isProcessing.current && !isReconnecting) {
|
|
1763
|
+
const currentId = rfqQuote?.id || null;
|
|
1764
|
+
if (currentId && hasAutoStartedRef.current !== currentId) {
|
|
1765
|
+
hasAutoStartedRef.current = currentId;
|
|
1766
|
+
handleSignAndSubmit();
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
}, [hasSucceeded, reviewState, signState, walletClient, isReconnecting, handleSignAndSubmit, goToNextStep, rfqQuote?.id, refresh, stopRfq]);
|
|
1770
|
+
useEffect8(() => {
|
|
1771
|
+
if (signState === "refreshingQuote" && rfqQuote?.id) {
|
|
1772
|
+
setSignState("idle");
|
|
1773
|
+
}
|
|
1774
|
+
}, [signState, rfqQuote?.id]);
|
|
1775
|
+
useEffect8(() => {
|
|
1776
|
+
if (reviewState !== ComponentStep2) hasAutoStartedRef.current = null;
|
|
1777
|
+
}, [reviewState]);
|
|
1778
|
+
useEffect8(() => {
|
|
1779
|
+
const interval = setInterval(() => {
|
|
1780
|
+
if (isQuoteStale() && !isProcessing.current && signState !== "submitting" && signState !== "success") {
|
|
1781
|
+
setSignState("stale");
|
|
1782
|
+
}
|
|
1783
|
+
}, 1e3);
|
|
1784
|
+
return () => clearInterval(interval);
|
|
1785
|
+
}, [isQuoteStale, signState]);
|
|
1786
|
+
const handleRetry = () => {
|
|
1787
|
+
if (signState === "error" && !isProcessing.current) {
|
|
1788
|
+
hasAutoStartedRef.current = null;
|
|
1789
|
+
setSignState("idle");
|
|
1790
|
+
}
|
|
1791
|
+
};
|
|
1792
|
+
const handleRefreshFromStale = useCallback7(() => {
|
|
1793
|
+
hasAutoStartedRef.current = null;
|
|
1794
|
+
setSignState("refreshingQuote");
|
|
1795
|
+
refresh();
|
|
1796
|
+
}, [refresh]);
|
|
1797
|
+
const handleCloseWalletAndRestart = useCallback7(() => {
|
|
1798
|
+
if (restartSigningFlow) {
|
|
1799
|
+
restartSigningFlow(true);
|
|
1800
|
+
} else {
|
|
1801
|
+
cancel();
|
|
1802
|
+
}
|
|
1803
|
+
}, [restartSigningFlow, cancel]);
|
|
1804
|
+
const buttonText = () => {
|
|
1805
|
+
if (signState === "error") return "Retry swap";
|
|
1806
|
+
if (signState === "signing") return "Sign Swap Order";
|
|
1807
|
+
if (signState === "submitting") return "Submitting...";
|
|
1808
|
+
if (signState === "refreshingQuote") return "Refreshing Quote...";
|
|
1809
|
+
if (signState === "stale") return "Quote Stale";
|
|
1810
|
+
return "Sign & Submit";
|
|
1811
|
+
};
|
|
1812
|
+
const isErrorOrStale = !hasSucceeded && (signState === "error" || signState === "stale");
|
|
1813
|
+
const statusVar = isErrorOrStale ? "var(--widget-status-failed)" : "var(--widget-status-pending)";
|
|
1814
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex w-full flex-row justify-between space-x-3", children: [
|
|
1815
|
+
/* @__PURE__ */ jsx7(
|
|
1816
|
+
"button",
|
|
1817
|
+
{
|
|
1818
|
+
type: "button",
|
|
1819
|
+
className: `inline-flex h-12 w-full items-center justify-center text-sm font-medium transition duration-150 ${isErrorOrStale ? "cursor-pointer" : "cursor-not-allowed"}`,
|
|
1820
|
+
style: {
|
|
1821
|
+
backgroundColor: `color-mix(in srgb, ${statusVar} 12%, transparent)`,
|
|
1822
|
+
border: `1px solid color-mix(in srgb, ${statusVar} 20%, transparent)`,
|
|
1823
|
+
color: statusVar,
|
|
1824
|
+
borderRadius: "var(--widget-radius)"
|
|
1825
|
+
},
|
|
1826
|
+
onClick: !hasSucceeded && signState === "error" ? handleRetry : signState === "stale" ? handleCloseWalletAndRestart : void 0,
|
|
1827
|
+
children: /* @__PURE__ */ jsxs5("div", { className: "flex h-10 w-full flex-row items-center space-x-3 px-4", children: [
|
|
1828
|
+
/* @__PURE__ */ jsx7(TokenImage_default, { asset: base, size: "xs", noChain: true }),
|
|
1829
|
+
/* @__PURE__ */ jsx7("p", { className: "whitespace-nowrap", children: buttonText() }),
|
|
1830
|
+
/* @__PURE__ */ jsx7("div", { className: "flex flex-1 border-t", style: { borderColor: "var(--widget-border)" } }),
|
|
1831
|
+
quoteReceivedAtMs > 0 && signState !== "success" && signState !== "error" && signState !== "stale" && /* @__PURE__ */ jsx7(CountDown_default, { startTime: quoteReceivedAtMs, onExpired: () => setSignState("stale") }),
|
|
1832
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex h-6 w-3 items-center justify-center rounded-full", children: [
|
|
1833
|
+
!hasSucceeded && (signState === "idle" || signState === "signing" || signState === "submitting" || signState === "refreshingQuote") && /* @__PURE__ */ jsx7(LoadingSpinner_default, { size: "5" }),
|
|
1834
|
+
(hasSucceeded || signState === "success") && /* @__PURE__ */ jsx7(Checkmark, { className: "h-8 w-8" }),
|
|
1835
|
+
!hasSucceeded && (signState === "error" || signState === "stale") && /* @__PURE__ */ jsx7(RedoIcon, { className: "h-[12px] w-[12px]" })
|
|
1836
|
+
] })
|
|
1837
|
+
] })
|
|
1838
|
+
}
|
|
1839
|
+
),
|
|
1840
|
+
!hasSucceeded && signState === "error" && /* @__PURE__ */ jsx7(
|
|
1841
|
+
"button",
|
|
1842
|
+
{
|
|
1843
|
+
type: "button",
|
|
1844
|
+
className: "inline-flex h-12 items-center justify-center px-8 text-sm font-medium transition duration-150 cursor-pointer",
|
|
1845
|
+
style: {
|
|
1846
|
+
backgroundColor: "color-mix(in srgb, var(--widget-status-failed) 12%, transparent)",
|
|
1847
|
+
border: "1px solid color-mix(in srgb, var(--widget-status-failed) 20%, transparent)",
|
|
1848
|
+
color: "var(--widget-status-failed)",
|
|
1849
|
+
borderRadius: "var(--widget-radius)"
|
|
1850
|
+
},
|
|
1851
|
+
onClick: cancel,
|
|
1852
|
+
children: "Cancel"
|
|
1853
|
+
}
|
|
1854
|
+
)
|
|
1855
|
+
] });
|
|
1856
|
+
};
|
|
1857
|
+
var ReviewActionSignAndSubmitOrderRow_default = ReviewActionSignAndSubmitOrderRow;
|
|
1858
|
+
|
|
1859
|
+
// src/components/states/ReviewActionTxRow.tsx
|
|
1860
|
+
import { useEffect as useEffect9 } from "react";
|
|
1861
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1862
|
+
var ComponentStep3 = "trackingTx";
|
|
1863
|
+
var statusConfig = {
|
|
1864
|
+
pending: {
|
|
1865
|
+
icon: /* @__PURE__ */ jsx8(LoadingSpinner_default, { size: "5" }),
|
|
1866
|
+
buttonText: "Trade Pending",
|
|
1867
|
+
showNewSwap: false,
|
|
1868
|
+
statusVar: "var(--widget-status-pending)",
|
|
1869
|
+
clickable: false
|
|
1870
|
+
},
|
|
1871
|
+
received: {
|
|
1872
|
+
icon: /* @__PURE__ */ jsx8(LoadingSpinner_default, { size: "5" }),
|
|
1873
|
+
buttonText: "Trade Received",
|
|
1874
|
+
showNewSwap: false,
|
|
1875
|
+
statusVar: "var(--widget-status-received)",
|
|
1876
|
+
clickable: false
|
|
1877
|
+
},
|
|
1878
|
+
completed: {
|
|
1879
|
+
icon: /* @__PURE__ */ jsx8(Checkmark, {}),
|
|
1880
|
+
buttonText: "Continue Swapping",
|
|
1881
|
+
showNewSwap: true,
|
|
1882
|
+
statusVar: "var(--widget-status-completed)",
|
|
1883
|
+
clickable: true
|
|
1884
|
+
},
|
|
1885
|
+
failed: {
|
|
1886
|
+
icon: /* @__PURE__ */ jsx8(XAnimation, {}),
|
|
1887
|
+
buttonText: "Retry Swap",
|
|
1888
|
+
showNewSwap: true,
|
|
1889
|
+
statusVar: "var(--widget-status-failed)",
|
|
1890
|
+
clickable: true
|
|
1891
|
+
},
|
|
1892
|
+
cancelled: {
|
|
1893
|
+
icon: /* @__PURE__ */ jsx8(XAnimation, {}),
|
|
1894
|
+
buttonText: "Continue Swapping",
|
|
1895
|
+
showNewSwap: true,
|
|
1896
|
+
statusVar: "var(--widget-status-failed)",
|
|
1897
|
+
clickable: true
|
|
1898
|
+
}
|
|
1899
|
+
};
|
|
1900
|
+
var ReviewActionTxRow = ({
|
|
1901
|
+
orderHash,
|
|
1902
|
+
base,
|
|
1903
|
+
quote,
|
|
1904
|
+
baseAmount,
|
|
1905
|
+
quoteAmount,
|
|
1906
|
+
reviewState,
|
|
1907
|
+
onNewSwap,
|
|
1908
|
+
onRetry,
|
|
1909
|
+
status
|
|
1910
|
+
}) => {
|
|
1911
|
+
const hasSucceeded = isReviewStepPast(ComponentStep3, reviewState);
|
|
1912
|
+
const config = statusConfig[status] || statusConfig.pending;
|
|
1913
|
+
useEffect9(() => {
|
|
1914
|
+
if (status === "completed") {
|
|
1915
|
+
const timer = setTimeout(onNewSwap, 5e3);
|
|
1916
|
+
return () => clearTimeout(timer);
|
|
1917
|
+
}
|
|
1918
|
+
}, [status, onNewSwap]);
|
|
1919
|
+
const handleClick = () => {
|
|
1920
|
+
if (status === "failed") onRetry();
|
|
1921
|
+
else if (config.showNewSwap) onNewSwap();
|
|
1922
|
+
};
|
|
1923
|
+
const sv = config.statusVar;
|
|
1924
|
+
return /* @__PURE__ */ jsx8("div", { className: "flex flex-row justify-between space-x-3 w-full", children: /* @__PURE__ */ jsx8(
|
|
1925
|
+
"button",
|
|
1926
|
+
{
|
|
1927
|
+
type: "button",
|
|
1928
|
+
className: `inline-flex h-12 w-full items-center justify-center text-sm font-medium transition duration-150 ${config.clickable ? "cursor-pointer" : "cursor-not-allowed"}`,
|
|
1929
|
+
style: {
|
|
1930
|
+
backgroundColor: `color-mix(in srgb, ${sv} 12%, transparent)`,
|
|
1931
|
+
border: `1px solid color-mix(in srgb, ${sv} 20%, transparent)`,
|
|
1932
|
+
color: sv,
|
|
1933
|
+
borderRadius: "var(--widget-radius)"
|
|
1934
|
+
},
|
|
1935
|
+
onClick: config.showNewSwap ? handleClick : void 0,
|
|
1936
|
+
children: /* @__PURE__ */ jsxs6("div", { className: "flex h-10 w-full flex-row items-center space-x-3 px-4", children: [
|
|
1937
|
+
/* @__PURE__ */ jsx8("div", { className: "flex h-6 w-6 items-center justify-center rounded-full", children: config.icon }),
|
|
1938
|
+
/* @__PURE__ */ jsx8("p", { className: "whitespace-nowrap", children: config.buttonText }),
|
|
1939
|
+
/* @__PURE__ */ jsx8("div", { className: "flex flex-1 border-t", style: { borderColor: "var(--widget-border)" } }),
|
|
1940
|
+
config.showNewSwap && /* @__PURE__ */ jsx8("div", { className: "flex h-6 w-6 items-center justify-center", children: /* @__PURE__ */ jsx8(RedoAnimation, {}) })
|
|
1941
|
+
] })
|
|
1942
|
+
}
|
|
1943
|
+
) });
|
|
1944
|
+
};
|
|
1945
|
+
var ReviewActionTxRow_default = ReviewActionTxRow;
|
|
1946
|
+
|
|
1947
|
+
// src/components/states/ReviewActionUnwrapGasToken.tsx
|
|
1948
|
+
import { useCallback as useCallback8, useEffect as useEffect10, useState as useState7, useRef as useRef9 } from "react";
|
|
1949
|
+
import { parseUnits as parseUnits2 } from "viem";
|
|
1950
|
+
import { useAccount as useAccount3, useWriteContract } from "wagmi";
|
|
1951
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1952
|
+
var WALLET_TIMEOUT_MS2 = 15e3;
|
|
1953
|
+
var ComponentStep4 = "unwrapping";
|
|
1954
|
+
var ReviewActionUnwrapGasToken = ({
|
|
1955
|
+
needsToUnwrap,
|
|
1956
|
+
amount,
|
|
1957
|
+
chainId,
|
|
1958
|
+
token,
|
|
1959
|
+
reviewState,
|
|
1960
|
+
setReviewState,
|
|
1961
|
+
goToNextStep,
|
|
1962
|
+
cancel,
|
|
1963
|
+
delayedClose
|
|
1964
|
+
}) => {
|
|
1965
|
+
const hasSucceeded = isReviewStepPast(ComponentStep4, reviewState);
|
|
1966
|
+
const [unwrappingState, setUnwrappingState] = useState7("idle");
|
|
1967
|
+
const unwrapMutation = useUnwrapToken();
|
|
1968
|
+
const { writeContractAsync } = useWriteContract();
|
|
1969
|
+
const { address } = useWalletState();
|
|
1970
|
+
const emitBalanceEvent = useEmitBalanceEvent();
|
|
1971
|
+
const isUnwrapping = useRef9(false);
|
|
1972
|
+
const unwrapAttempted = useRef9(false);
|
|
1973
|
+
const timeoutRef = useRef9(null);
|
|
1974
|
+
const { status: wagmiStatus } = useAccount3();
|
|
1975
|
+
const clearPendingTimeout = useCallback8(() => {
|
|
1976
|
+
if (timeoutRef.current) {
|
|
1977
|
+
clearTimeout(timeoutRef.current);
|
|
1978
|
+
timeoutRef.current = null;
|
|
1979
|
+
}
|
|
1980
|
+
}, []);
|
|
1981
|
+
const handleUnwrap = useCallback8(async () => {
|
|
1982
|
+
if (isUnwrapping.current || unwrapAttempted.current || !address || wagmiStatus !== "connected") return;
|
|
1983
|
+
isUnwrapping.current = true;
|
|
1984
|
+
unwrapAttempted.current = true;
|
|
1985
|
+
setUnwrappingState("pending");
|
|
1986
|
+
timeoutRef.current = setTimeout(() => {
|
|
1987
|
+
if (isUnwrapping.current) {
|
|
1988
|
+
setUnwrappingState("error");
|
|
1989
|
+
isUnwrapping.current = false;
|
|
1990
|
+
}
|
|
1991
|
+
}, WALLET_TIMEOUT_MS2);
|
|
1992
|
+
try {
|
|
1993
|
+
const chainConfig = getChainConfig(chainId);
|
|
1994
|
+
if (!chainConfig) throw new Error(`No config for chain ${chainId}`);
|
|
1995
|
+
const wrappedDecimals = chainConfig.wrappedAsset.decimals ?? chainConfig.nativeAsset.decimals;
|
|
1996
|
+
const amountRaw = parseUnits2(amount.toString(), wrappedDecimals);
|
|
1997
|
+
await unwrapMutation.mutateAsync({
|
|
1998
|
+
chainId,
|
|
1999
|
+
accountAddress: address,
|
|
2000
|
+
amountRaw,
|
|
2001
|
+
writeContractAsync
|
|
2002
|
+
});
|
|
2003
|
+
const nativeAsset = {
|
|
2004
|
+
symbol: chainConfig.nativeAsset.symbol,
|
|
2005
|
+
name: chainConfig.nativeAsset.name,
|
|
2006
|
+
address: NATIVE_ASSET_ADDRESS,
|
|
2007
|
+
decimals: chainConfig.nativeAsset.decimals,
|
|
2008
|
+
chainId
|
|
2009
|
+
};
|
|
2010
|
+
const wrappedAsset = {
|
|
2011
|
+
symbol: chainConfig.wrappedAsset.symbol,
|
|
2012
|
+
name: chainConfig.wrappedAsset.name,
|
|
2013
|
+
address: chainConfig.wrappedAsset.address,
|
|
2014
|
+
decimals: chainConfig.wrappedAsset.decimals,
|
|
2015
|
+
chainId
|
|
2016
|
+
};
|
|
2017
|
+
emitBalanceEvent({
|
|
2018
|
+
type: "unwrap",
|
|
2019
|
+
tokens: [
|
|
2020
|
+
{ asset: nativeAsset, userAddress: address },
|
|
2021
|
+
{ asset: wrappedAsset, userAddress: address }
|
|
2022
|
+
]
|
|
2023
|
+
});
|
|
2024
|
+
clearPendingTimeout();
|
|
2025
|
+
setUnwrappingState("success");
|
|
2026
|
+
goToNextStep(ComponentStep4, reviewState);
|
|
2027
|
+
await delayedClose();
|
|
2028
|
+
} catch {
|
|
2029
|
+
clearPendingTimeout();
|
|
2030
|
+
setUnwrappingState("error");
|
|
2031
|
+
} finally {
|
|
2032
|
+
isUnwrapping.current = false;
|
|
2033
|
+
}
|
|
2034
|
+
}, [address, wagmiStatus, chainId, amount, unwrapMutation, writeContractAsync, emitBalanceEvent, goToNextStep, reviewState, delayedClose, clearPendingTimeout]);
|
|
2035
|
+
useEffect10(() => {
|
|
2036
|
+
if (isUnwrapping.current || unwrapAttempted.current) return;
|
|
2037
|
+
if (reviewState === ComponentStep4 && unwrappingState === "idle" && wagmiStatus === "connected") {
|
|
2038
|
+
if (needsToUnwrap) handleUnwrap();
|
|
2039
|
+
else {
|
|
2040
|
+
setUnwrappingState("success");
|
|
2041
|
+
goToNextStep(ComponentStep4, reviewState);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
}, [reviewState, unwrappingState, needsToUnwrap, wagmiStatus, handleUnwrap, goToNextStep]);
|
|
2045
|
+
useEffect10(() => () => clearPendingTimeout(), [clearPendingTimeout]);
|
|
2046
|
+
const handleRetry = () => {
|
|
2047
|
+
if (unwrappingState === "error") {
|
|
2048
|
+
unwrapAttempted.current = false;
|
|
2049
|
+
setUnwrappingState("idle");
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
const isError = !hasSucceeded && unwrappingState === "error";
|
|
2053
|
+
const showCancel = !hasSucceeded && (unwrappingState === "pending" || unwrappingState === "idle" || unwrappingState === "error");
|
|
2054
|
+
const statusVar = isError ? "var(--widget-status-failed)" : "var(--widget-status-pending)";
|
|
2055
|
+
return /* @__PURE__ */ jsxs7("div", { className: "flex w-full flex-row justify-between space-x-3", children: [
|
|
2056
|
+
/* @__PURE__ */ jsx9(
|
|
2057
|
+
"button",
|
|
2058
|
+
{
|
|
2059
|
+
type: "button",
|
|
2060
|
+
className: `inline-flex h-12 w-full items-center justify-center text-sm font-medium transition duration-150 ${isError ? "cursor-pointer" : "cursor-not-allowed"}`,
|
|
2061
|
+
style: {
|
|
2062
|
+
backgroundColor: `color-mix(in srgb, ${statusVar} 12%, transparent)`,
|
|
2063
|
+
border: `1px solid color-mix(in srgb, ${statusVar} 20%, transparent)`,
|
|
2064
|
+
color: statusVar,
|
|
2065
|
+
borderRadius: "var(--widget-radius)"
|
|
2066
|
+
},
|
|
2067
|
+
onClick: isError ? handleRetry : void 0,
|
|
2068
|
+
children: /* @__PURE__ */ jsxs7("div", { className: "flex h-10 w-full flex-row items-center space-x-3 px-4", children: [
|
|
2069
|
+
/* @__PURE__ */ jsx9("p", { className: "whitespace-nowrap", children: isError ? "Retry Unwrap" : token ? `Unwrap ${token.symbol}` : "Unwrap Native Token" }),
|
|
2070
|
+
/* @__PURE__ */ jsx9("div", { className: "flex flex-1 border-t", style: { borderColor: "var(--widget-border)" } }),
|
|
2071
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex h-6 w-3 items-center justify-center rounded-full", children: [
|
|
2072
|
+
!hasSucceeded && (unwrappingState === "pending" || unwrappingState === "idle") && /* @__PURE__ */ jsx9(LoadingSpinner_default, { size: "5" }),
|
|
2073
|
+
(hasSucceeded || unwrappingState === "success") && /* @__PURE__ */ jsx9(Checkmark, { className: "h-8 w-8" }),
|
|
2074
|
+
isError && /* @__PURE__ */ jsx9(RedoIcon, { className: "h-[12px] w-[12px]" })
|
|
2075
|
+
] })
|
|
2076
|
+
] })
|
|
2077
|
+
}
|
|
2078
|
+
),
|
|
2079
|
+
showCancel && /* @__PURE__ */ jsx9(
|
|
2080
|
+
"button",
|
|
2081
|
+
{
|
|
2082
|
+
type: "button",
|
|
2083
|
+
className: "inline-flex h-12 items-center justify-center px-8 text-sm font-medium transition duration-150 cursor-pointer",
|
|
2084
|
+
style: {
|
|
2085
|
+
backgroundColor: "color-mix(in srgb, var(--widget-status-failed) 12%, transparent)",
|
|
2086
|
+
border: "1px solid color-mix(in srgb, var(--widget-status-failed) 20%, transparent)",
|
|
2087
|
+
color: "var(--widget-status-failed)",
|
|
2088
|
+
borderRadius: "var(--widget-radius)"
|
|
2089
|
+
},
|
|
2090
|
+
onClick: cancel,
|
|
2091
|
+
children: "Cancel"
|
|
2092
|
+
}
|
|
2093
|
+
)
|
|
2094
|
+
] });
|
|
2095
|
+
};
|
|
2096
|
+
var ReviewActionUnwrapGasToken_default = ReviewActionUnwrapGasToken;
|
|
2097
|
+
|
|
2098
|
+
// src/components/states/ReviewActionWrapGasToken.tsx
|
|
2099
|
+
import { useCallback as useCallback9, useEffect as useEffect11, useState as useState8, useRef as useRef10 } from "react";
|
|
2100
|
+
import { parseUnits as parseUnits3 } from "viem";
|
|
2101
|
+
import { useAccount as useAccount4, useWriteContract as useWriteContract2 } from "wagmi";
|
|
2102
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2103
|
+
var WALLET_TIMEOUT_MS3 = 15e3;
|
|
2104
|
+
var ComponentStep5 = "wrapping";
|
|
2105
|
+
var ReviewActionWrapGasToken = ({
|
|
2106
|
+
needsToWrap,
|
|
2107
|
+
amount,
|
|
2108
|
+
chainId,
|
|
2109
|
+
token,
|
|
2110
|
+
reviewState,
|
|
2111
|
+
setReviewState,
|
|
2112
|
+
goToNextStep,
|
|
2113
|
+
cancel,
|
|
2114
|
+
delayedClose,
|
|
2115
|
+
isWrappingPair
|
|
2116
|
+
}) => {
|
|
2117
|
+
const hasSucceeded = isReviewStepPast(ComponentStep5, reviewState);
|
|
2118
|
+
const [wrappingState, setWrappingState] = useState8("idle");
|
|
2119
|
+
const wrapMutation = useWrapToken();
|
|
2120
|
+
const { writeContractAsync } = useWriteContract2();
|
|
2121
|
+
const { address } = useWalletState();
|
|
2122
|
+
const emitBalanceEvent = useEmitBalanceEvent();
|
|
2123
|
+
const isWrapping = useRef10(false);
|
|
2124
|
+
const wrapAttempted = useRef10(false);
|
|
2125
|
+
const timeoutRef = useRef10(null);
|
|
2126
|
+
const { status: wagmiStatus } = useAccount4();
|
|
2127
|
+
const clearPendingTimeout = useCallback9(() => {
|
|
2128
|
+
if (timeoutRef.current) {
|
|
2129
|
+
clearTimeout(timeoutRef.current);
|
|
2130
|
+
timeoutRef.current = null;
|
|
2131
|
+
}
|
|
2132
|
+
}, []);
|
|
2133
|
+
const handleWrap = useCallback9(async () => {
|
|
2134
|
+
if (isWrapping.current || wrapAttempted.current || !address || wagmiStatus !== "connected") return;
|
|
2135
|
+
isWrapping.current = true;
|
|
2136
|
+
wrapAttempted.current = true;
|
|
2137
|
+
setWrappingState("pending");
|
|
2138
|
+
timeoutRef.current = setTimeout(() => {
|
|
2139
|
+
if (isWrapping.current) {
|
|
2140
|
+
setWrappingState("error");
|
|
2141
|
+
isWrapping.current = false;
|
|
2142
|
+
}
|
|
2143
|
+
}, WALLET_TIMEOUT_MS3);
|
|
2144
|
+
try {
|
|
2145
|
+
const chainConfig = getChainConfig(chainId);
|
|
2146
|
+
if (!chainConfig) throw new Error(`No config for chain ${chainId}`);
|
|
2147
|
+
const amountRaw = parseUnits3(amount.toString(), chainConfig.nativeAsset.decimals);
|
|
2148
|
+
await wrapMutation.mutateAsync({
|
|
2149
|
+
chainId,
|
|
2150
|
+
accountAddress: address,
|
|
2151
|
+
amountRaw,
|
|
2152
|
+
writeContractAsync
|
|
2153
|
+
});
|
|
2154
|
+
const nativeAsset = {
|
|
2155
|
+
symbol: chainConfig.nativeAsset.symbol,
|
|
2156
|
+
name: chainConfig.nativeAsset.name,
|
|
2157
|
+
address: NATIVE_ASSET_ADDRESS,
|
|
2158
|
+
decimals: chainConfig.nativeAsset.decimals,
|
|
2159
|
+
chainId
|
|
2160
|
+
};
|
|
2161
|
+
const wrappedAsset = {
|
|
2162
|
+
symbol: chainConfig.wrappedAsset.symbol,
|
|
2163
|
+
name: chainConfig.wrappedAsset.name,
|
|
2164
|
+
address: chainConfig.wrappedAsset.address,
|
|
2165
|
+
decimals: chainConfig.wrappedAsset.decimals,
|
|
2166
|
+
chainId
|
|
2167
|
+
};
|
|
2168
|
+
emitBalanceEvent({
|
|
2169
|
+
type: "wrap",
|
|
2170
|
+
tokens: [
|
|
2171
|
+
{ asset: nativeAsset, userAddress: address },
|
|
2172
|
+
{ asset: wrappedAsset, userAddress: address }
|
|
2173
|
+
]
|
|
2174
|
+
});
|
|
2175
|
+
clearPendingTimeout();
|
|
2176
|
+
setWrappingState("success");
|
|
2177
|
+
goToNextStep(ComponentStep5, reviewState);
|
|
2178
|
+
if (isWrappingPair) await delayedClose();
|
|
2179
|
+
} catch {
|
|
2180
|
+
clearPendingTimeout();
|
|
2181
|
+
setWrappingState("error");
|
|
2182
|
+
} finally {
|
|
2183
|
+
isWrapping.current = false;
|
|
2184
|
+
}
|
|
2185
|
+
}, [address, wagmiStatus, chainId, amount, wrapMutation, writeContractAsync, emitBalanceEvent, goToNextStep, reviewState, delayedClose, isWrappingPair, clearPendingTimeout]);
|
|
2186
|
+
useEffect11(() => {
|
|
2187
|
+
if (isWrapping.current || wrapAttempted.current) return;
|
|
2188
|
+
if (reviewState === ComponentStep5 && wrappingState === "idle" && wagmiStatus === "connected") {
|
|
2189
|
+
if (needsToWrap) handleWrap();
|
|
2190
|
+
else {
|
|
2191
|
+
setWrappingState("success");
|
|
2192
|
+
goToNextStep(ComponentStep5, reviewState);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
}, [reviewState, wrappingState, needsToWrap, wagmiStatus, handleWrap, goToNextStep]);
|
|
2196
|
+
useEffect11(() => () => clearPendingTimeout(), [clearPendingTimeout]);
|
|
2197
|
+
const handleRetry = () => {
|
|
2198
|
+
if (wrappingState === "error") {
|
|
2199
|
+
wrapAttempted.current = false;
|
|
2200
|
+
setWrappingState("idle");
|
|
2201
|
+
}
|
|
2202
|
+
};
|
|
2203
|
+
const isError = !hasSucceeded && wrappingState === "error";
|
|
2204
|
+
const showCancel = !hasSucceeded && (wrappingState === "pending" || wrappingState === "idle" || wrappingState === "error");
|
|
2205
|
+
const statusVar = isError ? "var(--widget-status-failed)" : "var(--widget-status-pending)";
|
|
2206
|
+
return /* @__PURE__ */ jsxs8("div", { className: "flex w-full flex-row justify-between space-x-3", children: [
|
|
2207
|
+
/* @__PURE__ */ jsx10(
|
|
2208
|
+
"button",
|
|
2209
|
+
{
|
|
2210
|
+
type: "button",
|
|
2211
|
+
className: `inline-flex h-12 w-full items-center justify-center text-sm font-medium transition duration-150 ${isError ? "cursor-pointer" : "cursor-not-allowed"}`,
|
|
2212
|
+
style: {
|
|
2213
|
+
backgroundColor: `color-mix(in srgb, ${statusVar} 12%, transparent)`,
|
|
2214
|
+
border: `1px solid color-mix(in srgb, ${statusVar} 20%, transparent)`,
|
|
2215
|
+
color: statusVar,
|
|
2216
|
+
borderRadius: "var(--widget-radius)"
|
|
2217
|
+
},
|
|
2218
|
+
onClick: isError ? handleRetry : void 0,
|
|
2219
|
+
children: /* @__PURE__ */ jsxs8("div", { className: "flex h-10 w-full flex-row items-center space-x-3 px-4", children: [
|
|
2220
|
+
/* @__PURE__ */ jsx10("p", { className: "whitespace-nowrap", children: isError ? "Retry Wrap" : token ? `Wrap ${token.symbol}` : "Wrap Native Token" }),
|
|
2221
|
+
/* @__PURE__ */ jsx10("div", { className: "flex flex-1 border-t", style: { borderColor: "var(--widget-border)" } }),
|
|
2222
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex h-6 w-3 items-center justify-center rounded-full", children: [
|
|
2223
|
+
!hasSucceeded && (wrappingState === "pending" || wrappingState === "idle") && /* @__PURE__ */ jsx10(LoadingSpinner_default, { size: "5" }),
|
|
2224
|
+
(hasSucceeded || wrappingState === "success") && /* @__PURE__ */ jsx10(Checkmark, { className: "h-8 w-8" }),
|
|
2225
|
+
isError && /* @__PURE__ */ jsx10(RedoIcon, { className: "h-[12px] w-[12px]" })
|
|
2226
|
+
] })
|
|
2227
|
+
] })
|
|
2228
|
+
}
|
|
2229
|
+
),
|
|
2230
|
+
showCancel && /* @__PURE__ */ jsx10(
|
|
2231
|
+
"button",
|
|
2232
|
+
{
|
|
2233
|
+
type: "button",
|
|
2234
|
+
className: "inline-flex h-12 items-center justify-center px-8 text-sm font-medium transition duration-150 cursor-pointer",
|
|
2235
|
+
style: {
|
|
2236
|
+
backgroundColor: "color-mix(in srgb, var(--widget-status-failed) 12%, transparent)",
|
|
2237
|
+
border: "1px solid color-mix(in srgb, var(--widget-status-failed) 20%, transparent)",
|
|
2238
|
+
color: "var(--widget-status-failed)",
|
|
2239
|
+
borderRadius: "var(--widget-radius)"
|
|
2240
|
+
},
|
|
2241
|
+
onClick: cancel,
|
|
2242
|
+
children: "Cancel"
|
|
2243
|
+
}
|
|
2244
|
+
)
|
|
2245
|
+
] });
|
|
2246
|
+
};
|
|
2247
|
+
var ReviewActionWrapGasToken_default = ReviewActionWrapGasToken;
|
|
2248
|
+
|
|
2249
|
+
// src/components/states/SwapButton.tsx
|
|
2250
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2251
|
+
var SwapButton = ({
|
|
2252
|
+
base,
|
|
2253
|
+
baseAmount,
|
|
2254
|
+
quote,
|
|
2255
|
+
quoteAmount,
|
|
2256
|
+
userAddress,
|
|
2257
|
+
isWrappingPair,
|
|
2258
|
+
isUnwrappingPair,
|
|
2259
|
+
isBaseGasToken,
|
|
2260
|
+
isQuoteGasToken,
|
|
2261
|
+
reviewActionProps,
|
|
2262
|
+
reviewState,
|
|
2263
|
+
setReviewState,
|
|
2264
|
+
trackedOrderHash,
|
|
2265
|
+
setTrackedOrderHash,
|
|
2266
|
+
txStatus,
|
|
2267
|
+
onSwapComplete,
|
|
2268
|
+
onSwapInitiated,
|
|
2269
|
+
onOrderSubmitted,
|
|
2270
|
+
onStaleQuoteRestart,
|
|
2271
|
+
setExitHandler,
|
|
2272
|
+
setRetryHandler
|
|
2273
|
+
}) => {
|
|
2274
|
+
const {
|
|
2275
|
+
rfqQuote,
|
|
2276
|
+
status: rfqStatus,
|
|
2277
|
+
liquidityError,
|
|
2278
|
+
routingError,
|
|
2279
|
+
sizeCapError,
|
|
2280
|
+
stop,
|
|
2281
|
+
clear
|
|
2282
|
+
} = useRfq();
|
|
2283
|
+
const { clearForm } = useSwapFormContext();
|
|
2284
|
+
const currentChainId = useChainId2();
|
|
2285
|
+
const { swapButtonVariant, widgetType } = useWidgetConfig();
|
|
2286
|
+
const isCompact = widgetType === "compact";
|
|
2287
|
+
const { trackOrderTransaction, trackNativeTransaction } = useTransactionTracker();
|
|
2288
|
+
const { startPolling } = useOrderStatusPolling((orderHash, status) => {
|
|
2289
|
+
useWidgetSwapUIStore.getState().setTxStatus(status);
|
|
2290
|
+
if (status === "completed") {
|
|
2291
|
+
trackOrderTransaction(orderHash, `Swap ${base.symbol} \u2192 ${quote.symbol}`);
|
|
2292
|
+
}
|
|
2293
|
+
});
|
|
2294
|
+
const [isRestarting, setIsRestarting] = useState9(false);
|
|
2295
|
+
const awaitingAutoResumeRef = useRef11(false);
|
|
2296
|
+
const restartTimerRef = useRef11(null);
|
|
2297
|
+
const needsToWrap = isWrappingPair;
|
|
2298
|
+
const needsToUnwrap = isUnwrappingPair;
|
|
2299
|
+
const needsSignature = !isWrappingPair && !isUnwrappingPair;
|
|
2300
|
+
const needsChainSwitch = currentChainId !== base?.chainId && (needsToWrap || needsToUnwrap || needsSignature);
|
|
2301
|
+
const chainConfig = getChainConfig(base.chainId);
|
|
2302
|
+
const requiredChain = {
|
|
2303
|
+
chainId: base.chainId,
|
|
2304
|
+
name: chainConfig?.displayName ?? `Chain ${base.chainId}`
|
|
2305
|
+
};
|
|
2306
|
+
const goToNextStep = useCallback10(
|
|
2307
|
+
(componentStep, currentStep) => {
|
|
2308
|
+
if (currentStep !== componentStep) return;
|
|
2309
|
+
if (componentStep === "chain") {
|
|
2310
|
+
if (needsToWrap) {
|
|
2311
|
+
setReviewState("wrapping");
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2314
|
+
if (needsToUnwrap) {
|
|
2315
|
+
setReviewState("unwrapping");
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2318
|
+
setReviewState("signingOrder");
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2321
|
+
if (componentStep === "signingOrder") {
|
|
2322
|
+
setReviewState("trackingTx");
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
if (componentStep === "wrapping") {
|
|
2326
|
+
if (!isWrappingPair) {
|
|
2327
|
+
setReviewState("signingOrder");
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
setReviewState("wrapSuccess");
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
if (componentStep === "unwrapping") {
|
|
2334
|
+
setReviewState("wrapSuccess");
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2337
|
+
const nextStep = getNextReviewStep(componentStep);
|
|
2338
|
+
if (nextStep) setReviewState(nextStep);
|
|
2339
|
+
},
|
|
2340
|
+
[
|
|
2341
|
+
setReviewState,
|
|
2342
|
+
isWrappingPair,
|
|
2343
|
+
needsToWrap,
|
|
2344
|
+
needsToUnwrap,
|
|
2345
|
+
needsSignature
|
|
2346
|
+
]
|
|
2347
|
+
);
|
|
2348
|
+
const determineInitialStep = useCallback10(() => {
|
|
2349
|
+
if (needsChainSwitch) return "chain";
|
|
2350
|
+
if (needsToWrap) return "wrapping";
|
|
2351
|
+
if (needsToUnwrap) return "unwrapping";
|
|
2352
|
+
if (needsSignature) return "signingOrder";
|
|
2353
|
+
return null;
|
|
2354
|
+
}, [
|
|
2355
|
+
needsChainSwitch,
|
|
2356
|
+
needsToWrap,
|
|
2357
|
+
needsToUnwrap,
|
|
2358
|
+
needsSignature
|
|
2359
|
+
]);
|
|
2360
|
+
const resetState = useCallback10(() => {
|
|
2361
|
+
setReviewState(null);
|
|
2362
|
+
clearForm();
|
|
2363
|
+
}, [clearForm, setReviewState]);
|
|
2364
|
+
const delayedClose = useCallback10(async () => {
|
|
2365
|
+
setReviewState("wrapSuccess");
|
|
2366
|
+
for (let i = 0; i < 3; i++) {
|
|
2367
|
+
await sleep(700);
|
|
2368
|
+
}
|
|
2369
|
+
resetState();
|
|
2370
|
+
}, [resetState, setReviewState]);
|
|
2371
|
+
const cancel = useCallback10(async () => {
|
|
2372
|
+
clear();
|
|
2373
|
+
setReviewState("cancelled");
|
|
2374
|
+
for (let i = 0; i < 3; i++) {
|
|
2375
|
+
await sleep(500);
|
|
2376
|
+
}
|
|
2377
|
+
resetState();
|
|
2378
|
+
}, [clear, resetState, setReviewState]);
|
|
2379
|
+
const handleNewSwap = useCallback10(() => {
|
|
2380
|
+
if (!trackedOrderHash) return;
|
|
2381
|
+
useWidgetSwapUIStore.getState().stopTracking();
|
|
2382
|
+
setReviewState(null);
|
|
2383
|
+
setTrackedOrderHash(null);
|
|
2384
|
+
clearForm();
|
|
2385
|
+
}, [trackedOrderHash, setReviewState, setTrackedOrderHash, clearForm]);
|
|
2386
|
+
const handleRetrySwap = useCallback10(() => {
|
|
2387
|
+
setReviewState(null);
|
|
2388
|
+
setTrackedOrderHash(null);
|
|
2389
|
+
awaitingAutoResumeRef.current = true;
|
|
2390
|
+
onStaleQuoteRestart?.();
|
|
2391
|
+
}, [setReviewState, setTrackedOrderHash, onStaleQuoteRestart]);
|
|
2392
|
+
const handleReview = useCallback10(() => {
|
|
2393
|
+
if (reviewActionProps) {
|
|
2394
|
+
setReviewState(determineInitialStep());
|
|
2395
|
+
}
|
|
2396
|
+
}, [determineInitialStep, reviewActionProps, setReviewState]);
|
|
2397
|
+
const restartSigningFlow = useCallback10(
|
|
2398
|
+
(isFromStaleState = false) => {
|
|
2399
|
+
if (isFromStaleState) {
|
|
2400
|
+
awaitingAutoResumeRef.current = true;
|
|
2401
|
+
setReviewState(null);
|
|
2402
|
+
onStaleQuoteRestart?.();
|
|
2403
|
+
} else {
|
|
2404
|
+
setReviewState(null);
|
|
2405
|
+
restartTimerRef.current = setTimeout(() => {
|
|
2406
|
+
if (reviewActionProps) {
|
|
2407
|
+
setReviewState(determineInitialStep());
|
|
2408
|
+
setIsRestarting(false);
|
|
2409
|
+
}
|
|
2410
|
+
}, 100);
|
|
2411
|
+
}
|
|
2412
|
+
},
|
|
2413
|
+
[
|
|
2414
|
+
setReviewState,
|
|
2415
|
+
reviewActionProps,
|
|
2416
|
+
determineInitialStep,
|
|
2417
|
+
onStaleQuoteRestart
|
|
2418
|
+
]
|
|
2419
|
+
);
|
|
2420
|
+
useEffect12(() => {
|
|
2421
|
+
if (reviewState !== null) stop();
|
|
2422
|
+
}, [reviewState, stop]);
|
|
2423
|
+
useEffect12(() => {
|
|
2424
|
+
if (reviewState !== null && isRestarting) setIsRestarting(false);
|
|
2425
|
+
}, [reviewState, isRestarting]);
|
|
2426
|
+
useEffect12(() => {
|
|
2427
|
+
setExitHandler?.(handleNewSwap);
|
|
2428
|
+
}, [setExitHandler, handleNewSwap]);
|
|
2429
|
+
useEffect12(() => {
|
|
2430
|
+
setRetryHandler?.(handleRetrySwap);
|
|
2431
|
+
}, [setRetryHandler, handleRetrySwap]);
|
|
2432
|
+
useEffect12(() => {
|
|
2433
|
+
return () => {
|
|
2434
|
+
if (restartTimerRef.current) clearTimeout(restartTimerRef.current);
|
|
2435
|
+
};
|
|
2436
|
+
}, []);
|
|
2437
|
+
useEffect12(() => {
|
|
2438
|
+
if (awaitingAutoResumeRef.current && rfqStatus === "fresh" && rfqQuote?.id) {
|
|
2439
|
+
awaitingAutoResumeRef.current = false;
|
|
2440
|
+
setReviewState("signingOrder");
|
|
2441
|
+
}
|
|
2442
|
+
}, [rfqStatus, rfqQuote?.id, setReviewState]);
|
|
2443
|
+
const getIdleButtonStyle = (active) => {
|
|
2444
|
+
if (!active) {
|
|
2445
|
+
return {
|
|
2446
|
+
backgroundColor: swapButtonVariant === "default" ? "var(--widget-muted)" : "transparent",
|
|
2447
|
+
color: "var(--widget-muted-foreground)",
|
|
2448
|
+
border: swapButtonVariant !== "default" ? "1px solid var(--widget-border)" : "none",
|
|
2449
|
+
borderRadius: "var(--widget-radius)"
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
switch (swapButtonVariant) {
|
|
2453
|
+
case "outline":
|
|
2454
|
+
return {
|
|
2455
|
+
backgroundColor: "transparent",
|
|
2456
|
+
color: "var(--widget-primary)",
|
|
2457
|
+
border: "1px solid var(--widget-primary)",
|
|
2458
|
+
borderRadius: "var(--widget-radius)"
|
|
2459
|
+
};
|
|
2460
|
+
case "ghost":
|
|
2461
|
+
return {
|
|
2462
|
+
backgroundColor: "transparent",
|
|
2463
|
+
color: "var(--widget-primary)",
|
|
2464
|
+
border: "none",
|
|
2465
|
+
borderRadius: "var(--widget-radius)"
|
|
2466
|
+
};
|
|
2467
|
+
default:
|
|
2468
|
+
return {
|
|
2469
|
+
backgroundColor: "var(--widget-primary)",
|
|
2470
|
+
color: "var(--widget-primary-foreground)",
|
|
2471
|
+
borderRadius: "var(--widget-radius)"
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
};
|
|
2475
|
+
const idleButtonClass = `w-full ${isCompact ? "py-2" : "py-3"} text-sm font-medium transition-colors cursor-pointer mt-1`;
|
|
2476
|
+
if (reviewState === null && !isWrappingPair && !isUnwrappingPair) {
|
|
2477
|
+
if (isRestarting) {
|
|
2478
|
+
return /* @__PURE__ */ jsx11(
|
|
2479
|
+
"button",
|
|
2480
|
+
{
|
|
2481
|
+
className: idleButtonClass,
|
|
2482
|
+
disabled: true,
|
|
2483
|
+
style: {
|
|
2484
|
+
...getIdleButtonStyle(false),
|
|
2485
|
+
opacity: 0.6,
|
|
2486
|
+
cursor: "not-allowed"
|
|
2487
|
+
},
|
|
2488
|
+
children: "Getting fresh quote..."
|
|
2489
|
+
}
|
|
2490
|
+
);
|
|
2491
|
+
}
|
|
2492
|
+
const getIdleText = () => {
|
|
2493
|
+
if (liquidityError) return "Insufficient Liquidity";
|
|
2494
|
+
if (routingError) return "Route Not Available";
|
|
2495
|
+
if (sizeCapError) return "Size Cap Exceeded";
|
|
2496
|
+
if (!rfqQuote && rfqStatus === "polling") return "Getting Quote...";
|
|
2497
|
+
if (!rfqQuote) return "Enter Amount";
|
|
2498
|
+
return "Swap";
|
|
2499
|
+
};
|
|
2500
|
+
const isActive = !!reviewActionProps;
|
|
2501
|
+
return /* @__PURE__ */ jsx11(
|
|
2502
|
+
"button",
|
|
2503
|
+
{
|
|
2504
|
+
onClick: isActive ? handleReview : void 0,
|
|
2505
|
+
disabled: !isActive,
|
|
2506
|
+
className: `${idleButtonClass} disabled:opacity-40 disabled:cursor-not-allowed`,
|
|
2507
|
+
style: getIdleButtonStyle(isActive),
|
|
2508
|
+
children: getIdleText()
|
|
2509
|
+
}
|
|
2510
|
+
);
|
|
2511
|
+
}
|
|
2512
|
+
if (reviewState === null && isUnwrappingPair) {
|
|
2513
|
+
return /* @__PURE__ */ jsx11(
|
|
2514
|
+
"button",
|
|
2515
|
+
{
|
|
2516
|
+
onClick: reviewActionProps ? handleReview : void 0,
|
|
2517
|
+
disabled: !reviewActionProps,
|
|
2518
|
+
className: `${idleButtonClass} disabled:opacity-40 disabled:cursor-not-allowed`,
|
|
2519
|
+
style: getIdleButtonStyle(!!reviewActionProps),
|
|
2520
|
+
children: "Unwrap"
|
|
2521
|
+
}
|
|
2522
|
+
);
|
|
2523
|
+
}
|
|
2524
|
+
if (reviewState === null && isWrappingPair) {
|
|
2525
|
+
return /* @__PURE__ */ jsx11(
|
|
2526
|
+
"button",
|
|
2527
|
+
{
|
|
2528
|
+
onClick: reviewActionProps ? handleReview : void 0,
|
|
2529
|
+
disabled: !reviewActionProps,
|
|
2530
|
+
className: `${idleButtonClass} disabled:opacity-40 disabled:cursor-not-allowed`,
|
|
2531
|
+
style: getIdleButtonStyle(!!reviewActionProps),
|
|
2532
|
+
children: "Wrap"
|
|
2533
|
+
}
|
|
2534
|
+
);
|
|
2535
|
+
}
|
|
2536
|
+
if (reviewState === "success") {
|
|
2537
|
+
return /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-2 w-full", children: /* @__PURE__ */ jsx11(
|
|
2538
|
+
"button",
|
|
2539
|
+
{
|
|
2540
|
+
className: `w-full ${isCompact ? "py-2" : "py-3"} text-sm font-medium cursor-default`,
|
|
2541
|
+
style: {
|
|
2542
|
+
backgroundColor: "color-mix(in srgb, var(--widget-status-completed) 15%, transparent)",
|
|
2543
|
+
color: "var(--widget-status-completed)",
|
|
2544
|
+
border: "1px solid color-mix(in srgb, var(--widget-status-completed) 20%, transparent)",
|
|
2545
|
+
borderRadius: "var(--widget-radius)"
|
|
2546
|
+
},
|
|
2547
|
+
children: "Order Placed"
|
|
2548
|
+
}
|
|
2549
|
+
) });
|
|
2550
|
+
}
|
|
2551
|
+
if (reviewState === "wrapSuccess") {
|
|
2552
|
+
return /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-2 w-full", children: /* @__PURE__ */ jsx11(
|
|
2553
|
+
"button",
|
|
2554
|
+
{
|
|
2555
|
+
className: `w-full ${isCompact ? "py-2" : "py-3"} text-sm font-medium cursor-default`,
|
|
2556
|
+
style: {
|
|
2557
|
+
backgroundColor: "color-mix(in srgb, var(--widget-status-completed) 15%, transparent)",
|
|
2558
|
+
color: "var(--widget-status-completed)",
|
|
2559
|
+
border: "1px solid color-mix(in srgb, var(--widget-status-completed) 20%, transparent)",
|
|
2560
|
+
borderRadius: "var(--widget-radius)"
|
|
2561
|
+
},
|
|
2562
|
+
children: isWrappingPair ? "Wrapped Successfully" : "Unwrapped Successfully"
|
|
2563
|
+
}
|
|
2564
|
+
) });
|
|
2565
|
+
}
|
|
2566
|
+
if (reviewState === "cancelled") {
|
|
2567
|
+
return /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-2 w-full", children: /* @__PURE__ */ jsx11(
|
|
2568
|
+
"button",
|
|
2569
|
+
{
|
|
2570
|
+
onClick: resetState,
|
|
2571
|
+
className: `w-full ${isCompact ? "py-2" : "py-3"} text-sm font-medium cursor-pointer`,
|
|
2572
|
+
style: {
|
|
2573
|
+
backgroundColor: "color-mix(in srgb, var(--widget-status-failed) 15%, transparent)",
|
|
2574
|
+
color: "var(--widget-status-failed)",
|
|
2575
|
+
border: "1px solid color-mix(in srgb, var(--widget-status-failed) 20%, transparent)",
|
|
2576
|
+
borderRadius: "var(--widget-radius)"
|
|
2577
|
+
},
|
|
2578
|
+
children: "Order Cancelled"
|
|
2579
|
+
}
|
|
2580
|
+
) });
|
|
2581
|
+
}
|
|
2582
|
+
return /* @__PURE__ */ jsxs9("div", { className: "flex w-full", children: [
|
|
2583
|
+
reviewState === "chain" && needsChainSwitch && /* @__PURE__ */ jsx11(
|
|
2584
|
+
ReviewActionChainSwitchRow_default,
|
|
2585
|
+
{
|
|
2586
|
+
requiredChain,
|
|
2587
|
+
needsChainSwitch: needsChainSwitch && currentChainId !== base.chainId,
|
|
2588
|
+
reviewState,
|
|
2589
|
+
goToNextStep,
|
|
2590
|
+
cancel
|
|
2591
|
+
}
|
|
2592
|
+
),
|
|
2593
|
+
reviewState === "wrapping" && needsToWrap && /* @__PURE__ */ jsx11(
|
|
2594
|
+
ReviewActionWrapGasToken_default,
|
|
2595
|
+
{
|
|
2596
|
+
needsToWrap,
|
|
2597
|
+
amount: baseAmount,
|
|
2598
|
+
chainId: base.chainId,
|
|
2599
|
+
token: base,
|
|
2600
|
+
reviewState,
|
|
2601
|
+
setReviewState,
|
|
2602
|
+
goToNextStep,
|
|
2603
|
+
cancel,
|
|
2604
|
+
delayedClose,
|
|
2605
|
+
isWrappingPair
|
|
2606
|
+
}
|
|
2607
|
+
),
|
|
2608
|
+
reviewState === "unwrapping" && needsToUnwrap && /* @__PURE__ */ jsx11(
|
|
2609
|
+
ReviewActionUnwrapGasToken_default,
|
|
2610
|
+
{
|
|
2611
|
+
needsToUnwrap,
|
|
2612
|
+
amount: baseAmount,
|
|
2613
|
+
chainId: base.chainId,
|
|
2614
|
+
token: base,
|
|
2615
|
+
reviewState,
|
|
2616
|
+
setReviewState,
|
|
2617
|
+
goToNextStep,
|
|
2618
|
+
cancel,
|
|
2619
|
+
delayedClose
|
|
2620
|
+
}
|
|
2621
|
+
),
|
|
2622
|
+
reviewState === "signingOrder" && needsSignature && /* @__PURE__ */ jsx11(
|
|
2623
|
+
ReviewActionSignAndSubmitOrderRow_default,
|
|
2624
|
+
{
|
|
2625
|
+
base,
|
|
2626
|
+
baseAmount,
|
|
2627
|
+
quote,
|
|
2628
|
+
quoteAmount,
|
|
2629
|
+
userAddress,
|
|
2630
|
+
reviewState,
|
|
2631
|
+
goToNextStep,
|
|
2632
|
+
cancel,
|
|
2633
|
+
restartSigningFlow,
|
|
2634
|
+
onSwapInitiated,
|
|
2635
|
+
onOrderSubmitted,
|
|
2636
|
+
isWrappingPair,
|
|
2637
|
+
isUnwrappingPair,
|
|
2638
|
+
onSwapComplete,
|
|
2639
|
+
startPolling,
|
|
2640
|
+
trackNativeTransaction
|
|
2641
|
+
}
|
|
2642
|
+
),
|
|
2643
|
+
reviewState === "trackingTx" && trackedOrderHash && /* @__PURE__ */ jsx11(
|
|
2644
|
+
ReviewActionTxRow_default,
|
|
2645
|
+
{
|
|
2646
|
+
orderHash: trackedOrderHash,
|
|
2647
|
+
base,
|
|
2648
|
+
quote,
|
|
2649
|
+
baseAmount,
|
|
2650
|
+
quoteAmount,
|
|
2651
|
+
reviewState,
|
|
2652
|
+
status: txStatus,
|
|
2653
|
+
onNewSwap: handleNewSwap,
|
|
2654
|
+
onRetry: handleRetrySwap
|
|
2655
|
+
}
|
|
2656
|
+
),
|
|
2657
|
+
reviewState && ![
|
|
2658
|
+
"chain",
|
|
2659
|
+
"wrapping",
|
|
2660
|
+
"unwrapping",
|
|
2661
|
+
"approval",
|
|
2662
|
+
"signingOrder",
|
|
2663
|
+
"trackingTx",
|
|
2664
|
+
"success",
|
|
2665
|
+
"wrapSuccess",
|
|
2666
|
+
"cancelled"
|
|
2667
|
+
].includes(reviewState) && /* @__PURE__ */ jsx11(
|
|
2668
|
+
"button",
|
|
2669
|
+
{
|
|
2670
|
+
disabled: true,
|
|
2671
|
+
className: `w-full ${isCompact ? "py-2" : "py-3"} text-sm font-medium`,
|
|
2672
|
+
style: {
|
|
2673
|
+
backgroundColor: "var(--widget-muted)",
|
|
2674
|
+
color: "var(--widget-muted-foreground)",
|
|
2675
|
+
borderRadius: "var(--widget-radius)"
|
|
2676
|
+
},
|
|
2677
|
+
children: "Processing..."
|
|
2678
|
+
}
|
|
2679
|
+
)
|
|
2680
|
+
] });
|
|
2681
|
+
};
|
|
2682
|
+
|
|
2683
|
+
// src/components/states/TxStatusDisplay.tsx
|
|
2684
|
+
import { useState as useState10, useCallback as useCallback11, useRef as useRef12, useEffect as useEffect13 } from "react";
|
|
2685
|
+
import { useShallow } from "zustand/react/shallow";
|
|
2686
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2687
|
+
var statusVarMap = {
|
|
2688
|
+
pending: "var(--widget-status-pending)",
|
|
2689
|
+
received: "var(--widget-status-received)",
|
|
2690
|
+
completed: "var(--widget-status-completed)",
|
|
2691
|
+
failed: "var(--widget-status-failed)",
|
|
2692
|
+
cancelled: "var(--widget-status-failed)"
|
|
2693
|
+
};
|
|
2694
|
+
var statusLabels = {
|
|
2695
|
+
pending: { title: "Trade Pending", subtitle: "Submitting to network" },
|
|
2696
|
+
received: { title: "Trade Received", subtitle: "Processing settlement" },
|
|
2697
|
+
completed: { title: "Trade Filled", subtitle: "Settlement complete" },
|
|
2698
|
+
failed: { title: "Trade Failed", subtitle: "Settlement unsuccessful" },
|
|
2699
|
+
cancelled: { title: "Trade Cancelled", subtitle: "Order was cancelled" }
|
|
2700
|
+
};
|
|
2701
|
+
function getStatusStyles(status) {
|
|
2702
|
+
const v = statusVarMap[status] ?? statusVarMap.pending;
|
|
2703
|
+
return {
|
|
2704
|
+
textColor: v,
|
|
2705
|
+
bgColor: `color-mix(in srgb, ${v} 15%, transparent)`,
|
|
2706
|
+
borderColor: `color-mix(in srgb, ${v} 20%, transparent)`,
|
|
2707
|
+
cardBgColor: `color-mix(in srgb, ${v} 8%, transparent)`,
|
|
2708
|
+
cardBorderColor: `color-mix(in srgb, ${v} 15%, transparent)`,
|
|
2709
|
+
iconBgColor: `color-mix(in srgb, ${v} 12%, transparent)`,
|
|
2710
|
+
iconBorderColor: `color-mix(in srgb, ${v} 15%, transparent)`
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
var StatusIcon = ({ status }) => {
|
|
2714
|
+
switch (status) {
|
|
2715
|
+
case "completed":
|
|
2716
|
+
return /* @__PURE__ */ jsx12(Checkmark, {});
|
|
2717
|
+
case "failed":
|
|
2718
|
+
case "cancelled":
|
|
2719
|
+
return /* @__PURE__ */ jsx12(XAnimation, {});
|
|
2720
|
+
default:
|
|
2721
|
+
return /* @__PURE__ */ jsx12(LoadingSpinner_default, { size: "10" });
|
|
2722
|
+
}
|
|
2723
|
+
};
|
|
2724
|
+
var TxStatusDisplay = ({
|
|
2725
|
+
orderHash,
|
|
2726
|
+
base,
|
|
2727
|
+
quote,
|
|
2728
|
+
baseAmount,
|
|
2729
|
+
quoteAmount,
|
|
2730
|
+
status
|
|
2731
|
+
}) => {
|
|
2732
|
+
const [copied, setCopied] = useState10(false);
|
|
2733
|
+
const receivedTimeRef = useRef12(null);
|
|
2734
|
+
const submissionTimeRef = useRef12(Date.now());
|
|
2735
|
+
const [transactionSpeed, setTransactionSpeed] = useState10(null);
|
|
2736
|
+
const copyTimerRef = useRef12(null);
|
|
2737
|
+
const { isRecipientInputOpen, recipient } = useWidgetSwapUIStore(
|
|
2738
|
+
useShallow((state) => ({
|
|
2739
|
+
isRecipientInputOpen: state.isRecipientInputOpen,
|
|
2740
|
+
recipient: state.recipient
|
|
2741
|
+
}))
|
|
2742
|
+
);
|
|
2743
|
+
useEffect13(() => {
|
|
2744
|
+
if (status === "received") receivedTimeRef.current = Date.now();
|
|
2745
|
+
}, [status]);
|
|
2746
|
+
useEffect13(() => {
|
|
2747
|
+
if (status === "completed" && !transactionSpeed) {
|
|
2748
|
+
const startTime = receivedTimeRef.current || submissionTimeRef.current;
|
|
2749
|
+
setTransactionSpeed((Date.now() - startTime) / 1e3);
|
|
2750
|
+
}
|
|
2751
|
+
}, [status, transactionSpeed]);
|
|
2752
|
+
useEffect13(() => {
|
|
2753
|
+
return () => {
|
|
2754
|
+
if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
|
|
2755
|
+
};
|
|
2756
|
+
}, []);
|
|
2757
|
+
const handleCopyHash = useCallback11(() => {
|
|
2758
|
+
if (!navigator.clipboard?.writeText) return;
|
|
2759
|
+
navigator.clipboard.writeText(orderHash).then(() => {
|
|
2760
|
+
setCopied(true);
|
|
2761
|
+
if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
|
|
2762
|
+
copyTimerRef.current = setTimeout(() => setCopied(false), 2e3);
|
|
2763
|
+
}).catch(() => {
|
|
2764
|
+
});
|
|
2765
|
+
}, [orderHash]);
|
|
2766
|
+
const labels = statusLabels[status] ?? statusLabels.pending;
|
|
2767
|
+
const config = getStatusStyles(status);
|
|
2768
|
+
const formattedBaseAmount = formatNumber(baseAmount);
|
|
2769
|
+
const formattedQuoteAmount = formatNumber(quoteAmount);
|
|
2770
|
+
return /* @__PURE__ */ jsx12(
|
|
2771
|
+
"div",
|
|
2772
|
+
{
|
|
2773
|
+
className: "relative w-full h-full overflow-hidden flex flex-col",
|
|
2774
|
+
style: {
|
|
2775
|
+
backgroundColor: config.bgColor,
|
|
2776
|
+
border: `1px var(--widget-border-style) ${config.borderColor}`,
|
|
2777
|
+
backdropFilter: "blur(8px)"
|
|
2778
|
+
},
|
|
2779
|
+
children: /* @__PURE__ */ jsxs10("div", { className: "relative z-10 px-3 pt-5 pb-3 flex-1 flex flex-col", children: [
|
|
2780
|
+
/* @__PURE__ */ jsx12("div", { className: "flex items-center justify-center mb-2", children: /* @__PURE__ */ jsxs10("div", { className: "flex flex-col items-center gap-1 text-center", children: [
|
|
2781
|
+
/* @__PURE__ */ jsx12(
|
|
2782
|
+
"div",
|
|
2783
|
+
{
|
|
2784
|
+
className: "flex h-10 w-10 items-center justify-center rounded-full border",
|
|
2785
|
+
style: {
|
|
2786
|
+
backgroundColor: config.iconBgColor,
|
|
2787
|
+
borderColor: config.iconBorderColor
|
|
2788
|
+
},
|
|
2789
|
+
children: /* @__PURE__ */ jsx12("div", { className: "h-7 w-7 flex items-center justify-center", children: /* @__PURE__ */ jsx12(StatusIcon, { status }) })
|
|
2790
|
+
}
|
|
2791
|
+
),
|
|
2792
|
+
/* @__PURE__ */ jsxs10("div", { children: [
|
|
2793
|
+
/* @__PURE__ */ jsx12(
|
|
2794
|
+
"h3",
|
|
2795
|
+
{
|
|
2796
|
+
className: "text-xl font-bold",
|
|
2797
|
+
style: { color: config.textColor },
|
|
2798
|
+
children: labels.title
|
|
2799
|
+
}
|
|
2800
|
+
),
|
|
2801
|
+
/* @__PURE__ */ jsx12(
|
|
2802
|
+
"p",
|
|
2803
|
+
{
|
|
2804
|
+
className: "text-2xs uppercase tracking-wider",
|
|
2805
|
+
style: { color: config.textColor, opacity: 0.6 },
|
|
2806
|
+
children: labels.subtitle
|
|
2807
|
+
}
|
|
2808
|
+
)
|
|
2809
|
+
] })
|
|
2810
|
+
] }) }),
|
|
2811
|
+
/* @__PURE__ */ jsx12("div", { className: "flex-1 flex flex-col justify-center", children: /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
|
|
2812
|
+
/* @__PURE__ */ jsxs10(
|
|
2813
|
+
"div",
|
|
2814
|
+
{
|
|
2815
|
+
className: "flex items-center justify-between backdrop-blur-sm p-2.5 border transition-all duration-300",
|
|
2816
|
+
style: {
|
|
2817
|
+
backgroundColor: config.cardBgColor,
|
|
2818
|
+
borderColor: config.cardBorderColor
|
|
2819
|
+
},
|
|
2820
|
+
children: [
|
|
2821
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2.5", children: [
|
|
2822
|
+
/* @__PURE__ */ jsx12(TokenImage_default, { asset: base, size: "sm" }),
|
|
2823
|
+
/* @__PURE__ */ jsxs10("div", { children: [
|
|
2824
|
+
/* @__PURE__ */ jsx12(
|
|
2825
|
+
"div",
|
|
2826
|
+
{
|
|
2827
|
+
className: "text-sm font-bold",
|
|
2828
|
+
style: { color: config.textColor },
|
|
2829
|
+
children: base.symbol
|
|
2830
|
+
}
|
|
2831
|
+
),
|
|
2832
|
+
/* @__PURE__ */ jsx12(
|
|
2833
|
+
"div",
|
|
2834
|
+
{
|
|
2835
|
+
className: "text-2xs",
|
|
2836
|
+
style: { color: config.textColor, opacity: 0.5 },
|
|
2837
|
+
children: base.name
|
|
2838
|
+
}
|
|
2839
|
+
)
|
|
2840
|
+
] })
|
|
2841
|
+
] }),
|
|
2842
|
+
/* @__PURE__ */ jsxs10("div", { className: "text-right", children: [
|
|
2843
|
+
/* @__PURE__ */ jsx12(
|
|
2844
|
+
"div",
|
|
2845
|
+
{
|
|
2846
|
+
className: "text-lg font-bold mono",
|
|
2847
|
+
style: { color: config.textColor },
|
|
2848
|
+
children: formattedBaseAmount
|
|
2849
|
+
}
|
|
2850
|
+
),
|
|
2851
|
+
base.price && /* @__PURE__ */ jsxs10(
|
|
2852
|
+
"div",
|
|
2853
|
+
{
|
|
2854
|
+
className: "text-2xs mono",
|
|
2855
|
+
style: { color: config.textColor, opacity: 0.5 },
|
|
2856
|
+
children: [
|
|
2857
|
+
"$",
|
|
2858
|
+
(base.price * baseAmount).toFixed(2)
|
|
2859
|
+
]
|
|
2860
|
+
}
|
|
2861
|
+
)
|
|
2862
|
+
] })
|
|
2863
|
+
]
|
|
2864
|
+
}
|
|
2865
|
+
),
|
|
2866
|
+
/* @__PURE__ */ jsx12("div", { className: "flex justify-center pb-0.5 pt-1", children: /* @__PURE__ */ jsx12(
|
|
2867
|
+
"div",
|
|
2868
|
+
{
|
|
2869
|
+
className: "flex h-6 w-6 items-center justify-center rounded-full border",
|
|
2870
|
+
style: {
|
|
2871
|
+
backgroundColor: config.cardBgColor,
|
|
2872
|
+
borderColor: config.cardBorderColor
|
|
2873
|
+
},
|
|
2874
|
+
children: /* @__PURE__ */ jsx12("span", { className: "text-base", style: { color: config.textColor }, children: "\u2193" })
|
|
2875
|
+
}
|
|
2876
|
+
) }),
|
|
2877
|
+
/* @__PURE__ */ jsxs10(
|
|
2878
|
+
"div",
|
|
2879
|
+
{
|
|
2880
|
+
className: "flex items-center justify-between backdrop-blur-sm p-2.5 border transition-all duration-300",
|
|
2881
|
+
style: {
|
|
2882
|
+
backgroundColor: config.cardBgColor,
|
|
2883
|
+
borderColor: config.cardBorderColor
|
|
2884
|
+
},
|
|
2885
|
+
children: [
|
|
2886
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2.5", children: [
|
|
2887
|
+
/* @__PURE__ */ jsx12(TokenImage_default, { asset: quote, size: "sm" }),
|
|
2888
|
+
/* @__PURE__ */ jsxs10("div", { children: [
|
|
2889
|
+
/* @__PURE__ */ jsx12(
|
|
2890
|
+
"div",
|
|
2891
|
+
{
|
|
2892
|
+
className: "text-sm font-bold",
|
|
2893
|
+
style: { color: config.textColor },
|
|
2894
|
+
children: quote.symbol
|
|
2895
|
+
}
|
|
2896
|
+
),
|
|
2897
|
+
/* @__PURE__ */ jsx12(
|
|
2898
|
+
"div",
|
|
2899
|
+
{
|
|
2900
|
+
className: "text-2xs",
|
|
2901
|
+
style: { color: config.textColor, opacity: 0.5 },
|
|
2902
|
+
children: quote.name
|
|
2903
|
+
}
|
|
2904
|
+
)
|
|
2905
|
+
] })
|
|
2906
|
+
] }),
|
|
2907
|
+
/* @__PURE__ */ jsxs10("div", { className: "text-right", children: [
|
|
2908
|
+
/* @__PURE__ */ jsx12(
|
|
2909
|
+
"div",
|
|
2910
|
+
{
|
|
2911
|
+
className: "text-lg font-bold mono",
|
|
2912
|
+
style: { color: config.textColor },
|
|
2913
|
+
children: formattedQuoteAmount
|
|
2914
|
+
}
|
|
2915
|
+
),
|
|
2916
|
+
quote.price && /* @__PURE__ */ jsxs10(
|
|
2917
|
+
"div",
|
|
2918
|
+
{
|
|
2919
|
+
className: "text-2xs mono",
|
|
2920
|
+
style: { color: config.textColor, opacity: 0.5 },
|
|
2921
|
+
children: [
|
|
2922
|
+
"$",
|
|
2923
|
+
(quote.price * quoteAmount).toFixed(2)
|
|
2924
|
+
]
|
|
2925
|
+
}
|
|
2926
|
+
)
|
|
2927
|
+
] })
|
|
2928
|
+
]
|
|
2929
|
+
}
|
|
2930
|
+
),
|
|
2931
|
+
isRecipientInputOpen && recipient && /* @__PURE__ */ jsxs10("div", { className: "mt-1.5", children: [
|
|
2932
|
+
/* @__PURE__ */ jsx12(
|
|
2933
|
+
"div",
|
|
2934
|
+
{
|
|
2935
|
+
className: "text-2xs mb-0.5 uppercase tracking-wider font-medium",
|
|
2936
|
+
style: { color: config.textColor, opacity: 0.5 },
|
|
2937
|
+
children: "Recipient"
|
|
2938
|
+
}
|
|
2939
|
+
),
|
|
2940
|
+
/* @__PURE__ */ jsx12(
|
|
2941
|
+
"div",
|
|
2942
|
+
{
|
|
2943
|
+
className: "flex items-center backdrop-blur-sm p-2 border transition-all duration-300",
|
|
2944
|
+
style: {
|
|
2945
|
+
backgroundColor: config.cardBgColor,
|
|
2946
|
+
borderColor: config.cardBorderColor
|
|
2947
|
+
},
|
|
2948
|
+
children: /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
|
|
2949
|
+
/* @__PURE__ */ jsx12(
|
|
2950
|
+
"div",
|
|
2951
|
+
{
|
|
2952
|
+
className: "flex h-5 w-5 items-center justify-center overflow-hidden rounded-full",
|
|
2953
|
+
style: { backgroundColor: config.iconBgColor },
|
|
2954
|
+
children: /* @__PURE__ */ jsx12("span", { className: "text-2xs", children: "\u{1F464}" })
|
|
2955
|
+
}
|
|
2956
|
+
),
|
|
2957
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex flex-col", children: [
|
|
2958
|
+
/* @__PURE__ */ jsx12(
|
|
2959
|
+
"div",
|
|
2960
|
+
{
|
|
2961
|
+
className: "text-2xs",
|
|
2962
|
+
style: { color: config.textColor, opacity: 0.5 },
|
|
2963
|
+
children: "To:"
|
|
2964
|
+
}
|
|
2965
|
+
),
|
|
2966
|
+
/* @__PURE__ */ jsxs10(
|
|
2967
|
+
"div",
|
|
2968
|
+
{
|
|
2969
|
+
className: "text-xs mono",
|
|
2970
|
+
style: { color: config.textColor },
|
|
2971
|
+
children: [
|
|
2972
|
+
recipient.slice(0, 6),
|
|
2973
|
+
"...",
|
|
2974
|
+
recipient.slice(-4)
|
|
2975
|
+
]
|
|
2976
|
+
}
|
|
2977
|
+
)
|
|
2978
|
+
] })
|
|
2979
|
+
] })
|
|
2980
|
+
}
|
|
2981
|
+
)
|
|
2982
|
+
] })
|
|
2983
|
+
] }) }),
|
|
2984
|
+
/* @__PURE__ */ jsxs10(
|
|
2985
|
+
"div",
|
|
2986
|
+
{
|
|
2987
|
+
className: "mt-3 pt-2.5 space-y-1.5",
|
|
2988
|
+
style: { borderTop: `1px solid ${config.cardBorderColor}` },
|
|
2989
|
+
children: [
|
|
2990
|
+
/* @__PURE__ */ jsxs10(
|
|
2991
|
+
"div",
|
|
2992
|
+
{
|
|
2993
|
+
role: "button",
|
|
2994
|
+
tabIndex: 0,
|
|
2995
|
+
onClick: handleCopyHash,
|
|
2996
|
+
onKeyDown: (e) => {
|
|
2997
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
2998
|
+
e.preventDefault();
|
|
2999
|
+
handleCopyHash();
|
|
3000
|
+
}
|
|
3001
|
+
},
|
|
3002
|
+
className: "flex items-center justify-between cursor-pointer group hover:opacity-80 transition-opacity",
|
|
3003
|
+
children: [
|
|
3004
|
+
/* @__PURE__ */ jsx12(
|
|
3005
|
+
"div",
|
|
3006
|
+
{
|
|
3007
|
+
className: "text-xs uppercase tracking-wider font-medium",
|
|
3008
|
+
style: { color: config.textColor, opacity: 0.5 },
|
|
3009
|
+
children: copied ? "Copied!" : status === "completed" || status === "failed" || status === "cancelled" ? "Order Hash" : status === "received" ? "Received" : "Pending"
|
|
3010
|
+
}
|
|
3011
|
+
),
|
|
3012
|
+
/* @__PURE__ */ jsxs10(
|
|
3013
|
+
"div",
|
|
3014
|
+
{
|
|
3015
|
+
className: "text-xs mono transition-colors",
|
|
3016
|
+
style: { color: config.textColor },
|
|
3017
|
+
children: [
|
|
3018
|
+
orderHash.slice(0, 6),
|
|
3019
|
+
"...",
|
|
3020
|
+
orderHash.slice(-4)
|
|
3021
|
+
]
|
|
3022
|
+
}
|
|
3023
|
+
)
|
|
3024
|
+
]
|
|
3025
|
+
}
|
|
3026
|
+
),
|
|
3027
|
+
status === "completed" && transactionSpeed ? /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between", children: [
|
|
3028
|
+
/* @__PURE__ */ jsx12(
|
|
3029
|
+
"div",
|
|
3030
|
+
{
|
|
3031
|
+
className: "text-xs uppercase tracking-wider font-medium",
|
|
3032
|
+
style: { color: config.textColor, opacity: 0.5 },
|
|
3033
|
+
children: "Speed"
|
|
3034
|
+
}
|
|
3035
|
+
),
|
|
3036
|
+
/* @__PURE__ */ jsxs10(
|
|
3037
|
+
"div",
|
|
3038
|
+
{
|
|
3039
|
+
className: "text-base font-bold mono",
|
|
3040
|
+
style: { color: config.textColor },
|
|
3041
|
+
children: [
|
|
3042
|
+
transactionSpeed.toFixed(2),
|
|
3043
|
+
"s"
|
|
3044
|
+
]
|
|
3045
|
+
}
|
|
3046
|
+
)
|
|
3047
|
+
] }) : /* @__PURE__ */ jsx12("div", { className: "h-5" })
|
|
3048
|
+
]
|
|
3049
|
+
}
|
|
3050
|
+
)
|
|
3051
|
+
] })
|
|
3052
|
+
}
|
|
3053
|
+
);
|
|
3054
|
+
};
|
|
3055
|
+
var TxStatusDisplay_default = TxStatusDisplay;
|
|
3056
|
+
|
|
3057
|
+
export {
|
|
3058
|
+
useBalanceEventListener,
|
|
3059
|
+
useEmitBalanceEvent,
|
|
3060
|
+
useBalanceEventSubscription,
|
|
3061
|
+
RfqProvider,
|
|
3062
|
+
useRfq,
|
|
3063
|
+
WalletModalContext,
|
|
3064
|
+
useWalletModal,
|
|
3065
|
+
AssetAmountInput_default,
|
|
3066
|
+
AssetSelection_default,
|
|
3067
|
+
RecipientForm_default,
|
|
3068
|
+
pollOrderStatus,
|
|
3069
|
+
useOrderStatusPolling,
|
|
3070
|
+
useTransactionRegistry,
|
|
3071
|
+
ChainSwitch,
|
|
3072
|
+
SignSwap,
|
|
3073
|
+
isQuoteFresh,
|
|
3074
|
+
canSubmitOrder,
|
|
3075
|
+
markOrderAsSubmitted,
|
|
3076
|
+
cleanupOldSubmissions,
|
|
3077
|
+
useUnwrapToken,
|
|
3078
|
+
useWrapToken,
|
|
3079
|
+
SwapButton,
|
|
3080
|
+
TxStatusDisplay_default
|
|
3081
|
+
};
|
|
3082
|
+
//# sourceMappingURL=chunk-ARMW5POL.js.map
|