@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.
Files changed (54) hide show
  1. package/dist/checkout-widget.es.js +25523 -24215
  2. package/dist/checkout-widget.es.js.map +1 -1
  3. package/dist/checkout-widget.umd.js +64 -59
  4. package/dist/checkout-widget.umd.js.map +1 -1
  5. package/dist/index.d.ts +5 -1
  6. package/package.json +1 -1
  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 +7 -1
  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 +412 -0
  31. package/src/components/steps/CardBuyFlow/ChooseAmountStep.tsx +352 -0
  32. package/src/components/steps/CardBuyFlow/OpenWidgetStep.tsx +193 -0
  33. package/src/components/steps/ExchangeFlow.tsx +254 -1416
  34. package/src/components/steps/FlowSelector.tsx +117 -60
  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 +92 -51
  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 +625 -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/types/index.ts +99 -0
  50. package/src/util/constants.tsx +27 -0
  51. package/src/util/enso-hooks.tsx +75 -61
  52. package/src/util/meld-hooks.tsx +533 -0
  53. package/src/assets/usdc.webp +0 -0
  54. 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
+ }