@ensofinance/checkout-widget 0.1.6 → 0.1.8
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/dist/checkout-widget.es.js +25523 -24215
- package/dist/checkout-widget.es.js.map +1 -1
- package/dist/checkout-widget.umd.js +64 -59
- package/dist/checkout-widget.umd.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/package.json +1 -1
- package/src/assets/providers/alchemypay.svg +21 -0
- package/src/assets/providers/banxa.svg +21 -0
- package/src/assets/providers/binanceconnect.svg +14 -0
- package/src/assets/providers/kryptonim.svg +6 -0
- package/src/assets/providers/mercuryo.svg +21 -0
- package/src/assets/providers/moonpay.svg +14 -0
- package/src/assets/providers/stripe.svg +16 -0
- package/src/assets/providers/swapped.svg +1 -0
- package/src/assets/providers/topper.svg +14 -0
- package/src/assets/providers/transak.svg +21 -0
- package/src/assets/providers/unlimit.svg +21 -0
- package/src/components/AmountInput.tsx +41 -25
- package/src/components/ChakraProvider.tsx +36 -13
- package/src/components/Checkout.tsx +7 -1
- package/src/components/CurrencySwapDisplay.tsx +59 -22
- package/src/components/DepositProcessing.tsx +1 -1
- package/src/components/ExchangeConfirmSecurity.tsx +1 -1
- package/src/components/QuoteParameters.tsx +1 -1
- package/src/components/TransactionDetailRow.tsx +2 -2
- package/src/components/cards/ExchangeCard.tsx +1 -1
- package/src/components/cards/OptionCard.tsx +2 -1
- package/src/components/cards/WalletCard.tsx +1 -1
- package/src/components/modal.tsx +3 -3
- package/src/components/steps/CardBuyFlow/CardBuyFlow.tsx +412 -0
- package/src/components/steps/CardBuyFlow/ChooseAmountStep.tsx +352 -0
- package/src/components/steps/CardBuyFlow/OpenWidgetStep.tsx +193 -0
- package/src/components/steps/ExchangeFlow.tsx +254 -1416
- package/src/components/steps/FlowSelector.tsx +117 -60
- package/src/components/steps/SmartAccountFlow.tsx +372 -0
- package/src/components/steps/WalletFlow/WalletAmountStep.tsx +2 -2
- package/src/components/steps/WalletFlow/WalletConfirmStep.tsx +92 -51
- package/src/components/steps/WalletFlow/WalletFlow.tsx +17 -16
- package/src/components/steps/WalletFlow/WalletQuoteStep.tsx +2 -2
- package/src/components/steps/WalletFlow/WalletTokenStep.tsx +6 -4
- package/src/components/steps/shared/ChooseAmountStep.tsx +325 -0
- package/src/components/steps/shared/SignUserOpStep.tsx +117 -0
- package/src/components/steps/shared/TrackUserOpStep.tsx +625 -0
- package/src/components/steps/shared/exchangeIntegration.ts +19 -0
- package/src/components/steps/shared/types.ts +22 -0
- package/src/components/ui/index.tsx +23 -6
- package/src/components/ui/toaster.tsx +2 -1
- package/src/components/ui/transitions.tsx +16 -0
- package/src/types/index.ts +99 -0
- package/src/util/constants.tsx +27 -0
- package/src/util/enso-hooks.tsx +75 -61
- package/src/util/meld-hooks.tsx +533 -0
- package/src/assets/usdc.webp +0 -0
- package/src/assets/usdt.webp +0 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { Box, Image, Table, Text } from "@chakra-ui/react";
|
|
3
|
+
|
|
4
|
+
import { Button } from "@/components/ui";
|
|
5
|
+
import { BodyWrapper } from "@/components/ui/styled";
|
|
6
|
+
import { CircleTimer } from "@/components/CircleTimer";
|
|
7
|
+
import QuoteParameters from "@/components/QuoteParameters";
|
|
8
|
+
import { TransactionDetailRow } from "@/components/TransactionDetailRow";
|
|
9
|
+
import { useAppDetails } from "@/util/enso-hooks";
|
|
10
|
+
import { useLayerZeroStatus } from "@/util/tx-tracker";
|
|
11
|
+
import { CHAINS_ETHERSCAN, STARGATE_CHAIN_NAMES } from "@/util/constants";
|
|
12
|
+
import { useAppStore } from "@/store";
|
|
13
|
+
|
|
14
|
+
import SuccessIcon from "@/assets/success.svg";
|
|
15
|
+
import FailIcon from "@/assets/fail.svg";
|
|
16
|
+
|
|
17
|
+
type OperationStatus = "sending" | "tracking" | "completed" | "failed";
|
|
18
|
+
|
|
19
|
+
type Props = {
|
|
20
|
+
userOp: any;
|
|
21
|
+
timerSeconds?: number;
|
|
22
|
+
pendingMessage?: string;
|
|
23
|
+
onReset?: () => void;
|
|
24
|
+
resetLabel?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const OPERATIONS_API_BASE =
|
|
28
|
+
"https://alpha-scanners-dev-054573dc8549.herokuapp.com";
|
|
29
|
+
|
|
30
|
+
// Phase indicator component for cross-chain tracking
|
|
31
|
+
const PhaseIndicator = ({
|
|
32
|
+
currentPhase,
|
|
33
|
+
phases,
|
|
34
|
+
}: {
|
|
35
|
+
currentPhase: number;
|
|
36
|
+
phases: string[];
|
|
37
|
+
}) => {
|
|
38
|
+
return (
|
|
39
|
+
<Box display="flex" gap={4} justifyContent="center" mb={4}>
|
|
40
|
+
{phases.map((phase, index) => (
|
|
41
|
+
<Box key={phase} display="flex" alignItems="center" gap={2}>
|
|
42
|
+
<Box
|
|
43
|
+
w={6}
|
|
44
|
+
h={6}
|
|
45
|
+
borderRadius="full"
|
|
46
|
+
bg={
|
|
47
|
+
index < currentPhase
|
|
48
|
+
? "green.500"
|
|
49
|
+
: index === currentPhase
|
|
50
|
+
? "blue.500"
|
|
51
|
+
: "gray.300"
|
|
52
|
+
}
|
|
53
|
+
display="flex"
|
|
54
|
+
alignItems="center"
|
|
55
|
+
justifyContent="center"
|
|
56
|
+
color="white"
|
|
57
|
+
fontSize="xs"
|
|
58
|
+
fontWeight="bold"
|
|
59
|
+
>
|
|
60
|
+
{index < currentPhase ? "✓" : index + 1}
|
|
61
|
+
</Box>
|
|
62
|
+
<Text
|
|
63
|
+
fontSize="sm"
|
|
64
|
+
color={index === currentPhase ? "fg" : "fg.muted"}
|
|
65
|
+
fontWeight={
|
|
66
|
+
index === currentPhase ? "semibold" : "normal"
|
|
67
|
+
}
|
|
68
|
+
>
|
|
69
|
+
{phase}
|
|
70
|
+
</Text>
|
|
71
|
+
</Box>
|
|
72
|
+
))}
|
|
73
|
+
</Box>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const TrackUserOpStep = ({
|
|
78
|
+
userOp,
|
|
79
|
+
timerSeconds,
|
|
80
|
+
pendingMessage,
|
|
81
|
+
onReset,
|
|
82
|
+
resetLabel,
|
|
83
|
+
}: Props) => {
|
|
84
|
+
const { chainIdIn, chainIdOut, tokenInData } = useAppDetails();
|
|
85
|
+
const amountIn = useAppStore((s) => s.amountIn);
|
|
86
|
+
|
|
87
|
+
const isCrosschain = useMemo(
|
|
88
|
+
() => !!chainIdIn && !!chainIdOut && chainIdIn !== chainIdOut,
|
|
89
|
+
[chainIdIn, chainIdOut],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Phase management: for crosschain 'cex' -> 'bridge' -> 'completed', for single-chain just 'cex' -> 'completed'
|
|
93
|
+
const [phase, setPhase] = useState<
|
|
94
|
+
"cex" | "bridge" | "completed" | "failed"
|
|
95
|
+
>("cex");
|
|
96
|
+
const [operationId, setOperationId] = useState<string | null>(null);
|
|
97
|
+
const [status, setStatus] = useState<OperationStatus>("sending");
|
|
98
|
+
const [message, setMessage] = useState("Sending operation to tracker...");
|
|
99
|
+
const [txHash, setTxHash] = useState<`0x${string}` | null>(null);
|
|
100
|
+
const [isTimerFinished, setIsTimerFinished] = useState(false);
|
|
101
|
+
const [destinationVerified, setDestinationVerified] = useState(false);
|
|
102
|
+
const [refundDetails, setRefundDetails] = useState<{
|
|
103
|
+
token: string;
|
|
104
|
+
amount: string;
|
|
105
|
+
recipient: string;
|
|
106
|
+
isNative: boolean;
|
|
107
|
+
} | null>(null);
|
|
108
|
+
const [destinationTxHash, setDestinationTxHash] = useState<string | null>(
|
|
109
|
+
null,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// LayerZero tracking for bridge progress (real-time updates)
|
|
113
|
+
const lzStatus = useLayerZeroStatus(
|
|
114
|
+
txHash ?? undefined,
|
|
115
|
+
isCrosschain && phase === "bridge",
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Submit UserOp to tracker
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (status !== "sending") return;
|
|
121
|
+
if (!userOp || !tokenInData || !chainIdIn || !amountIn) return;
|
|
122
|
+
|
|
123
|
+
const sendUserOpToTracker = async () => {
|
|
124
|
+
try {
|
|
125
|
+
const response = await fetch(
|
|
126
|
+
`${OPERATIONS_API_BASE}/operations`,
|
|
127
|
+
{
|
|
128
|
+
method: "POST",
|
|
129
|
+
headers: {
|
|
130
|
+
"Content-Type": "application/json",
|
|
131
|
+
},
|
|
132
|
+
body: JSON.stringify({
|
|
133
|
+
userOperationData: {
|
|
134
|
+
sender: userOp.sender,
|
|
135
|
+
nonce: userOp.nonce,
|
|
136
|
+
factory: userOp.factory,
|
|
137
|
+
factoryData: userOp.factoryData,
|
|
138
|
+
callData: userOp.callData,
|
|
139
|
+
callGasLimit: userOp.callGasLimit,
|
|
140
|
+
verificationGasLimit: userOp.verificationGasLimit,
|
|
141
|
+
preVerificationGas: userOp.preVerificationGas,
|
|
142
|
+
maxFeePerGas: userOp.maxFeePerGas,
|
|
143
|
+
maxPriorityFeePerGas: userOp.maxPriorityFeePerGas,
|
|
144
|
+
paymaster: userOp.paymaster,
|
|
145
|
+
paymasterData: userOp.paymasterData,
|
|
146
|
+
paymasterVerificationGasLimit:
|
|
147
|
+
userOp.paymasterVerificationGasLimit,
|
|
148
|
+
paymasterPostOpGasLimit:
|
|
149
|
+
userOp.paymasterPostOpGasLimit,
|
|
150
|
+
signature: userOp.signature,
|
|
151
|
+
},
|
|
152
|
+
chainId: chainIdIn,
|
|
153
|
+
expectedBalance: amountIn,
|
|
154
|
+
tokenAddress: tokenInData.address,
|
|
155
|
+
}),
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const data = await response.json();
|
|
160
|
+
|
|
161
|
+
if (data.success && data.operationId) {
|
|
162
|
+
setOperationId(data.operationId);
|
|
163
|
+
setStatus("tracking");
|
|
164
|
+
setMessage(
|
|
165
|
+
pendingMessage ??
|
|
166
|
+
(isCrosschain
|
|
167
|
+
? "Funds forwarding in progress..."
|
|
168
|
+
: "Tracking operation progress..."),
|
|
169
|
+
);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw new Error(
|
|
174
|
+
data.message || "Failed to send operation to tracker",
|
|
175
|
+
);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error("Failed to send operation to tracker:", error);
|
|
178
|
+
setStatus("failed");
|
|
179
|
+
setMessage("Failed to start tracking");
|
|
180
|
+
setPhase("failed");
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
sendUserOpToTracker();
|
|
185
|
+
}, [amountIn, chainIdIn, isCrosschain, pendingMessage, status, tokenInData, userOp]);
|
|
186
|
+
|
|
187
|
+
// Track operation status
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
if (!operationId || status !== "tracking") return;
|
|
190
|
+
|
|
191
|
+
const trackOperation = async () => {
|
|
192
|
+
try {
|
|
193
|
+
const response = await fetch(
|
|
194
|
+
`${OPERATIONS_API_BASE}/operations/${operationId}/status`,
|
|
195
|
+
);
|
|
196
|
+
const data = await response.json();
|
|
197
|
+
|
|
198
|
+
if (data.operation?.status === "completed") {
|
|
199
|
+
setStatus("completed");
|
|
200
|
+
|
|
201
|
+
if (isCrosschain) {
|
|
202
|
+
// For crosschain: move to bridge phase
|
|
203
|
+
setMessage("Funds forwarding completed!");
|
|
204
|
+
if (data.operation?.bundleTxHash) {
|
|
205
|
+
setTxHash(
|
|
206
|
+
data.operation.bundleTxHash as `0x${string}`,
|
|
207
|
+
);
|
|
208
|
+
setPhase("bridge");
|
|
209
|
+
} else {
|
|
210
|
+
console.warn(
|
|
211
|
+
"No bundleTxHash returned from indexer, cannot track bridge",
|
|
212
|
+
);
|
|
213
|
+
setPhase("completed");
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
// For single-chain: complete
|
|
217
|
+
setMessage("Operation completed successfully!");
|
|
218
|
+
setPhase("completed");
|
|
219
|
+
}
|
|
220
|
+
} else if (
|
|
221
|
+
["failed", "failed_permanent"].includes(data.operation?.status)
|
|
222
|
+
) {
|
|
223
|
+
setStatus("failed");
|
|
224
|
+
setMessage(
|
|
225
|
+
isCrosschain
|
|
226
|
+
? "Bridging failed. Please select Smart-account balance as a source to use withdrawn funds"
|
|
227
|
+
: "Operation failed",
|
|
228
|
+
);
|
|
229
|
+
setPhase("failed");
|
|
230
|
+
}
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error("Failed to fetch operation status:", error);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const interval = setInterval(trackOperation, 3000);
|
|
237
|
+
return () => clearInterval(interval);
|
|
238
|
+
}, [isCrosschain, operationId, status]);
|
|
239
|
+
|
|
240
|
+
// Handle bridge completion - verify destination execution once LayerZero shows DELIVERED
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
if (!isCrosschain || phase !== "bridge") return;
|
|
243
|
+
|
|
244
|
+
// If LayerZero failed, mark as failed immediately
|
|
245
|
+
if (lzStatus.isFailed) {
|
|
246
|
+
setPhase("failed");
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// When LayerZero shows DELIVERED, verify destination execution with Enso API
|
|
251
|
+
if (lzStatus.isComplete && !destinationVerified && txHash && chainIdIn) {
|
|
252
|
+
const verifyDestination = async () => {
|
|
253
|
+
try {
|
|
254
|
+
const res = await fetch(
|
|
255
|
+
`https://api.enso.build/api/v1/layerzero/bridge/check?chainId=${chainIdIn}&txHash=${txHash}`,
|
|
256
|
+
);
|
|
257
|
+
if (!res.ok) {
|
|
258
|
+
// If API call fails, assume success (LayerZero delivered)
|
|
259
|
+
setDestinationVerified(true);
|
|
260
|
+
setPhase("completed");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const data = await res.json();
|
|
264
|
+
setDestinationVerified(true);
|
|
265
|
+
setDestinationTxHash(data.destinationTxHash || null);
|
|
266
|
+
|
|
267
|
+
if (data.status === "success") {
|
|
268
|
+
setPhase("completed");
|
|
269
|
+
} else if (data.status === "failed") {
|
|
270
|
+
setRefundDetails(
|
|
271
|
+
data.ensoDestinationEvent?.refundDetails || null,
|
|
272
|
+
);
|
|
273
|
+
setPhase("failed");
|
|
274
|
+
} else {
|
|
275
|
+
// Still pending, assume success since LZ delivered
|
|
276
|
+
setPhase("completed");
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error("Failed to verify destination:", error);
|
|
280
|
+
// On error, assume success since LayerZero delivered
|
|
281
|
+
setDestinationVerified(true);
|
|
282
|
+
setPhase("completed");
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
verifyDestination();
|
|
286
|
+
}
|
|
287
|
+
}, [
|
|
288
|
+
chainIdIn,
|
|
289
|
+
destinationVerified,
|
|
290
|
+
isCrosschain,
|
|
291
|
+
lzStatus.isComplete,
|
|
292
|
+
lzStatus.isFailed,
|
|
293
|
+
phase,
|
|
294
|
+
txHash,
|
|
295
|
+
]);
|
|
296
|
+
|
|
297
|
+
const handleTimerFinish = () => {
|
|
298
|
+
setIsTimerFinished(true);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const getOverallStatus = () => {
|
|
302
|
+
if (phase === "failed") return "failed";
|
|
303
|
+
if (phase === "completed") return "completed";
|
|
304
|
+
return "processing";
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const getStatusColor = () => {
|
|
308
|
+
switch (getOverallStatus()) {
|
|
309
|
+
case "completed":
|
|
310
|
+
return "success";
|
|
311
|
+
case "failed":
|
|
312
|
+
return "error";
|
|
313
|
+
default:
|
|
314
|
+
return "fg";
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const getStatusText = () => {
|
|
319
|
+
switch (getOverallStatus()) {
|
|
320
|
+
case "completed":
|
|
321
|
+
return "Success";
|
|
322
|
+
case "failed":
|
|
323
|
+
return "Failed";
|
|
324
|
+
default:
|
|
325
|
+
return "Processing";
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const getCurrentMessage = () => {
|
|
330
|
+
if (isCrosschain) {
|
|
331
|
+
if (phase === "cex") {
|
|
332
|
+
return `(1/2) ${message}`;
|
|
333
|
+
} else if (phase === "bridge") {
|
|
334
|
+
return `(2/2) ${lzStatus.message}`;
|
|
335
|
+
} else if (phase === "completed") {
|
|
336
|
+
return "Transfer completed successfully!";
|
|
337
|
+
} else {
|
|
338
|
+
return refundDetails
|
|
339
|
+
? "Destination execution failed. Funds refunded to smart account."
|
|
340
|
+
: "Transfer failed";
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
if (phase === "completed") {
|
|
344
|
+
return "Operation completed successfully!";
|
|
345
|
+
} else if (phase === "failed") {
|
|
346
|
+
return "Operation failed";
|
|
347
|
+
}
|
|
348
|
+
return message;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const renderStatusIcon = () => {
|
|
353
|
+
const isProcessing = phase === "cex" || phase === "bridge";
|
|
354
|
+
|
|
355
|
+
if (isProcessing) {
|
|
356
|
+
const duration = timerSeconds ?? (isCrosschain ? 180 : 120);
|
|
357
|
+
return (
|
|
358
|
+
<CircleTimer
|
|
359
|
+
start={status === "tracking" || phase === "bridge"}
|
|
360
|
+
onFinish={handleTimerFinish}
|
|
361
|
+
duration={duration}
|
|
362
|
+
/>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (phase === "completed") {
|
|
367
|
+
return (
|
|
368
|
+
<Box display="flex" flexDirection="column" alignItems="center">
|
|
369
|
+
<Image
|
|
370
|
+
src={SuccessIcon}
|
|
371
|
+
boxShadow="0px 0px 20px var(--chakra-colors-success)"
|
|
372
|
+
borderRadius="90%"
|
|
373
|
+
width="58px"
|
|
374
|
+
height="58px"
|
|
375
|
+
/>
|
|
376
|
+
</Box>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
<Box display="flex" flexDirection="column" alignItems="center">
|
|
382
|
+
<Image
|
|
383
|
+
src={FailIcon}
|
|
384
|
+
boxShadow="0px 0px 20px var(--chakra-colors-error)"
|
|
385
|
+
borderRadius="90%"
|
|
386
|
+
width="58px"
|
|
387
|
+
height="58px"
|
|
388
|
+
/>
|
|
389
|
+
</Box>
|
|
390
|
+
);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const intermediateChainName = chainIdIn
|
|
394
|
+
? STARGATE_CHAIN_NAMES[chainIdIn as keyof typeof STARGATE_CHAIN_NAMES]
|
|
395
|
+
: "Unknown";
|
|
396
|
+
const targetChainName = chainIdOut
|
|
397
|
+
? STARGATE_CHAIN_NAMES[chainIdOut as keyof typeof STARGATE_CHAIN_NAMES]
|
|
398
|
+
: "Unknown";
|
|
399
|
+
|
|
400
|
+
const canReset = phase === "completed" || phase === "failed";
|
|
401
|
+
const finalResetLabel =
|
|
402
|
+
resetLabel ?? (phase === "completed" ? "New Deposit" : "Retry Deposit");
|
|
403
|
+
|
|
404
|
+
return (
|
|
405
|
+
<BodyWrapper>
|
|
406
|
+
<Box display="flex" flexDirection="column" gap={4}>
|
|
407
|
+
{/* Phase Indicator (crosschain only) */}
|
|
408
|
+
{isCrosschain && (
|
|
409
|
+
<PhaseIndicator
|
|
410
|
+
currentPhase={
|
|
411
|
+
phase === "cex" ? 0 : phase === "bridge" ? 1 : 2
|
|
412
|
+
}
|
|
413
|
+
phases={["Forward funds", "Bridge"]}
|
|
414
|
+
/>
|
|
415
|
+
)}
|
|
416
|
+
|
|
417
|
+
{/* Status Icon */}
|
|
418
|
+
<Box
|
|
419
|
+
display="flex"
|
|
420
|
+
flexDirection="column"
|
|
421
|
+
paddingBottom="16px"
|
|
422
|
+
alignItems="center"
|
|
423
|
+
width="100%"
|
|
424
|
+
>
|
|
425
|
+
{renderStatusIcon()}
|
|
426
|
+
<Box
|
|
427
|
+
display="flex"
|
|
428
|
+
flexDirection="column"
|
|
429
|
+
alignItems="center"
|
|
430
|
+
marginTop="16px"
|
|
431
|
+
textAlign="center"
|
|
432
|
+
>
|
|
433
|
+
<Text
|
|
434
|
+
fontSize="lg"
|
|
435
|
+
fontWeight="semibold"
|
|
436
|
+
color="fg"
|
|
437
|
+
marginBottom="8px"
|
|
438
|
+
>
|
|
439
|
+
{getCurrentMessage()}
|
|
440
|
+
</Text>
|
|
441
|
+
{(phase === "cex" || phase === "bridge") &&
|
|
442
|
+
isTimerFinished && (
|
|
443
|
+
<Text
|
|
444
|
+
fontSize="sm"
|
|
445
|
+
color="fg.muted"
|
|
446
|
+
maxWidth="280px"
|
|
447
|
+
>
|
|
448
|
+
Your operation is being processed – no action is
|
|
449
|
+
required from you.
|
|
450
|
+
</Text>
|
|
451
|
+
)}
|
|
452
|
+
</Box>
|
|
453
|
+
</Box>
|
|
454
|
+
|
|
455
|
+
{/* Status Table */}
|
|
456
|
+
<Table.Root key="status" size="sm" variant="outline" width="100%">
|
|
457
|
+
<Table.Body>
|
|
458
|
+
<Table.Row>
|
|
459
|
+
<Table.Cell>Status</Table.Cell>
|
|
460
|
+
<Table.Cell
|
|
461
|
+
display="flex"
|
|
462
|
+
textAlign="end"
|
|
463
|
+
justifyContent="end"
|
|
464
|
+
>
|
|
465
|
+
<Text color={getStatusColor()}>
|
|
466
|
+
{getStatusText()}
|
|
467
|
+
</Text>
|
|
468
|
+
</Table.Cell>
|
|
469
|
+
</Table.Row>
|
|
470
|
+
{isCrosschain && (
|
|
471
|
+
<>
|
|
472
|
+
<Table.Row>
|
|
473
|
+
<Table.Cell>Current Phase</Table.Cell>
|
|
474
|
+
<Table.Cell
|
|
475
|
+
display="flex"
|
|
476
|
+
textAlign="end"
|
|
477
|
+
justifyContent="end"
|
|
478
|
+
>
|
|
479
|
+
<Text>
|
|
480
|
+
{phase === "cex"
|
|
481
|
+
? "Awaiting funds"
|
|
482
|
+
: phase === "bridge"
|
|
483
|
+
? "Bridging"
|
|
484
|
+
: phase === "completed"
|
|
485
|
+
? "Complete"
|
|
486
|
+
: "Failed"}
|
|
487
|
+
</Text>
|
|
488
|
+
</Table.Cell>
|
|
489
|
+
</Table.Row>
|
|
490
|
+
<Table.Row>
|
|
491
|
+
<Table.Cell>Intermediate Chain</Table.Cell>
|
|
492
|
+
<Table.Cell
|
|
493
|
+
display="flex"
|
|
494
|
+
textAlign="end"
|
|
495
|
+
justifyContent="end"
|
|
496
|
+
>
|
|
497
|
+
<Text textTransform="capitalize">
|
|
498
|
+
{intermediateChainName}
|
|
499
|
+
</Text>
|
|
500
|
+
</Table.Cell>
|
|
501
|
+
</Table.Row>
|
|
502
|
+
<Table.Row>
|
|
503
|
+
<Table.Cell>Final Destination</Table.Cell>
|
|
504
|
+
<Table.Cell
|
|
505
|
+
display="flex"
|
|
506
|
+
textAlign="end"
|
|
507
|
+
justifyContent="end"
|
|
508
|
+
>
|
|
509
|
+
<Text textTransform="capitalize">
|
|
510
|
+
{targetChainName}
|
|
511
|
+
</Text>
|
|
512
|
+
</Table.Cell>
|
|
513
|
+
</Table.Row>
|
|
514
|
+
</>
|
|
515
|
+
)}
|
|
516
|
+
{operationId && (
|
|
517
|
+
<Table.Row>
|
|
518
|
+
<Table.Cell>Operation ID</Table.Cell>
|
|
519
|
+
<Table.Cell
|
|
520
|
+
display="flex"
|
|
521
|
+
textAlign="end"
|
|
522
|
+
justifyContent="end"
|
|
523
|
+
>
|
|
524
|
+
<Text fontSize="sm" color="fg.muted">
|
|
525
|
+
{operationId}
|
|
526
|
+
</Text>
|
|
527
|
+
</Table.Cell>
|
|
528
|
+
</Table.Row>
|
|
529
|
+
)}
|
|
530
|
+
{isCrosschain && txHash && (
|
|
531
|
+
<Table.Row>
|
|
532
|
+
<Table.Cell>Bridge TX</Table.Cell>
|
|
533
|
+
<Table.Cell
|
|
534
|
+
display="flex"
|
|
535
|
+
textAlign="end"
|
|
536
|
+
justifyContent="end"
|
|
537
|
+
>
|
|
538
|
+
<Text
|
|
539
|
+
fontSize="sm"
|
|
540
|
+
color="blue.500"
|
|
541
|
+
cursor="pointer"
|
|
542
|
+
onClick={() =>
|
|
543
|
+
window.open(
|
|
544
|
+
`https://layerzeroscan.com/tx/${txHash}`,
|
|
545
|
+
"_blank",
|
|
546
|
+
)
|
|
547
|
+
}
|
|
548
|
+
>
|
|
549
|
+
View on LayerZero
|
|
550
|
+
</Text>
|
|
551
|
+
</Table.Cell>
|
|
552
|
+
</Table.Row>
|
|
553
|
+
)}
|
|
554
|
+
{isCrosschain && destinationTxHash && (
|
|
555
|
+
<Table.Row>
|
|
556
|
+
<Table.Cell>Destination TX</Table.Cell>
|
|
557
|
+
<Table.Cell
|
|
558
|
+
display="flex"
|
|
559
|
+
textAlign="end"
|
|
560
|
+
justifyContent="end"
|
|
561
|
+
>
|
|
562
|
+
<Text
|
|
563
|
+
fontSize="sm"
|
|
564
|
+
color="blue.500"
|
|
565
|
+
cursor="pointer"
|
|
566
|
+
onClick={() => {
|
|
567
|
+
const explorer =
|
|
568
|
+
CHAINS_ETHERSCAN[
|
|
569
|
+
chainIdOut as keyof typeof CHAINS_ETHERSCAN
|
|
570
|
+
] || "https://etherscan.io";
|
|
571
|
+
window.open(
|
|
572
|
+
`${explorer}/tx/${destinationTxHash}`,
|
|
573
|
+
"_blank",
|
|
574
|
+
);
|
|
575
|
+
}}
|
|
576
|
+
>
|
|
577
|
+
View on Explorer
|
|
578
|
+
</Text>
|
|
579
|
+
</Table.Cell>
|
|
580
|
+
</Table.Row>
|
|
581
|
+
)}
|
|
582
|
+
{isCrosschain && phase === "bridge" && (
|
|
583
|
+
<Table.Row>
|
|
584
|
+
<Table.Cell>Bridge Progress</Table.Cell>
|
|
585
|
+
<Table.Cell
|
|
586
|
+
display="flex"
|
|
587
|
+
textAlign="end"
|
|
588
|
+
justifyContent="end"
|
|
589
|
+
>
|
|
590
|
+
<Text>({lzStatus.step}/4)</Text>
|
|
591
|
+
</Table.Cell>
|
|
592
|
+
</Table.Row>
|
|
593
|
+
)}
|
|
594
|
+
{isCrosschain && refundDetails && (
|
|
595
|
+
<Table.Row>
|
|
596
|
+
<Table.Cell>Refund</Table.Cell>
|
|
597
|
+
<Table.Cell
|
|
598
|
+
display="flex"
|
|
599
|
+
textAlign="end"
|
|
600
|
+
justifyContent="end"
|
|
601
|
+
>
|
|
602
|
+
<Text fontSize="sm" color="orange.500">
|
|
603
|
+
Funds refunded to smart account
|
|
604
|
+
</Text>
|
|
605
|
+
</Table.Cell>
|
|
606
|
+
</Table.Row>
|
|
607
|
+
)}
|
|
608
|
+
</Table.Body>
|
|
609
|
+
</Table.Root>
|
|
610
|
+
|
|
611
|
+
<QuoteParameters />
|
|
612
|
+
|
|
613
|
+
<TransactionDetailRow />
|
|
614
|
+
|
|
615
|
+
{canReset && onReset && (
|
|
616
|
+
<Button onClick={onReset} visual="solid">
|
|
617
|
+
{finalResetLabel}
|
|
618
|
+
</Button>
|
|
619
|
+
)}
|
|
620
|
+
</Box>
|
|
621
|
+
</BodyWrapper>
|
|
622
|
+
);
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
export default TrackUserOpStep;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SupportedExchanges } from "@/types";
|
|
2
|
+
|
|
3
|
+
export const ExchangeToIntegrationType: Record<SupportedExchanges, string> = {
|
|
4
|
+
[SupportedExchanges.Binance]: "binanceInternationalDirect",
|
|
5
|
+
[SupportedExchanges.Kraken]: "krakenDirect",
|
|
6
|
+
[SupportedExchanges.Coinbase]: "coinbase",
|
|
7
|
+
[SupportedExchanges.Bybit]: "bybitDirect",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const EXCHANGE_ICON_BY_TYPE: Record<string, string> = {
|
|
11
|
+
[ExchangeToIntegrationType[SupportedExchanges.Binance]]:
|
|
12
|
+
"https://assets.coingecko.com/markets/images/52/large/binance.jpg",
|
|
13
|
+
[ExchangeToIntegrationType[SupportedExchanges.Kraken]]:
|
|
14
|
+
"https://assets.coingecko.com/markets/images/29/large/kraken.jpg",
|
|
15
|
+
[ExchangeToIntegrationType[SupportedExchanges.Coinbase]]:
|
|
16
|
+
"https://assets.coingecko.com/markets/images/23/large/Coinbase_Coin_Primary.png",
|
|
17
|
+
[ExchangeToIntegrationType[SupportedExchanges.Bybit]]:
|
|
18
|
+
"https://assets.coingecko.com/markets/images/698/large/bybit_spot.png",
|
|
19
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface CryptocurrencyPosition {
|
|
2
|
+
marketValue: number;
|
|
3
|
+
lastPrice: number;
|
|
4
|
+
name: string;
|
|
5
|
+
symbol: string;
|
|
6
|
+
amount: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SupportedToken {
|
|
10
|
+
symbol: string;
|
|
11
|
+
name: string;
|
|
12
|
+
networkId: string;
|
|
13
|
+
chainId: number;
|
|
14
|
+
integrationNetworks: any[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface MatchedToken extends SupportedToken {
|
|
18
|
+
balance: number;
|
|
19
|
+
marketValue: number;
|
|
20
|
+
tokenAddress?: string;
|
|
21
|
+
holding?: CryptocurrencyPosition;
|
|
22
|
+
}
|