@ensofinance/checkout-widget 0.1.9 → 1.0.1

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