@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
|
@@ -5,14 +5,17 @@ import {
|
|
|
5
5
|
Icon,
|
|
6
6
|
Text,
|
|
7
7
|
Flex,
|
|
8
|
-
Skeleton,
|
|
9
|
-
Image,
|
|
10
|
-
Table,
|
|
11
8
|
} from "@chakra-ui/react";
|
|
12
9
|
import { ChevronLeft, X, TriangleAlert } from "lucide-react";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
import {
|
|
11
|
+
useContext,
|
|
12
|
+
useEffect,
|
|
13
|
+
useMemo,
|
|
14
|
+
useState,
|
|
15
|
+
useCallback,
|
|
16
|
+
} from "react";
|
|
17
|
+
import { useQuery } from "@tanstack/react-query";
|
|
18
|
+
import { useAccount } from "wagmi";
|
|
16
19
|
import {
|
|
17
20
|
BodyWrapper,
|
|
18
21
|
HeaderDescription,
|
|
@@ -20,10 +23,13 @@ import {
|
|
|
20
23
|
HeaderWrapper,
|
|
21
24
|
ListWrapper,
|
|
22
25
|
} from "../ui/styled";
|
|
23
|
-
import { IconButton, Button
|
|
24
|
-
import { AmountInput, AmountInputValue } from "../AmountInput";
|
|
26
|
+
import { IconButton, Button } from "../ui";
|
|
25
27
|
import { CheckoutContext } from "../Checkout";
|
|
26
28
|
import Modal from "../modal";
|
|
29
|
+
import TrackUserOpStep from "@/components/steps/shared/TrackUserOpStep";
|
|
30
|
+
import SharedChooseAmountStep from "@/components/steps/shared/ChooseAmountStep";
|
|
31
|
+
import SharedSignUserOpStep from "@/components/steps/shared/SignUserOpStep";
|
|
32
|
+
import { AnimatedStep } from "../ui/transitions";
|
|
27
33
|
import {
|
|
28
34
|
AccessTokenPayload,
|
|
29
35
|
createLink,
|
|
@@ -32,69 +38,26 @@ import {
|
|
|
32
38
|
import { useAppStore } from "@/store";
|
|
33
39
|
import { AssetCard } from "../cards";
|
|
34
40
|
import {
|
|
35
|
-
denormalizeValue,
|
|
36
41
|
formatNumber,
|
|
37
42
|
formatUSD,
|
|
38
43
|
normalizeValue,
|
|
39
44
|
} from "@/util";
|
|
45
|
+
import { useTokenFromListBySymbols } from "@/util/common";
|
|
40
46
|
import {
|
|
41
|
-
useTokenFromListBySymbols,
|
|
42
|
-
precisionizeNumber,
|
|
43
|
-
getPositiveDecimalValue,
|
|
44
|
-
} from "@/util/common";
|
|
45
|
-
import {
|
|
46
|
-
EXCHANGE_MAX_LIMIT_GAP_USD,
|
|
47
|
-
EXCHANGE_MIN_LIMIT,
|
|
48
47
|
getCexIntermediateChain,
|
|
49
48
|
DEFAULT_CEX_BRIDGE_CHAIN_MAPPING,
|
|
49
|
+
CHECKOUT_BFF_URL,
|
|
50
50
|
} from "@/util/constants";
|
|
51
|
-
import {
|
|
52
|
-
useAppDetails,
|
|
53
|
-
useRouteData,
|
|
54
|
-
useSmartAccountBalances,
|
|
55
|
-
} from "@/util/enso-hooks";
|
|
56
|
-
import QuoteParameters from "../QuoteParameters";
|
|
57
|
-
import { TransactionDetailRow } from "../TransactionDetailRow";
|
|
58
|
-
import { CircleTimer } from "../CircleTimer";
|
|
51
|
+
import { useAppDetails } from "@/util/enso-hooks";
|
|
59
52
|
import { ConfirmExchangeStep } from "../ExchangeConfirmSecurity";
|
|
53
|
+
import {
|
|
54
|
+
ExchangeToIntegrationType,
|
|
55
|
+
EXCHANGE_ICON_BY_TYPE,
|
|
56
|
+
} from "./shared/exchangeIntegration";
|
|
57
|
+
import type { MatchedToken, CryptocurrencyPosition, SupportedToken } from "./shared/types";
|
|
60
58
|
|
|
61
|
-
import SuccessIcon from "@/assets/success.svg";
|
|
62
|
-
import FailIcon from "@/assets/fail.svg";
|
|
63
|
-
import { SupportedExchanges } from "../../types";
|
|
64
|
-
import { useLayerZeroStatus } from "@/util/tx-tracker";
|
|
65
|
-
import { STARGATE_CHAIN_NAMES, CHAINS_ETHERSCAN } from "@/util/constants";
|
|
66
|
-
|
|
67
|
-
const ENTRY_POINT_ADDRESS: `0x${string}` =
|
|
68
|
-
"0x0000000071727de22e5e9d8baf0edac6f37da032";
|
|
69
|
-
|
|
70
|
-
export const ExchangeToIntegrationType: Record<SupportedExchanges, string> = {
|
|
71
|
-
[SupportedExchanges.Binance]: "binanceInternationalDirect",
|
|
72
|
-
[SupportedExchanges.Kraken]: "krakenDirect",
|
|
73
|
-
[SupportedExchanges.Coinbase]: "coinbase",
|
|
74
|
-
[SupportedExchanges.Bybit]: "bybitDirect",
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
// Map Mesh broker types to icon URLs
|
|
78
|
-
export const EXCHANGE_ICON_BY_TYPE: Record<string, string> = {
|
|
79
|
-
[ExchangeToIntegrationType[SupportedExchanges.Binance]]:
|
|
80
|
-
"https://assets.coingecko.com/markets/images/52/large/binance.jpg",
|
|
81
|
-
[ExchangeToIntegrationType[SupportedExchanges.Kraken]]:
|
|
82
|
-
"https://assets.coingecko.com/markets/images/29/large/kraken.jpg",
|
|
83
|
-
[ExchangeToIntegrationType[SupportedExchanges.Coinbase]]:
|
|
84
|
-
"https://assets.coingecko.com/markets/images/23/large/Coinbase_Coin_Primary.png",
|
|
85
|
-
[ExchangeToIntegrationType[SupportedExchanges.Bybit]]:
|
|
86
|
-
"https://assets.coingecko.com/markets/images/698/large/bybit_spot.png",
|
|
87
|
-
};
|
|
88
59
|
|
|
89
60
|
// Types for Mesh Holdings API response
|
|
90
|
-
interface CryptocurrencyPosition {
|
|
91
|
-
marketValue: number;
|
|
92
|
-
lastPrice: number;
|
|
93
|
-
name: string;
|
|
94
|
-
symbol: string;
|
|
95
|
-
amount: number;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
61
|
interface HoldingsContent {
|
|
99
62
|
equityPositions: any[];
|
|
100
63
|
cryptocurrencyPositions: CryptocurrencyPosition[];
|
|
@@ -119,59 +82,38 @@ interface HoldingsResponse {
|
|
|
119
82
|
errorType: string;
|
|
120
83
|
}
|
|
121
84
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
networkId: string;
|
|
126
|
-
chainId: number;
|
|
127
|
-
integrationNetworks: any[];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
interface MatchedToken extends SupportedToken {
|
|
131
|
-
balance: number;
|
|
132
|
-
marketValue: number;
|
|
133
|
-
holding?: CryptocurrencyPosition;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const isDelayedBalanceUsed = (integrationType: string) =>
|
|
137
|
-
integrationType === "delayed";
|
|
138
|
-
|
|
139
|
-
// const MESH_API_URL = "http://localhost:8787";
|
|
140
|
-
const MESH_API_URL = "https://mesh-bff.enso-checkout.workers.dev";
|
|
85
|
+
type MeshRequestError = Error & {
|
|
86
|
+
code?: string;
|
|
87
|
+
};
|
|
141
88
|
|
|
142
89
|
/*
|
|
143
90
|
Withdrawal steps:
|
|
144
|
-
1. Check if session key is available
|
|
145
|
-
2. Perform auth if not
|
|
91
|
+
1. Check if session key is available
|
|
92
|
+
2. Perform auth if not available (optional)
|
|
146
93
|
3. Get holdings and show token selector
|
|
147
94
|
4. Select amount
|
|
148
|
-
|
|
149
|
-
|
|
95
|
+
5. Get userOp signature
|
|
96
|
+
6. Open transfer modal with amount and token
|
|
150
97
|
*/
|
|
151
98
|
|
|
152
99
|
export enum WithdrawalStep {
|
|
153
|
-
CheckSessionKey,
|
|
154
100
|
ChooseExchange,
|
|
101
|
+
CheckSessionKey,
|
|
155
102
|
ChooseExchangeAsset,
|
|
156
|
-
ChooseBalanceAsset,
|
|
157
103
|
ChooseAmount,
|
|
158
104
|
SignUserOp,
|
|
159
105
|
InitiateWithdrawal,
|
|
160
106
|
TrackUserOp,
|
|
161
107
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
]
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
WithdrawalStep.ChooseAmount,
|
|
172
|
-
WithdrawalStep.SignUserOp,
|
|
173
|
-
];
|
|
174
|
-
// Integration details are fetched dynamically from Mesh API.
|
|
108
|
+
|
|
109
|
+
const withdrawalPreviousStep: Partial<Record<WithdrawalStep, WithdrawalStep>> =
|
|
110
|
+
{
|
|
111
|
+
[WithdrawalStep.CheckSessionKey]: WithdrawalStep.ChooseExchange,
|
|
112
|
+
[WithdrawalStep.ChooseExchangeAsset]: WithdrawalStep.ChooseExchange,
|
|
113
|
+
[WithdrawalStep.ChooseAmount]: WithdrawalStep.ChooseExchangeAsset,
|
|
114
|
+
[WithdrawalStep.SignUserOp]: WithdrawalStep.ChooseAmount,
|
|
115
|
+
[WithdrawalStep.InitiateWithdrawal]: WithdrawalStep.SignUserOp,
|
|
116
|
+
};
|
|
175
117
|
|
|
176
118
|
// Mesh network IDs for EVM chains (from Mesh networks API)
|
|
177
119
|
const MESH_NETWORK_IDS: { [chainId: number]: string } = {
|
|
@@ -183,8 +125,6 @@ const MESH_NETWORK_IDS: { [chainId: number]: string } = {
|
|
|
183
125
|
43114: "bad16371-c22a-4bf4-a311-274d046cd760", // Avalanche C-Chain
|
|
184
126
|
56: "ed0ebeec-b166-4c8b-8574-cb078f7af8cf", // BSC
|
|
185
127
|
146: "385f0b3a-8471-4b8f-884f-c4f4496f1603", // Sonic
|
|
186
|
-
// 81457: "0c17e03f-77fa-4644-b84c-eb247af8c4c1", // Blast
|
|
187
|
-
// 11155111: "03b2d786-7092-4a6a-9737-d6013e21819b", // Sepolia (testnet)
|
|
188
128
|
};
|
|
189
129
|
|
|
190
130
|
const MESH_NETWORKS = Object.keys(MESH_NETWORK_IDS).map(Number);
|
|
@@ -193,6 +133,20 @@ const getNetworkId = (chainId: number): string => {
|
|
|
193
133
|
return MESH_NETWORK_IDS[chainId] || MESH_NETWORK_IDS[8453]; // Default to Base
|
|
194
134
|
};
|
|
195
135
|
|
|
136
|
+
const DEVICE_ID_KEY = "meshDeviceId";
|
|
137
|
+
const useDeviceId = () => {
|
|
138
|
+
return useMemo(() => {
|
|
139
|
+
let deviceId = localStorage.getItem(DEVICE_ID_KEY);
|
|
140
|
+
|
|
141
|
+
if (!deviceId) {
|
|
142
|
+
deviceId = `device_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
143
|
+
localStorage.setItem(DEVICE_ID_KEY, deviceId);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return deviceId;
|
|
147
|
+
}, []);
|
|
148
|
+
};
|
|
149
|
+
|
|
196
150
|
const useHandleMeshAccessPayload = () => {
|
|
197
151
|
const { setMeshAccessToken } = useAppStore();
|
|
198
152
|
const deviceKey = useDeviceId();
|
|
@@ -200,12 +154,12 @@ const useHandleMeshAccessPayload = () => {
|
|
|
200
154
|
|
|
201
155
|
return useCallback(
|
|
202
156
|
(accessTokenPayload: AccessTokenPayload, sessionId: string) => {
|
|
203
|
-
setMeshAccessToken(accessTokenPayload);
|
|
157
|
+
setMeshAccessToken(accessTokenPayload);
|
|
204
158
|
|
|
205
159
|
sessionStorage.setItem(
|
|
206
160
|
`${deviceKey}:${selectedIntegration?.type}`,
|
|
207
161
|
JSON.stringify({
|
|
208
|
-
accessTokenPayload,
|
|
162
|
+
accessTokenPayload,
|
|
209
163
|
sessionId,
|
|
210
164
|
timestamp: Date.now(),
|
|
211
165
|
}),
|
|
@@ -217,7 +171,7 @@ const useHandleMeshAccessPayload = () => {
|
|
|
217
171
|
|
|
218
172
|
type MeshIntegration = {
|
|
219
173
|
id: string;
|
|
220
|
-
type: string;
|
|
174
|
+
type: string;
|
|
221
175
|
name: string;
|
|
222
176
|
networks?: {
|
|
223
177
|
id: string;
|
|
@@ -233,9 +187,6 @@ const ChooseExchangeStep = ({
|
|
|
233
187
|
setStep: (step: WithdrawalStep) => void;
|
|
234
188
|
}) => {
|
|
235
189
|
const { chainIdOut, setChainIdIn } = useAppStore();
|
|
236
|
-
const [integrations, setIntegrations] = useState<MeshIntegration[]>([]);
|
|
237
|
-
const [loading, setLoading] = useState(true);
|
|
238
|
-
const [error, setError] = useState<string | null>(null);
|
|
239
190
|
const setSelectedIntegration = useAppStore(
|
|
240
191
|
(state) => state.setSelectedIntegration,
|
|
241
192
|
);
|
|
@@ -244,42 +195,50 @@ const ChooseExchangeStep = ({
|
|
|
244
195
|
const cexMapping =
|
|
245
196
|
cexBridgeChainMapping ?? DEFAULT_CEX_BRIDGE_CHAIN_MAPPING;
|
|
246
197
|
|
|
247
|
-
// Use intermediate chain for filtering if target chain needs bridging
|
|
248
198
|
const effectiveChainId =
|
|
249
199
|
getCexIntermediateChain(chainIdOut, cexMapping) ?? chainIdOut;
|
|
250
200
|
|
|
251
|
-
|
|
201
|
+
const availableExchanges = useMemo(
|
|
202
|
+
() =>
|
|
203
|
+
(enableExchange ?? [])
|
|
204
|
+
.map((exchange) => ExchangeToIntegrationType[exchange])
|
|
205
|
+
.filter(Boolean),
|
|
206
|
+
[enableExchange],
|
|
207
|
+
);
|
|
208
|
+
|
|
252
209
|
useEffect(() => {
|
|
253
210
|
effectiveChainId ? setChainIdIn(effectiveChainId) : chainIdOut;
|
|
254
211
|
}, [effectiveChainId, setChainIdIn]);
|
|
255
212
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
setIntegrations(filtered);
|
|
274
|
-
} catch (e) {
|
|
275
|
-
console.error("Failed to fetch integrations", e);
|
|
276
|
-
setError("Failed to load exchanges");
|
|
277
|
-
} finally {
|
|
278
|
-
setLoading(false);
|
|
213
|
+
const {
|
|
214
|
+
data: integrations = [],
|
|
215
|
+
isLoading: loading,
|
|
216
|
+
error,
|
|
217
|
+
} = useQuery({
|
|
218
|
+
queryKey: [
|
|
219
|
+
"mesh-integrations",
|
|
220
|
+
effectiveChainId,
|
|
221
|
+
...availableExchanges,
|
|
222
|
+
],
|
|
223
|
+
queryFn: async (): Promise<MeshIntegration[]> => {
|
|
224
|
+
if (!effectiveChainId || availableExchanges.length === 0) return [];
|
|
225
|
+
|
|
226
|
+
const res = await fetch(`${CHECKOUT_BFF_URL}/integrations`);
|
|
227
|
+
if (!res.ok) {
|
|
228
|
+
throw new Error("Failed to load exchanges");
|
|
279
229
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
230
|
+
|
|
231
|
+
const data = (await res.json()) as MeshIntegration[];
|
|
232
|
+
|
|
233
|
+
return (data ?? []).filter(
|
|
234
|
+
(integration) =>
|
|
235
|
+
integration.networks?.some(
|
|
236
|
+
(network) => +network.chainId === effectiveChainId,
|
|
237
|
+
) && availableExchanges.includes(integration.type),
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
staleTime: 60_000,
|
|
241
|
+
});
|
|
283
242
|
|
|
284
243
|
if (loading)
|
|
285
244
|
return (
|
|
@@ -291,23 +250,18 @@ const ChooseExchangeStep = ({
|
|
|
291
250
|
return (
|
|
292
251
|
<BodyWrapper>
|
|
293
252
|
<Box p={5} color="red.500">
|
|
294
|
-
|
|
253
|
+
Failed to load exchanges
|
|
295
254
|
</Box>
|
|
296
255
|
</BodyWrapper>
|
|
297
256
|
);
|
|
298
257
|
|
|
299
258
|
return (
|
|
300
259
|
<BodyWrapper>
|
|
301
|
-
{/*<Box mb={4} width="100%" textAlign="left">*/}
|
|
302
|
-
{/* <HeaderTitle>Choose Exchange</HeaderTitle>*/}
|
|
303
|
-
{/*</Box>*/}
|
|
304
|
-
|
|
305
260
|
{integrations?.length > 0 ? (
|
|
306
261
|
<ListWrapper>
|
|
307
262
|
{integrations.map((integration) => (
|
|
308
263
|
<AssetCard
|
|
309
264
|
key={integration.id}
|
|
310
|
-
// chainId={chainIdOut || 1}
|
|
311
265
|
icon={EXCHANGE_ICON_BY_TYPE[integration.type]}
|
|
312
266
|
title={integration.name}
|
|
313
267
|
balance={""}
|
|
@@ -352,26 +306,20 @@ const CheckSessionKeyStep = ({
|
|
|
352
306
|
const [showConfirmation, setShowConfirmation] = useState(false);
|
|
353
307
|
const selectedIntegration = useAppStore((s) => s.selectedIntegration);
|
|
354
308
|
|
|
355
|
-
// Bridging is required if chainIdIn differs from chainIdOut
|
|
356
309
|
const needsBridging = chainIdIn !== chainIdOut;
|
|
357
|
-
|
|
358
|
-
// Invalid only if chain is not in MESH_NETWORKS AND cannot be bridged
|
|
359
310
|
const invalidChainId =
|
|
360
311
|
chainIdOut && !MESH_NETWORKS.includes(chainIdOut) && !needsBridging;
|
|
361
312
|
const handleMeshAccessPayload = useHandleMeshAccessPayload();
|
|
362
313
|
|
|
363
314
|
useEffect(() => {
|
|
364
315
|
if (!selectedIntegration) {
|
|
365
|
-
// ensure an exchange is selected
|
|
366
316
|
setStep(WithdrawalStep.ChooseExchange);
|
|
367
317
|
return;
|
|
368
318
|
}
|
|
369
319
|
if (invalidChainId) return;
|
|
370
|
-
// If connection is persisted, skip fetching a new link token
|
|
371
320
|
const saved = sessionStorage.getItem(
|
|
372
321
|
`${deviceKey}:${selectedIntegration.type}`,
|
|
373
322
|
);
|
|
374
|
-
// On load: check for persisted connection and hydrate state
|
|
375
323
|
if (saved) {
|
|
376
324
|
try {
|
|
377
325
|
const parsed = JSON.parse(saved);
|
|
@@ -382,19 +330,17 @@ const CheckSessionKeyStep = ({
|
|
|
382
330
|
return;
|
|
383
331
|
}
|
|
384
332
|
} catch (e) {
|
|
385
|
-
// ignore malformed storage
|
|
386
333
|
console.error("Failed to parse saved Mesh connection", e);
|
|
387
334
|
}
|
|
388
335
|
}
|
|
389
336
|
|
|
390
|
-
// Show confirmation instead of auto-connecting
|
|
391
337
|
setShowConfirmation(true);
|
|
392
338
|
}, [deviceKey, invalidChainId, selectedIntegration]);
|
|
393
339
|
|
|
394
340
|
const handleConfirmAuth = () => {
|
|
395
341
|
const brokerType = selectedIntegration?.type;
|
|
396
342
|
fetch(
|
|
397
|
-
`${
|
|
343
|
+
`${CHECKOUT_BFF_URL}/linktoken?brokerType=${encodeURIComponent(brokerType)}`,
|
|
398
344
|
{
|
|
399
345
|
method: "POST",
|
|
400
346
|
headers: {
|
|
@@ -420,7 +366,7 @@ const CheckSessionKeyStep = ({
|
|
|
420
366
|
handleMeshAccessPayload(
|
|
421
367
|
payload.accessToken,
|
|
422
368
|
response.content.sessionId,
|
|
423
|
-
);
|
|
369
|
+
);
|
|
424
370
|
},
|
|
425
371
|
onExit: (error) => {
|
|
426
372
|
console.log("onExit", error);
|
|
@@ -477,21 +423,6 @@ const CheckSessionKeyStep = ({
|
|
|
477
423
|
return <Spinner m={5} />;
|
|
478
424
|
};
|
|
479
425
|
|
|
480
|
-
const DEVICE_ID_KEY = "meshDeviceId";
|
|
481
|
-
// Generate a unique device ID to use as user id for Mesh
|
|
482
|
-
const useDeviceId = () => {
|
|
483
|
-
return useMemo(() => {
|
|
484
|
-
let deviceId = localStorage.getItem(DEVICE_ID_KEY);
|
|
485
|
-
|
|
486
|
-
if (!deviceId) {
|
|
487
|
-
deviceId = `device_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
488
|
-
localStorage.setItem(DEVICE_ID_KEY, deviceId);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
return deviceId;
|
|
492
|
-
}, []);
|
|
493
|
-
};
|
|
494
|
-
|
|
495
426
|
const ChooseAssetStep = ({
|
|
496
427
|
setStep,
|
|
497
428
|
onTokenSelect,
|
|
@@ -499,17 +430,9 @@ const ChooseAssetStep = ({
|
|
|
499
430
|
setStep: (step: WithdrawalStep) => void;
|
|
500
431
|
onTokenSelect: (token: MatchedToken) => void;
|
|
501
432
|
}) => {
|
|
502
|
-
// const [holdings, setHoldings] = useState<CryptocurrencyPosition[]>([]);
|
|
503
|
-
// const [supportedTokens, setSupportedTokens] = useState<SupportedToken[]>(
|
|
504
|
-
// [],
|
|
505
|
-
// );
|
|
506
|
-
const [matchedTokens, setMatchedTokens] = useState<MatchedToken[]>([]);
|
|
507
433
|
const [selectedTokenSymbol, setSelectedTokenSymbol] = useState<
|
|
508
434
|
string | null
|
|
509
435
|
>(null);
|
|
510
|
-
const [loading, setLoading] = useState(true);
|
|
511
|
-
const [error, setError] = useState<string | null>(null);
|
|
512
|
-
const { address } = useAccount();
|
|
513
436
|
const {
|
|
514
437
|
meshAccessToken,
|
|
515
438
|
sessionId,
|
|
@@ -522,108 +445,115 @@ const ChooseAssetStep = ({
|
|
|
522
445
|
|
|
523
446
|
const selectedIntegration = useAppStore((s) => s.selectedIntegration);
|
|
524
447
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
448
|
+
const {
|
|
449
|
+
data: matchedTokens = [],
|
|
450
|
+
isLoading: loading,
|
|
451
|
+
error,
|
|
452
|
+
} = useQuery({
|
|
453
|
+
queryKey: [
|
|
454
|
+
"mesh-matched-tokens",
|
|
455
|
+
sessionId,
|
|
456
|
+
chainIdIn,
|
|
457
|
+
selectedIntegration?.type,
|
|
458
|
+
meshAccessToken?.accountTokens?.[0]?.accessToken,
|
|
459
|
+
],
|
|
460
|
+
queryFn: async (): Promise<MatchedToken[]> => {
|
|
461
|
+
const holdingsResponse = await fetch(
|
|
462
|
+
`${CHECKOUT_BFF_URL}/holdings`,
|
|
463
|
+
{
|
|
464
|
+
method: "POST",
|
|
465
|
+
headers: {
|
|
466
|
+
"Content-Type": "application/json",
|
|
467
|
+
"x-session-id": sessionId || "",
|
|
543
468
|
},
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
holdingsData.message || "Failed to fetch holdings",
|
|
565
|
-
);
|
|
469
|
+
body: JSON.stringify({
|
|
470
|
+
authToken:
|
|
471
|
+
meshAccessToken?.accountTokens?.[0]?.accessToken,
|
|
472
|
+
brokerType: selectedIntegration?.type,
|
|
473
|
+
}),
|
|
474
|
+
},
|
|
475
|
+
);
|
|
476
|
+
const holdingsData =
|
|
477
|
+
(await holdingsResponse.json()) as HoldingsResponse;
|
|
478
|
+
console.log("Holdings data:", holdingsData);
|
|
479
|
+
|
|
480
|
+
if (
|
|
481
|
+
holdingsData.status !== "ok" ||
|
|
482
|
+
!holdingsData.content?.cryptocurrencyPositions
|
|
483
|
+
) {
|
|
484
|
+
const meshError = new Error(
|
|
485
|
+
holdingsData.message || "Failed to fetch holdings",
|
|
486
|
+
) as MeshRequestError;
|
|
487
|
+
if (holdingsData.errorType === "invalidIntegrationToken") {
|
|
488
|
+
meshError.code = "invalidIntegrationToken";
|
|
566
489
|
}
|
|
490
|
+
throw meshError;
|
|
491
|
+
}
|
|
567
492
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
// Match holdings with supported tokens
|
|
581
|
-
const matched = tokensData.content.tokens
|
|
582
|
-
.map((token: SupportedToken) => {
|
|
583
|
-
const holding =
|
|
584
|
-
holdingsData.content.cryptocurrencyPositions.find(
|
|
585
|
-
(h: CryptocurrencyPosition) =>
|
|
586
|
-
h.symbol === token.symbol,
|
|
587
|
-
);
|
|
588
|
-
if (holding) {
|
|
589
|
-
return {
|
|
590
|
-
...token,
|
|
591
|
-
balance: holding.amount,
|
|
592
|
-
marketValue: holding.marketValue,
|
|
593
|
-
holding: holding,
|
|
594
|
-
} as MatchedToken;
|
|
595
|
-
}
|
|
596
|
-
return null;
|
|
597
|
-
})
|
|
598
|
-
.filter(
|
|
599
|
-
(token): token is MatchedToken =>
|
|
600
|
-
token !== null && token.marketValue > 5,
|
|
601
|
-
)
|
|
602
|
-
.sort((a, b) => b.marketValue - a.marketValue);
|
|
603
|
-
|
|
604
|
-
console.log("Matched tokens with balances:", matched);
|
|
605
|
-
setMatchedTokens(matched);
|
|
606
|
-
} else {
|
|
607
|
-
throw new Error(
|
|
608
|
-
tokensData.message ||
|
|
609
|
-
"Failed to fetch supported tokens",
|
|
610
|
-
);
|
|
611
|
-
}
|
|
612
|
-
} catch (err) {
|
|
613
|
-
debugger;
|
|
614
|
-
console.error("Error fetching data:", err);
|
|
615
|
-
setError(
|
|
616
|
-
err instanceof Error ? err.message : "Failed to fetch data",
|
|
493
|
+
const tokensResponse = await fetch(
|
|
494
|
+
`${CHECKOUT_BFF_URL}/tokens?chainId=${chainIdIn}&brokerType=${encodeURIComponent(selectedIntegration?.type || "")}`,
|
|
495
|
+
);
|
|
496
|
+
const tokensData = await tokensResponse.json();
|
|
497
|
+
|
|
498
|
+
if (
|
|
499
|
+
tokensData.status !== "success" ||
|
|
500
|
+
!tokensData.content?.tokens
|
|
501
|
+
) {
|
|
502
|
+
throw new Error(
|
|
503
|
+
tokensData.message || "Failed to fetch supported tokens",
|
|
617
504
|
);
|
|
618
|
-
} finally {
|
|
619
|
-
setLoading(false);
|
|
620
505
|
}
|
|
621
|
-
};
|
|
622
506
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
507
|
+
const matched = tokensData.content.tokens
|
|
508
|
+
.map((token: SupportedToken) => {
|
|
509
|
+
const holding =
|
|
510
|
+
holdingsData.content.cryptocurrencyPositions.find(
|
|
511
|
+
(h: CryptocurrencyPosition) =>
|
|
512
|
+
h.symbol === token.symbol,
|
|
513
|
+
);
|
|
514
|
+
if (holding) {
|
|
515
|
+
return {
|
|
516
|
+
...token,
|
|
517
|
+
balance: holding.amount,
|
|
518
|
+
marketValue: holding.marketValue,
|
|
519
|
+
holding: holding,
|
|
520
|
+
} as MatchedToken;
|
|
521
|
+
}
|
|
522
|
+
return null;
|
|
523
|
+
})
|
|
524
|
+
.filter(
|
|
525
|
+
(token): token is MatchedToken =>
|
|
526
|
+
token !== null && token.marketValue > 5,
|
|
527
|
+
)
|
|
528
|
+
.sort((a, b) => b.marketValue - a.marketValue);
|
|
529
|
+
|
|
530
|
+
console.log("Matched tokens with balances:", matched);
|
|
531
|
+
return matched;
|
|
532
|
+
},
|
|
533
|
+
enabled: Boolean(
|
|
534
|
+
meshAccessToken && sessionId && chainIdIn && selectedIntegration,
|
|
535
|
+
),
|
|
536
|
+
retry: 0,
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
useEffect(() => {
|
|
540
|
+
if (!(error instanceof Error)) return;
|
|
541
|
+
const meshError = error as MeshRequestError;
|
|
542
|
+
if (meshError.code !== "invalidIntegrationToken") return;
|
|
543
|
+
|
|
544
|
+
console.log("Invalid integration token");
|
|
545
|
+
setStep(WithdrawalStep.CheckSessionKey);
|
|
546
|
+
setMeshAccessToken(null);
|
|
547
|
+
setSessionId(null);
|
|
548
|
+
sessionStorage.removeItem(`${deviceKey}:${selectedIntegration?.type}`);
|
|
549
|
+
}, [
|
|
550
|
+
error,
|
|
551
|
+
setStep,
|
|
552
|
+
setMeshAccessToken,
|
|
553
|
+
setSessionId,
|
|
554
|
+
deviceKey,
|
|
555
|
+
selectedIntegration?.type,
|
|
556
|
+
]);
|
|
627
557
|
|
|
628
558
|
const geckoTokens = useTokenFromListBySymbols(
|
|
629
559
|
matchedTokens.map((token) => token.symbol),
|
|
@@ -639,7 +569,10 @@ const ChooseAssetStep = ({
|
|
|
639
569
|
if (error)
|
|
640
570
|
return (
|
|
641
571
|
<Box p={5} color="red.500">
|
|
642
|
-
Error:
|
|
572
|
+
Error:{" "}
|
|
573
|
+
{error instanceof Error
|
|
574
|
+
? error.message
|
|
575
|
+
: "Failed to fetch data"}
|
|
643
576
|
</Box>
|
|
644
577
|
);
|
|
645
578
|
|
|
@@ -668,19 +601,24 @@ const ChooseAssetStep = ({
|
|
|
668
601
|
loading={false}
|
|
669
602
|
selected={selectedTokenSymbol === token.symbol}
|
|
670
603
|
onClick={() => {
|
|
604
|
+
const tokenAddress =
|
|
605
|
+
geckoTokens?.[index]?.address;
|
|
671
606
|
setSelectedTokenSymbol(token.symbol);
|
|
672
|
-
onTokenSelect(
|
|
673
|
-
|
|
607
|
+
onTokenSelect({
|
|
608
|
+
...token,
|
|
609
|
+
tokenAddress,
|
|
610
|
+
});
|
|
611
|
+
setTokenIn(tokenAddress);
|
|
674
612
|
}}
|
|
675
613
|
/>
|
|
676
614
|
))}
|
|
677
615
|
</ListWrapper>
|
|
678
616
|
</Box>
|
|
679
|
-
{matchedTokens.length === 0
|
|
617
|
+
{matchedTokens.length === 0 ? (
|
|
680
618
|
<Box textAlign="center" color="fg.subtle" py={8}>
|
|
681
619
|
No tokens with balances found for this chain
|
|
682
620
|
</Box>
|
|
683
|
-
)}
|
|
621
|
+
) : null}
|
|
684
622
|
{
|
|
685
623
|
<Button
|
|
686
624
|
disabled={!selectedTokenSymbol}
|
|
@@ -695,441 +633,6 @@ const ChooseAssetStep = ({
|
|
|
695
633
|
);
|
|
696
634
|
};
|
|
697
635
|
|
|
698
|
-
const ChooseDelayedBalance = ({
|
|
699
|
-
setStep,
|
|
700
|
-
onTokenSelect,
|
|
701
|
-
}: {
|
|
702
|
-
setStep: (step: WithdrawalStep) => void;
|
|
703
|
-
onTokenSelect: (token: MatchedToken) => void;
|
|
704
|
-
}) => {
|
|
705
|
-
const { chainIdIn, setTokenIn, setChainIdIn } = useAppStore();
|
|
706
|
-
const [selectedToken, setSelectedToken] = useState<string | null>(null);
|
|
707
|
-
|
|
708
|
-
// Get smart account balances
|
|
709
|
-
const { holdingsList, total, isLoading } = useSmartAccountBalances(1);
|
|
710
|
-
const setSelectedIntegration = useAppStore(
|
|
711
|
-
(state) => state.setSelectedIntegration,
|
|
712
|
-
);
|
|
713
|
-
|
|
714
|
-
useEffect(() => {
|
|
715
|
-
setSelectedIntegration({
|
|
716
|
-
type: "delayed",
|
|
717
|
-
name: "Smart account",
|
|
718
|
-
id: "",
|
|
719
|
-
});
|
|
720
|
-
}, []);
|
|
721
|
-
|
|
722
|
-
if (isLoading) {
|
|
723
|
-
return (
|
|
724
|
-
<Center>
|
|
725
|
-
<Spinner m={5} />
|
|
726
|
-
</Center>
|
|
727
|
-
);
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
return (
|
|
731
|
-
<BodyWrapper>
|
|
732
|
-
<Box mb={4} width="100%" textAlign="left">
|
|
733
|
-
<HeaderDescription>
|
|
734
|
-
Smart Account Balance: {formatUSD(total)}
|
|
735
|
-
</HeaderDescription>
|
|
736
|
-
</Box>
|
|
737
|
-
<Box overflowY={"scroll"} maxH={"400px"}>
|
|
738
|
-
<ListWrapper>
|
|
739
|
-
{holdingsList?.map((asset) => (
|
|
740
|
-
<AssetCard
|
|
741
|
-
key={`${asset.token}-${asset.chainId}`}
|
|
742
|
-
chainId={asset.chainId}
|
|
743
|
-
icon={asset.logoUri}
|
|
744
|
-
title={asset.name}
|
|
745
|
-
balance={`${formatNumber(
|
|
746
|
-
normalizeValue(asset.amount, asset.decimals),
|
|
747
|
-
)} ${asset.symbol}`}
|
|
748
|
-
usdBalance={formatUSD(asset.total)}
|
|
749
|
-
tag=""
|
|
750
|
-
loading={false}
|
|
751
|
-
selected={
|
|
752
|
-
selectedToken === asset.token &&
|
|
753
|
-
chainIdIn === asset.chainId
|
|
754
|
-
}
|
|
755
|
-
onClick={() => {
|
|
756
|
-
setSelectedToken(asset.token);
|
|
757
|
-
setTokenIn(asset.token);
|
|
758
|
-
setChainIdIn(asset.chainId);
|
|
759
|
-
// Mock MatchedToken from balance data
|
|
760
|
-
const mockMatchedToken: MatchedToken = {
|
|
761
|
-
symbol: asset.symbol,
|
|
762
|
-
name: asset.name,
|
|
763
|
-
networkId: asset.chainId.toString(),
|
|
764
|
-
chainId: asset.chainId,
|
|
765
|
-
integrationNetworks: [],
|
|
766
|
-
balance: Number(
|
|
767
|
-
normalizeValue(
|
|
768
|
-
asset.amount,
|
|
769
|
-
asset.decimals,
|
|
770
|
-
),
|
|
771
|
-
),
|
|
772
|
-
marketValue: asset.total,
|
|
773
|
-
};
|
|
774
|
-
onTokenSelect(mockMatchedToken);
|
|
775
|
-
}}
|
|
776
|
-
/>
|
|
777
|
-
))}
|
|
778
|
-
</ListWrapper>
|
|
779
|
-
</Box>
|
|
780
|
-
{holdingsList?.length === 0 && (
|
|
781
|
-
<Box textAlign="center" color="fg.subtle" py={8}>
|
|
782
|
-
No tokens found in smart account
|
|
783
|
-
</Box>
|
|
784
|
-
)}
|
|
785
|
-
<Button
|
|
786
|
-
disabled={!selectedToken}
|
|
787
|
-
onClick={() => {
|
|
788
|
-
setStep(WithdrawalStep.ChooseAmount);
|
|
789
|
-
}}
|
|
790
|
-
>
|
|
791
|
-
Continue
|
|
792
|
-
</Button>
|
|
793
|
-
</BodyWrapper>
|
|
794
|
-
);
|
|
795
|
-
};
|
|
796
|
-
|
|
797
|
-
const ChooseAmountStep = ({
|
|
798
|
-
setStep,
|
|
799
|
-
selectedToken,
|
|
800
|
-
}: {
|
|
801
|
-
setStep: (step: WithdrawalStep) => void;
|
|
802
|
-
selectedToken: MatchedToken | null;
|
|
803
|
-
}) => {
|
|
804
|
-
const [amountInput, setAmountInput] = useState<AmountInputValue>({
|
|
805
|
-
tokenAmount: "",
|
|
806
|
-
usdAmount: "",
|
|
807
|
-
mode: "usd",
|
|
808
|
-
});
|
|
809
|
-
const setAmountIn = useAppStore((s) => s.setAmountIn);
|
|
810
|
-
const { tokenInData, selectedIntegration } = useAppDetails();
|
|
811
|
-
const isStable = selectedToken?.symbol.toLowerCase().includes("USD");
|
|
812
|
-
const roundingPrecision = isStable ? 2 : 6;
|
|
813
|
-
const amount = amountInput.tokenAmount;
|
|
814
|
-
const usdValue = amountInput.usdAmount;
|
|
815
|
-
|
|
816
|
-
// Only apply CEX withdrawal limits if using a CEX holding (not smart account)
|
|
817
|
-
const isWithdrawal = !isDelayedBalanceUsed(selectedIntegration.type);
|
|
818
|
-
|
|
819
|
-
const maxUsdAmount = selectedToken
|
|
820
|
-
? isWithdrawal
|
|
821
|
-
? (selectedToken.marketValue - EXCHANGE_MAX_LIMIT_GAP_USD).toFixed(
|
|
822
|
-
2,
|
|
823
|
-
)
|
|
824
|
-
: selectedToken.marketValue.toFixed(2)
|
|
825
|
-
: 0;
|
|
826
|
-
|
|
827
|
-
const tokenPriceUsd = useMemo(() => {
|
|
828
|
-
if (!selectedToken || !selectedToken.balance) return undefined;
|
|
829
|
-
return selectedToken.marketValue / selectedToken.balance;
|
|
830
|
-
}, [selectedToken]);
|
|
831
|
-
|
|
832
|
-
// Handle percentage selection with limits (only for CEX withdrawals)
|
|
833
|
-
const getPercentAmounts = useCallback(
|
|
834
|
-
(percent: number) => {
|
|
835
|
-
if (!selectedToken) return;
|
|
836
|
-
|
|
837
|
-
// Calculate target USD amount based on percentage
|
|
838
|
-
const targetUsdAmount = (selectedToken.marketValue * percent) / 100;
|
|
839
|
-
|
|
840
|
-
let finalUsdAmount: number;
|
|
841
|
-
let finalTokenAmount: number;
|
|
842
|
-
|
|
843
|
-
if (isWithdrawal) {
|
|
844
|
-
// Apply limits for CEX withdrawals
|
|
845
|
-
const minValueForToken =
|
|
846
|
-
EXCHANGE_MIN_LIMIT[
|
|
847
|
-
selectedToken.symbol as keyof typeof EXCHANGE_MIN_LIMIT
|
|
848
|
-
] || 0;
|
|
849
|
-
|
|
850
|
-
finalUsdAmount = Math.max(
|
|
851
|
-
minValueForToken,
|
|
852
|
-
Math.min(targetUsdAmount, +maxUsdAmount),
|
|
853
|
-
);
|
|
854
|
-
|
|
855
|
-
const tokenPrice =
|
|
856
|
-
selectedToken.marketValue / selectedToken.balance;
|
|
857
|
-
finalTokenAmount = Math.min(
|
|
858
|
-
finalUsdAmount / tokenPrice,
|
|
859
|
-
selectedToken.balance,
|
|
860
|
-
);
|
|
861
|
-
} else {
|
|
862
|
-
// No limits for smart account balances
|
|
863
|
-
finalUsdAmount = targetUsdAmount;
|
|
864
|
-
finalTokenAmount = (selectedToken.balance * percent) / 100;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
return {
|
|
868
|
-
tokenAmount: precisionizeNumber(
|
|
869
|
-
finalTokenAmount,
|
|
870
|
-
roundingPrecision,
|
|
871
|
-
),
|
|
872
|
-
usdAmount: finalUsdAmount.toFixed(2),
|
|
873
|
-
};
|
|
874
|
-
},
|
|
875
|
-
[selectedToken, isWithdrawal, maxUsdAmount, roundingPrecision],
|
|
876
|
-
);
|
|
877
|
-
|
|
878
|
-
// Set max value on load
|
|
879
|
-
useEffect(() => {
|
|
880
|
-
if (selectedToken) {
|
|
881
|
-
const percentAmounts = getPercentAmounts(100);
|
|
882
|
-
if (!percentAmounts) return;
|
|
883
|
-
|
|
884
|
-
setAmountInput((prev) => ({
|
|
885
|
-
...prev,
|
|
886
|
-
...percentAmounts,
|
|
887
|
-
}));
|
|
888
|
-
}
|
|
889
|
-
}, [selectedToken, getPercentAmounts]);
|
|
890
|
-
|
|
891
|
-
useEffect(() => {
|
|
892
|
-
if (!tokenInData?.decimals) return;
|
|
893
|
-
|
|
894
|
-
const normalizedAmount = amount.endsWith(".")
|
|
895
|
-
? amount.slice(0, -1)
|
|
896
|
-
: amount;
|
|
897
|
-
|
|
898
|
-
if (!normalizedAmount || normalizedAmount === ".") {
|
|
899
|
-
setAmountIn("0");
|
|
900
|
-
return;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
try {
|
|
904
|
-
setAmountIn(denormalizeValue(normalizedAmount, tokenInData.decimals));
|
|
905
|
-
} catch (error) {
|
|
906
|
-
setAmountIn("0");
|
|
907
|
-
}
|
|
908
|
-
}, [amount, tokenInData?.decimals, setAmountIn]);
|
|
909
|
-
|
|
910
|
-
const numericAmount = getPositiveDecimalValue(amount);
|
|
911
|
-
const hasPositiveAmount = numericAmount !== null;
|
|
912
|
-
const hasUsdValue = !!usdValue && usdValue !== ".";
|
|
913
|
-
const notEnoughBalance = selectedToken
|
|
914
|
-
? hasPositiveAmount &&
|
|
915
|
-
numericAmount !== null &&
|
|
916
|
-
numericAmount > selectedToken.balance
|
|
917
|
-
: true;
|
|
918
|
-
|
|
919
|
-
// Limits validation logic - only for CEX withdrawals
|
|
920
|
-
const currentUsdValue = hasUsdValue
|
|
921
|
-
? getPositiveDecimalValue(usdValue) ?? 0
|
|
922
|
-
: 0;
|
|
923
|
-
const minValueForToken =
|
|
924
|
-
isWithdrawal && selectedToken
|
|
925
|
-
? EXCHANGE_MIN_LIMIT[
|
|
926
|
-
selectedToken.symbol as keyof typeof EXCHANGE_MIN_LIMIT
|
|
927
|
-
]
|
|
928
|
-
: 0;
|
|
929
|
-
|
|
930
|
-
const isBelowMinAmount =
|
|
931
|
-
isWithdrawal &&
|
|
932
|
-
selectedToken &&
|
|
933
|
-
hasPositiveAmount &&
|
|
934
|
-
currentUsdValue > 0 &&
|
|
935
|
-
minValueForToken &&
|
|
936
|
-
numericAmount !== null &&
|
|
937
|
-
numericAmount < minValueForToken;
|
|
938
|
-
const isAboveMaxAmount =
|
|
939
|
-
isWithdrawal &&
|
|
940
|
-
selectedToken &&
|
|
941
|
-
hasPositiveAmount &&
|
|
942
|
-
currentUsdValue > 0 &&
|
|
943
|
-
currentUsdValue > +maxUsdAmount;
|
|
944
|
-
|
|
945
|
-
const isAmountInvalid =
|
|
946
|
-
!hasPositiveAmount ||
|
|
947
|
-
isBelowMinAmount ||
|
|
948
|
-
isAboveMaxAmount ||
|
|
949
|
-
notEnoughBalance;
|
|
950
|
-
|
|
951
|
-
if (!selectedToken) {
|
|
952
|
-
return (
|
|
953
|
-
<BodyWrapper>
|
|
954
|
-
<Box textAlign="center" color="fg.subtle" py={8}>
|
|
955
|
-
No token selected
|
|
956
|
-
</Box>
|
|
957
|
-
</BodyWrapper>
|
|
958
|
-
);
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
return (
|
|
962
|
-
<BodyWrapper>
|
|
963
|
-
<Box mb={4} width="100%" textAlign="left">
|
|
964
|
-
<HeaderTitle>Enter Amount</HeaderTitle>
|
|
965
|
-
<HeaderDescription>
|
|
966
|
-
Available: {formatNumber(selectedToken.balance)}{" "}
|
|
967
|
-
{selectedToken.symbol} (
|
|
968
|
-
{formatUSD(selectedToken.marketValue)})
|
|
969
|
-
</HeaderDescription>
|
|
970
|
-
</Box>
|
|
971
|
-
|
|
972
|
-
<Box
|
|
973
|
-
display={"flex"}
|
|
974
|
-
flexDirection={"column"}
|
|
975
|
-
gap={"8px"}
|
|
976
|
-
width="100%"
|
|
977
|
-
>
|
|
978
|
-
<AmountInput
|
|
979
|
-
value={amountInput}
|
|
980
|
-
onChange={setAmountInput}
|
|
981
|
-
tokenSymbol={selectedToken.symbol}
|
|
982
|
-
tokenPriceUsd={tokenPriceUsd}
|
|
983
|
-
roundingPrecision={roundingPrecision}
|
|
984
|
-
onPercentSelect={getPercentAmounts}
|
|
985
|
-
/>
|
|
986
|
-
</Box>
|
|
987
|
-
|
|
988
|
-
{
|
|
989
|
-
<Box
|
|
990
|
-
textAlign="center"
|
|
991
|
-
color="fg.subtle"
|
|
992
|
-
fontSize="xs"
|
|
993
|
-
h={3}
|
|
994
|
-
m={-1}
|
|
995
|
-
visibility={
|
|
996
|
-
isAmountInvalid ? "visible" : "hidden"
|
|
997
|
-
}
|
|
998
|
-
>
|
|
999
|
-
{!hasPositiveAmount
|
|
1000
|
-
? "Please enter an amount"
|
|
1001
|
-
: isBelowMinAmount
|
|
1002
|
-
? `Minimum amount is ${formatNumber(minValueForToken)} ${selectedToken.symbol}`
|
|
1003
|
-
: isAboveMaxAmount
|
|
1004
|
-
? `Maximum amount is ${formatUSD(maxUsdAmount)} (balance - $${EXCHANGE_MAX_LIMIT_GAP_USD})`
|
|
1005
|
-
: notEnoughBalance
|
|
1006
|
-
? "Amount exceeds available balance"
|
|
1007
|
-
: ""}
|
|
1008
|
-
</Box>
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
<Button
|
|
1012
|
-
onClick={() =>
|
|
1013
|
-
isAmountInvalid
|
|
1014
|
-
? undefined
|
|
1015
|
-
: setStep(WithdrawalStep.SignUserOp)
|
|
1016
|
-
}
|
|
1017
|
-
disabled={isAmountInvalid}
|
|
1018
|
-
>
|
|
1019
|
-
Continue
|
|
1020
|
-
</Button>
|
|
1021
|
-
</BodyWrapper>
|
|
1022
|
-
);
|
|
1023
|
-
};
|
|
1024
|
-
|
|
1025
|
-
const SignUserOpStep = ({
|
|
1026
|
-
setStep,
|
|
1027
|
-
setUserOp,
|
|
1028
|
-
nextStep,
|
|
1029
|
-
}: {
|
|
1030
|
-
nextStep: WithdrawalStep;
|
|
1031
|
-
setStep: (step: WithdrawalStep) => void;
|
|
1032
|
-
setUserOp: (userOp: any) => void;
|
|
1033
|
-
}) => {
|
|
1034
|
-
const { chainIdIn } = useAppDetails();
|
|
1035
|
-
const { routeLoading, usdAmountIn, routeData } = useRouteData();
|
|
1036
|
-
const { signMessageAsync } = useSignMessage();
|
|
1037
|
-
const { address } = useAccount();
|
|
1038
|
-
const [isSigning, setIsSigning] = useState(false);
|
|
1039
|
-
|
|
1040
|
-
const handleSignUserOp = async () => {
|
|
1041
|
-
if (!routeData || (routeData as any)?.error) {
|
|
1042
|
-
console.error("No valid router data available");
|
|
1043
|
-
return;
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
try {
|
|
1047
|
-
setIsSigning(true);
|
|
1048
|
-
|
|
1049
|
-
// Extract userOp from routeData
|
|
1050
|
-
const userOperation = routeData?.userOp;
|
|
1051
|
-
if (!userOperation) {
|
|
1052
|
-
console.error("No userOperation found in routeData");
|
|
1053
|
-
return;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
console.log("Signing userOperation:", userOperation);
|
|
1057
|
-
|
|
1058
|
-
// Use viem's getUserOperationHash function
|
|
1059
|
-
const userOpHash = getUserOperationHash({
|
|
1060
|
-
// @ts-ignore
|
|
1061
|
-
userOperation,
|
|
1062
|
-
entryPointAddress: ENTRY_POINT_ADDRESS,
|
|
1063
|
-
entryPointVersion: "0.7",
|
|
1064
|
-
chainId: chainIdIn,
|
|
1065
|
-
});
|
|
1066
|
-
|
|
1067
|
-
// Sign the userOperation hash directly
|
|
1068
|
-
const signature = await signMessageAsync({
|
|
1069
|
-
account: address as `0x${string}`,
|
|
1070
|
-
message: { raw: userOpHash as `0x${string}` },
|
|
1071
|
-
});
|
|
1072
|
-
|
|
1073
|
-
// Update userOperation with signature
|
|
1074
|
-
const signedUserOp = {
|
|
1075
|
-
...userOperation,
|
|
1076
|
-
signature,
|
|
1077
|
-
};
|
|
1078
|
-
|
|
1079
|
-
console.log("signedUserOp", JSON.stringify(signedUserOp));
|
|
1080
|
-
|
|
1081
|
-
setUserOp(signedUserOp);
|
|
1082
|
-
setStep(nextStep);
|
|
1083
|
-
} catch (error) {
|
|
1084
|
-
console.error("Failed to sign userOperation:", error);
|
|
1085
|
-
} finally {
|
|
1086
|
-
setIsSigning(false);
|
|
1087
|
-
}
|
|
1088
|
-
};
|
|
1089
|
-
|
|
1090
|
-
return (
|
|
1091
|
-
<BodyWrapper>
|
|
1092
|
-
<Flex
|
|
1093
|
-
flexDirection={"column"}
|
|
1094
|
-
gap={"16px"}
|
|
1095
|
-
alignItems={"center"}
|
|
1096
|
-
width={"100%"}
|
|
1097
|
-
>
|
|
1098
|
-
<Skeleton
|
|
1099
|
-
loading={routeLoading}
|
|
1100
|
-
width={routeLoading ? "156px" : "auto"}
|
|
1101
|
-
>
|
|
1102
|
-
<Input
|
|
1103
|
-
readOnly
|
|
1104
|
-
marginY={"8px"}
|
|
1105
|
-
variant={"text"}
|
|
1106
|
-
placeholder={"$10.00"}
|
|
1107
|
-
value={usdAmountIn}
|
|
1108
|
-
/>
|
|
1109
|
-
</Skeleton>
|
|
1110
|
-
|
|
1111
|
-
<QuoteParameters />
|
|
1112
|
-
</Flex>
|
|
1113
|
-
|
|
1114
|
-
<TransactionDetailRow />
|
|
1115
|
-
|
|
1116
|
-
<Button
|
|
1117
|
-
disabled={
|
|
1118
|
-
!!(routeData as any)?.message || routeLoading || isSigning
|
|
1119
|
-
}
|
|
1120
|
-
loading={routeLoading || isSigning}
|
|
1121
|
-
onClick={handleSignUserOp}
|
|
1122
|
-
>
|
|
1123
|
-
{routeLoading
|
|
1124
|
-
? "Loading quote"
|
|
1125
|
-
: isSigning
|
|
1126
|
-
? "Signing..."
|
|
1127
|
-
: "Sign Transaction"}
|
|
1128
|
-
</Button>
|
|
1129
|
-
</BodyWrapper>
|
|
1130
|
-
);
|
|
1131
|
-
};
|
|
1132
|
-
|
|
1133
636
|
const InitiateWithdrawalStep = ({
|
|
1134
637
|
selectedToken,
|
|
1135
638
|
userOp,
|
|
@@ -1156,7 +659,6 @@ const InitiateWithdrawalStep = ({
|
|
|
1156
659
|
return;
|
|
1157
660
|
}
|
|
1158
661
|
|
|
1159
|
-
// Convert amountIn from wei to token amount
|
|
1160
662
|
const transferAmount = tokenInData?.decimals
|
|
1161
663
|
? normalizeValue(amountIn, tokenInData.decimals)
|
|
1162
664
|
: 0;
|
|
@@ -1173,19 +675,16 @@ const InitiateWithdrawalStep = ({
|
|
|
1173
675
|
amount: transferAmount,
|
|
1174
676
|
},
|
|
1175
677
|
];
|
|
1176
|
-
|
|
678
|
+
|
|
679
|
+
console.log("link request body", {
|
|
1177
680
|
restrictMultipleAccounts: true,
|
|
1178
681
|
userId: deviceKey,
|
|
1179
682
|
integrationId: selectedIntegration?.id,
|
|
1180
|
-
transferOptions: {
|
|
1181
|
-
|
|
1182
|
-
},
|
|
1183
|
-
};
|
|
1184
|
-
|
|
1185
|
-
console.log("link request body", meshData);
|
|
683
|
+
transferOptions: { toAddresses },
|
|
684
|
+
});
|
|
1186
685
|
|
|
1187
686
|
const response = await fetch(
|
|
1188
|
-
`${
|
|
687
|
+
`${CHECKOUT_BFF_URL}/linktoken?brokerType=${encodeURIComponent(selectedIntegration?.type || "")}`,
|
|
1189
688
|
{
|
|
1190
689
|
method: "POST",
|
|
1191
690
|
headers: {
|
|
@@ -1222,7 +721,7 @@ const InitiateWithdrawalStep = ({
|
|
|
1222
721
|
clientId: address,
|
|
1223
722
|
accessTokens,
|
|
1224
723
|
onIntegrationConnected: (payload) => {
|
|
1225
|
-
handleMeshAccessPayload(payload.accessToken, sessionId);
|
|
724
|
+
handleMeshAccessPayload(payload.accessToken, sessionId);
|
|
1226
725
|
console.log("Integration connected", payload);
|
|
1227
726
|
},
|
|
1228
727
|
onTransferFinished: (transferData) => {
|
|
@@ -1270,7 +769,14 @@ const InitiateWithdrawalStep = ({
|
|
|
1270
769
|
if (error) {
|
|
1271
770
|
return (
|
|
1272
771
|
<BodyWrapper>
|
|
1273
|
-
<Flex
|
|
772
|
+
<Flex
|
|
773
|
+
direction="column"
|
|
774
|
+
align="center"
|
|
775
|
+
justify="center"
|
|
776
|
+
flex={1}
|
|
777
|
+
p={6}
|
|
778
|
+
gap={4}
|
|
779
|
+
>
|
|
1274
780
|
<Flex
|
|
1275
781
|
align="center"
|
|
1276
782
|
justify="center"
|
|
@@ -1279,7 +785,11 @@ const InitiateWithdrawalStep = ({
|
|
|
1279
785
|
borderRadius="full"
|
|
1280
786
|
bg="orange.100"
|
|
1281
787
|
>
|
|
1282
|
-
<Icon
|
|
788
|
+
<Icon
|
|
789
|
+
as={TriangleAlert}
|
|
790
|
+
boxSize={6}
|
|
791
|
+
color="orange.500"
|
|
792
|
+
/>
|
|
1283
793
|
</Flex>
|
|
1284
794
|
<Text fontSize="md" textAlign="center" color="gray.700">
|
|
1285
795
|
{error}
|
|
@@ -1307,657 +817,24 @@ const InitiateWithdrawalStep = ({
|
|
|
1307
817
|
);
|
|
1308
818
|
};
|
|
1309
819
|
|
|
1310
|
-
// Phase indicator component for cross-chain tracking
|
|
1311
|
-
const PhaseIndicator = ({
|
|
1312
|
-
currentPhase,
|
|
1313
|
-
phases,
|
|
1314
|
-
}: {
|
|
1315
|
-
currentPhase: number;
|
|
1316
|
-
phases: string[];
|
|
1317
|
-
}) => {
|
|
1318
|
-
return (
|
|
1319
|
-
<Box display="flex" gap={4} justifyContent="center" mb={4}>
|
|
1320
|
-
{phases.map((phase, index) => (
|
|
1321
|
-
<Box key={phase} display="flex" alignItems="center" gap={2}>
|
|
1322
|
-
<Box
|
|
1323
|
-
w={6}
|
|
1324
|
-
h={6}
|
|
1325
|
-
borderRadius="full"
|
|
1326
|
-
bg={
|
|
1327
|
-
index < currentPhase
|
|
1328
|
-
? "green.500"
|
|
1329
|
-
: index === currentPhase
|
|
1330
|
-
? "blue.500"
|
|
1331
|
-
: "gray.300"
|
|
1332
|
-
}
|
|
1333
|
-
display="flex"
|
|
1334
|
-
alignItems="center"
|
|
1335
|
-
justifyContent="center"
|
|
1336
|
-
color="white"
|
|
1337
|
-
fontSize="xs"
|
|
1338
|
-
fontWeight="bold"
|
|
1339
|
-
>
|
|
1340
|
-
{index < currentPhase ? "✓" : index + 1}
|
|
1341
|
-
</Box>
|
|
1342
|
-
<Text
|
|
1343
|
-
fontSize="sm"
|
|
1344
|
-
color={index === currentPhase ? "fg" : "fg.muted"}
|
|
1345
|
-
fontWeight={
|
|
1346
|
-
index === currentPhase ? "semibold" : "normal"
|
|
1347
|
-
}
|
|
1348
|
-
>
|
|
1349
|
-
{phase}
|
|
1350
|
-
</Text>
|
|
1351
|
-
</Box>
|
|
1352
|
-
))}
|
|
1353
|
-
</Box>
|
|
1354
|
-
);
|
|
1355
|
-
};
|
|
1356
|
-
|
|
1357
|
-
const TrackUserOpStep = ({
|
|
1358
|
-
selectedToken,
|
|
1359
|
-
userOp,
|
|
1360
|
-
setStep,
|
|
1361
|
-
}: {
|
|
1362
|
-
selectedToken: MatchedToken | null;
|
|
1363
|
-
userOp: any;
|
|
1364
|
-
setStep: (step: WithdrawalStep) => void;
|
|
1365
|
-
}) => {
|
|
1366
|
-
const { chainIdIn, chainIdOut, tokenInData } = useAppDetails();
|
|
1367
|
-
const { amountIn } = useAppStore();
|
|
1368
|
-
|
|
1369
|
-
// Determine if this is a cross-chain (bridge) flow
|
|
1370
|
-
const isCrosschain = chainIdIn !== chainIdOut;
|
|
1371
|
-
|
|
1372
|
-
// Phase management: for crosschain 'cex' -> 'bridge' -> 'completed', for single-chain just 'cex' -> 'completed'
|
|
1373
|
-
const [phase, setPhase] = useState<
|
|
1374
|
-
"cex" | "bridge" | "completed" | "failed"
|
|
1375
|
-
>("cex");
|
|
1376
|
-
const [operationId, setOperationId] = useState<string | null>(null);
|
|
1377
|
-
const [status, setStatus] = useState<
|
|
1378
|
-
"sending" | "tracking" | "completed" | "failed"
|
|
1379
|
-
>("sending");
|
|
1380
|
-
const [message, setMessage] = useState("Sending operation to tracker...");
|
|
1381
|
-
const [txHash, setTxHash] = useState<`0x${string}` | null>(null);
|
|
1382
|
-
const [isTimerFinished, setIsTimerFinished] = useState(false);
|
|
1383
|
-
const [trackingInterval, setTrackingInterval] =
|
|
1384
|
-
useState<NodeJS.Timeout | null>(null);
|
|
1385
|
-
const [destinationVerified, setDestinationVerified] = useState(false);
|
|
1386
|
-
const [destinationSuccess, setDestinationSuccess] = useState<
|
|
1387
|
-
boolean | null
|
|
1388
|
-
>(null);
|
|
1389
|
-
const [refundDetails, setRefundDetails] = useState<{
|
|
1390
|
-
token: string;
|
|
1391
|
-
amount: string;
|
|
1392
|
-
recipient: string;
|
|
1393
|
-
isNative: boolean;
|
|
1394
|
-
} | null>(null);
|
|
1395
|
-
const [destinationTxHash, setDestinationTxHash] = useState<string | null>(
|
|
1396
|
-
null,
|
|
1397
|
-
);
|
|
1398
|
-
|
|
1399
|
-
// LayerZero tracking for bridge progress (real-time updates)
|
|
1400
|
-
const lzStatus = useLayerZeroStatus(
|
|
1401
|
-
txHash ?? undefined,
|
|
1402
|
-
isCrosschain && phase === "bridge",
|
|
1403
|
-
);
|
|
1404
|
-
|
|
1405
|
-
// Send UserOp to tracker
|
|
1406
|
-
useEffect(() => {
|
|
1407
|
-
const sendUserOpToTracker = async () => {
|
|
1408
|
-
if (!selectedToken || !userOp || !tokenInData) {
|
|
1409
|
-
console.error("Missing required data for tracking");
|
|
1410
|
-
setStatus("failed");
|
|
1411
|
-
setMessage("Missing required data");
|
|
1412
|
-
setPhase("failed");
|
|
1413
|
-
return;
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
try {
|
|
1417
|
-
const response = await fetch(
|
|
1418
|
-
"https://alpha-scanners-dev-054573dc8549.herokuapp.com/operations",
|
|
1419
|
-
{
|
|
1420
|
-
method: "POST",
|
|
1421
|
-
headers: {
|
|
1422
|
-
"Content-Type": "application/json",
|
|
1423
|
-
},
|
|
1424
|
-
body: JSON.stringify({
|
|
1425
|
-
userOperationData: {
|
|
1426
|
-
sender: userOp.sender,
|
|
1427
|
-
nonce: userOp.nonce,
|
|
1428
|
-
factory: userOp.factory,
|
|
1429
|
-
factoryData: userOp.factoryData,
|
|
1430
|
-
callData: userOp.callData,
|
|
1431
|
-
callGasLimit: userOp.callGasLimit,
|
|
1432
|
-
verificationGasLimit:
|
|
1433
|
-
userOp.verificationGasLimit,
|
|
1434
|
-
preVerificationGas: userOp.preVerificationGas,
|
|
1435
|
-
maxFeePerGas: userOp.maxFeePerGas,
|
|
1436
|
-
maxPriorityFeePerGas:
|
|
1437
|
-
userOp.maxPriorityFeePerGas,
|
|
1438
|
-
paymaster: userOp.paymaster,
|
|
1439
|
-
paymasterData: userOp.paymasterData,
|
|
1440
|
-
paymasterVerificationGasLimit:
|
|
1441
|
-
userOp.paymasterVerificationGasLimit,
|
|
1442
|
-
paymasterPostOpGasLimit:
|
|
1443
|
-
userOp.paymasterPostOpGasLimit,
|
|
1444
|
-
signature: userOp.signature,
|
|
1445
|
-
},
|
|
1446
|
-
chainId: chainIdIn,
|
|
1447
|
-
expectedBalance: amountIn,
|
|
1448
|
-
tokenAddress: tokenInData.address,
|
|
1449
|
-
}),
|
|
1450
|
-
},
|
|
1451
|
-
);
|
|
1452
|
-
|
|
1453
|
-
const data = await response.json();
|
|
1454
|
-
console.log("Operation tracking response:", data);
|
|
1455
|
-
|
|
1456
|
-
if (data.success && data.operationId) {
|
|
1457
|
-
setOperationId(data.operationId);
|
|
1458
|
-
setStatus("tracking");
|
|
1459
|
-
setMessage(
|
|
1460
|
-
isCrosschain
|
|
1461
|
-
? "Funds forwarding in progress..."
|
|
1462
|
-
: "Tracking operation progress...",
|
|
1463
|
-
);
|
|
1464
|
-
} else {
|
|
1465
|
-
throw new Error(
|
|
1466
|
-
data.message || "Failed to send operation to tracker",
|
|
1467
|
-
);
|
|
1468
|
-
}
|
|
1469
|
-
} catch (error) {
|
|
1470
|
-
console.error("Failed to send operation to tracker:", error);
|
|
1471
|
-
setStatus("failed");
|
|
1472
|
-
setMessage("Failed to send operation to tracker");
|
|
1473
|
-
setPhase("failed");
|
|
1474
|
-
}
|
|
1475
|
-
};
|
|
1476
|
-
|
|
1477
|
-
sendUserOpToTracker();
|
|
1478
|
-
}, []);
|
|
1479
|
-
|
|
1480
|
-
// Track operation status
|
|
1481
|
-
useEffect(() => {
|
|
1482
|
-
if (!operationId || status !== "tracking") return;
|
|
1483
|
-
|
|
1484
|
-
const trackOperation = async () => {
|
|
1485
|
-
try {
|
|
1486
|
-
const response = await fetch(
|
|
1487
|
-
`https://alpha-scanners-dev-054573dc8549.herokuapp.com/operations/${operationId}/status`,
|
|
1488
|
-
);
|
|
1489
|
-
const data = await response.json();
|
|
1490
|
-
console.log("Operation status:", data);
|
|
1491
|
-
|
|
1492
|
-
if (data.operation?.status === "completed") {
|
|
1493
|
-
setStatus("completed");
|
|
1494
|
-
|
|
1495
|
-
if (isCrosschain) {
|
|
1496
|
-
// For crosschain: move to bridge phase
|
|
1497
|
-
setMessage("Funds forwarding completed!");
|
|
1498
|
-
if (data.operation?.bundleTxHash) {
|
|
1499
|
-
setTxHash(
|
|
1500
|
-
data.operation.bundleTxHash as `0x${string}`,
|
|
1501
|
-
);
|
|
1502
|
-
setPhase("bridge");
|
|
1503
|
-
} else {
|
|
1504
|
-
console.warn(
|
|
1505
|
-
"No bundleTxHash returned from indexer, cannot track bridge",
|
|
1506
|
-
);
|
|
1507
|
-
setPhase("completed");
|
|
1508
|
-
}
|
|
1509
|
-
} else {
|
|
1510
|
-
// For single-chain: complete
|
|
1511
|
-
setMessage("Operation completed successfully!");
|
|
1512
|
-
setPhase("completed");
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
if (trackingInterval) {
|
|
1516
|
-
clearInterval(trackingInterval);
|
|
1517
|
-
setTrackingInterval(null);
|
|
1518
|
-
}
|
|
1519
|
-
} else if (
|
|
1520
|
-
["failed", "failed_permanent"].includes(
|
|
1521
|
-
data.operation?.status,
|
|
1522
|
-
)
|
|
1523
|
-
) {
|
|
1524
|
-
setStatus("failed");
|
|
1525
|
-
setMessage(
|
|
1526
|
-
isCrosschain
|
|
1527
|
-
? "Bridging failed. Please select Smart-account balance as a source to use withdrawn funds"
|
|
1528
|
-
: "Operation failed",
|
|
1529
|
-
);
|
|
1530
|
-
setPhase("failed");
|
|
1531
|
-
if (trackingInterval) {
|
|
1532
|
-
clearInterval(trackingInterval);
|
|
1533
|
-
setTrackingInterval(null);
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
} catch (error) {
|
|
1537
|
-
console.error("Failed to fetch operation status:", error);
|
|
1538
|
-
}
|
|
1539
|
-
};
|
|
1540
|
-
|
|
1541
|
-
const interval = setInterval(trackOperation, 3000);
|
|
1542
|
-
setTrackingInterval(interval);
|
|
1543
|
-
|
|
1544
|
-
return () => {
|
|
1545
|
-
if (interval) clearInterval(interval);
|
|
1546
|
-
};
|
|
1547
|
-
}, [operationId, status, isCrosschain]);
|
|
1548
|
-
|
|
1549
|
-
// Handle bridge completion - verify destination execution once LayerZero shows DELIVERED
|
|
1550
|
-
useEffect(() => {
|
|
1551
|
-
if (!isCrosschain || phase !== "bridge") return;
|
|
1552
|
-
|
|
1553
|
-
// If LayerZero failed, mark as failed immediately
|
|
1554
|
-
if (lzStatus.isFailed) {
|
|
1555
|
-
setPhase("failed");
|
|
1556
|
-
return;
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
// When LayerZero shows DELIVERED, verify destination execution with Enso API
|
|
1560
|
-
if (
|
|
1561
|
-
lzStatus.isComplete &&
|
|
1562
|
-
!destinationVerified &&
|
|
1563
|
-
txHash &&
|
|
1564
|
-
chainIdIn
|
|
1565
|
-
) {
|
|
1566
|
-
// TODO: use hook
|
|
1567
|
-
const verifyDestination = async () => {
|
|
1568
|
-
try {
|
|
1569
|
-
const res = await fetch(
|
|
1570
|
-
`https://api.enso.build/api/v1/layerzero/bridge/check?chainId=${chainIdIn}&txHash=${txHash}`,
|
|
1571
|
-
);
|
|
1572
|
-
if (!res.ok) {
|
|
1573
|
-
// If API call fails, assume success (LayerZero delivered)
|
|
1574
|
-
setDestinationVerified(true);
|
|
1575
|
-
setDestinationSuccess(true);
|
|
1576
|
-
setPhase("completed");
|
|
1577
|
-
return;
|
|
1578
|
-
}
|
|
1579
|
-
const data = await res.json();
|
|
1580
|
-
setDestinationVerified(true);
|
|
1581
|
-
setDestinationTxHash(data.destinationTxHash || null);
|
|
1582
|
-
|
|
1583
|
-
if (data.status === "success") {
|
|
1584
|
-
setDestinationSuccess(true);
|
|
1585
|
-
setPhase("completed");
|
|
1586
|
-
} else if (data.status === "failed") {
|
|
1587
|
-
setDestinationSuccess(false);
|
|
1588
|
-
setRefundDetails(
|
|
1589
|
-
data.ensoDestinationEvent?.refundDetails || null,
|
|
1590
|
-
);
|
|
1591
|
-
setPhase("failed");
|
|
1592
|
-
} else {
|
|
1593
|
-
// Still pending, assume success since LZ delivered
|
|
1594
|
-
setDestinationSuccess(true);
|
|
1595
|
-
setPhase("completed");
|
|
1596
|
-
}
|
|
1597
|
-
} catch (error) {
|
|
1598
|
-
console.error("Failed to verify destination:", error);
|
|
1599
|
-
// On error, assume success since LayerZero delivered
|
|
1600
|
-
setDestinationVerified(true);
|
|
1601
|
-
setDestinationSuccess(true);
|
|
1602
|
-
setPhase("completed");
|
|
1603
|
-
}
|
|
1604
|
-
};
|
|
1605
|
-
verifyDestination();
|
|
1606
|
-
}
|
|
1607
|
-
}, [
|
|
1608
|
-
phase,
|
|
1609
|
-
lzStatus.isComplete,
|
|
1610
|
-
lzStatus.isFailed,
|
|
1611
|
-
isCrosschain,
|
|
1612
|
-
destinationVerified,
|
|
1613
|
-
txHash,
|
|
1614
|
-
chainIdIn,
|
|
1615
|
-
]);
|
|
1616
|
-
|
|
1617
|
-
const handleTimerFinish = () => {
|
|
1618
|
-
setIsTimerFinished(true);
|
|
1619
|
-
};
|
|
1620
|
-
|
|
1621
|
-
const getOverallStatus = () => {
|
|
1622
|
-
if (phase === "failed") return "failed";
|
|
1623
|
-
if (phase === "completed") return "completed";
|
|
1624
|
-
return "processing";
|
|
1625
|
-
};
|
|
1626
|
-
|
|
1627
|
-
const getStatusColor = () => {
|
|
1628
|
-
switch (getOverallStatus()) {
|
|
1629
|
-
case "completed":
|
|
1630
|
-
return "#14AE5C";
|
|
1631
|
-
case "failed":
|
|
1632
|
-
return "#E84142";
|
|
1633
|
-
default:
|
|
1634
|
-
return "#1E171F";
|
|
1635
|
-
}
|
|
1636
|
-
};
|
|
1637
|
-
|
|
1638
|
-
const getStatusText = () => {
|
|
1639
|
-
switch (getOverallStatus()) {
|
|
1640
|
-
case "completed":
|
|
1641
|
-
return "Success";
|
|
1642
|
-
case "failed":
|
|
1643
|
-
return "Failed";
|
|
1644
|
-
default:
|
|
1645
|
-
return "Processing";
|
|
1646
|
-
}
|
|
1647
|
-
};
|
|
1648
|
-
|
|
1649
|
-
const getCurrentMessage = () => {
|
|
1650
|
-
if (isCrosschain) {
|
|
1651
|
-
if (phase === "cex") {
|
|
1652
|
-
return `(1/2) ${message}`;
|
|
1653
|
-
} else if (phase === "bridge") {
|
|
1654
|
-
return `(2/2) ${lzStatus.message}`;
|
|
1655
|
-
} else if (phase === "completed") {
|
|
1656
|
-
return "Transfer completed successfully!";
|
|
1657
|
-
} else {
|
|
1658
|
-
return refundDetails
|
|
1659
|
-
? "Destination execution failed. Funds refunded to smart account."
|
|
1660
|
-
: "Transfer failed";
|
|
1661
|
-
}
|
|
1662
|
-
} else {
|
|
1663
|
-
if (phase === "completed") {
|
|
1664
|
-
return "Operation completed successfully!";
|
|
1665
|
-
} else if (phase === "failed") {
|
|
1666
|
-
return "Operation failed";
|
|
1667
|
-
}
|
|
1668
|
-
return message;
|
|
1669
|
-
}
|
|
1670
|
-
};
|
|
1671
|
-
|
|
1672
|
-
const renderStatusIcon = () => {
|
|
1673
|
-
const isProcessing = phase === "cex" || phase === "bridge";
|
|
1674
|
-
|
|
1675
|
-
if (isProcessing) {
|
|
1676
|
-
return (
|
|
1677
|
-
<CircleTimer
|
|
1678
|
-
start={status === "tracking" || phase === "bridge"}
|
|
1679
|
-
onFinish={handleTimerFinish}
|
|
1680
|
-
duration={isCrosschain ? 180 : 120}
|
|
1681
|
-
/>
|
|
1682
|
-
);
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
if (phase === "completed") {
|
|
1686
|
-
return (
|
|
1687
|
-
<Box display="flex" flexDirection="column" alignItems="center">
|
|
1688
|
-
<Image
|
|
1689
|
-
src={SuccessIcon}
|
|
1690
|
-
boxShadow="0px 0px 20px #14AE5C"
|
|
1691
|
-
borderRadius="90%"
|
|
1692
|
-
width="58px"
|
|
1693
|
-
height="58px"
|
|
1694
|
-
/>
|
|
1695
|
-
</Box>
|
|
1696
|
-
);
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
return (
|
|
1700
|
-
<Box display="flex" flexDirection="column" alignItems="center">
|
|
1701
|
-
<Image
|
|
1702
|
-
src={FailIcon}
|
|
1703
|
-
boxShadow="0px 0px 20px #E84142"
|
|
1704
|
-
borderRadius="90%"
|
|
1705
|
-
width="58px"
|
|
1706
|
-
height="58px"
|
|
1707
|
-
/>
|
|
1708
|
-
</Box>
|
|
1709
|
-
);
|
|
1710
|
-
};
|
|
1711
|
-
|
|
1712
|
-
const intermediateChainName = chainIdIn
|
|
1713
|
-
? STARGATE_CHAIN_NAMES[chainIdIn as keyof typeof STARGATE_CHAIN_NAMES]
|
|
1714
|
-
: "Unknown";
|
|
1715
|
-
const targetChainName = chainIdOut
|
|
1716
|
-
? STARGATE_CHAIN_NAMES[chainIdOut as keyof typeof STARGATE_CHAIN_NAMES]
|
|
1717
|
-
: "Unknown";
|
|
1718
|
-
|
|
1719
|
-
return (
|
|
1720
|
-
<BodyWrapper>
|
|
1721
|
-
{/* Phase Indicator (crosschain only) */}
|
|
1722
|
-
{isCrosschain && (
|
|
1723
|
-
<PhaseIndicator
|
|
1724
|
-
currentPhase={
|
|
1725
|
-
phase === "cex" ? 0 : phase === "bridge" ? 1 : 2
|
|
1726
|
-
}
|
|
1727
|
-
phases={["Forward funds", "Bridge"]}
|
|
1728
|
-
/>
|
|
1729
|
-
)}
|
|
1730
|
-
|
|
1731
|
-
{/* Status Icon */}
|
|
1732
|
-
<Box
|
|
1733
|
-
display="flex"
|
|
1734
|
-
flexDirection="column"
|
|
1735
|
-
paddingBottom="16px"
|
|
1736
|
-
alignItems="center"
|
|
1737
|
-
width="100%"
|
|
1738
|
-
>
|
|
1739
|
-
{renderStatusIcon()}
|
|
1740
|
-
<Box
|
|
1741
|
-
display="flex"
|
|
1742
|
-
flexDirection="column"
|
|
1743
|
-
alignItems="center"
|
|
1744
|
-
marginTop="16px"
|
|
1745
|
-
textAlign="center"
|
|
1746
|
-
>
|
|
1747
|
-
<Text
|
|
1748
|
-
fontSize="lg"
|
|
1749
|
-
fontWeight="semibold"
|
|
1750
|
-
color="fg"
|
|
1751
|
-
marginBottom="8px"
|
|
1752
|
-
>
|
|
1753
|
-
{getCurrentMessage()}
|
|
1754
|
-
</Text>
|
|
1755
|
-
{(phase === "cex" || phase === "bridge") &&
|
|
1756
|
-
isTimerFinished && (
|
|
1757
|
-
<Text
|
|
1758
|
-
fontSize="sm"
|
|
1759
|
-
color="fg.muted"
|
|
1760
|
-
maxWidth="280px"
|
|
1761
|
-
>
|
|
1762
|
-
Your operation is being processed – no action is
|
|
1763
|
-
required from you.
|
|
1764
|
-
</Text>
|
|
1765
|
-
)}
|
|
1766
|
-
</Box>
|
|
1767
|
-
</Box>
|
|
1768
|
-
|
|
1769
|
-
{/* Status Table */}
|
|
1770
|
-
<Table.Root key="status" size="sm" variant="outline" width="100%">
|
|
1771
|
-
<Table.Body>
|
|
1772
|
-
<Table.Row>
|
|
1773
|
-
<Table.Cell>Status</Table.Cell>
|
|
1774
|
-
<Table.Cell
|
|
1775
|
-
display="flex"
|
|
1776
|
-
textAlign="end"
|
|
1777
|
-
justifyContent="end"
|
|
1778
|
-
>
|
|
1779
|
-
<Text color={getStatusColor()}>
|
|
1780
|
-
{getStatusText()}
|
|
1781
|
-
</Text>
|
|
1782
|
-
</Table.Cell>
|
|
1783
|
-
</Table.Row>
|
|
1784
|
-
{isCrosschain && (
|
|
1785
|
-
<>
|
|
1786
|
-
<Table.Row>
|
|
1787
|
-
<Table.Cell>Current Phase</Table.Cell>
|
|
1788
|
-
<Table.Cell
|
|
1789
|
-
display="flex"
|
|
1790
|
-
textAlign="end"
|
|
1791
|
-
justifyContent="end"
|
|
1792
|
-
>
|
|
1793
|
-
<Text>
|
|
1794
|
-
{phase === "cex"
|
|
1795
|
-
? "Awaiting funds"
|
|
1796
|
-
: phase === "bridge"
|
|
1797
|
-
? "Bridging"
|
|
1798
|
-
: phase === "completed"
|
|
1799
|
-
? "Complete"
|
|
1800
|
-
: "Failed"}
|
|
1801
|
-
</Text>
|
|
1802
|
-
</Table.Cell>
|
|
1803
|
-
</Table.Row>
|
|
1804
|
-
<Table.Row>
|
|
1805
|
-
<Table.Cell>Intermediate Chain</Table.Cell>
|
|
1806
|
-
<Table.Cell
|
|
1807
|
-
display="flex"
|
|
1808
|
-
textAlign="end"
|
|
1809
|
-
justifyContent="end"
|
|
1810
|
-
>
|
|
1811
|
-
<Text textTransform="capitalize">
|
|
1812
|
-
{intermediateChainName}
|
|
1813
|
-
</Text>
|
|
1814
|
-
</Table.Cell>
|
|
1815
|
-
</Table.Row>
|
|
1816
|
-
<Table.Row>
|
|
1817
|
-
<Table.Cell>Final Destination</Table.Cell>
|
|
1818
|
-
<Table.Cell
|
|
1819
|
-
display="flex"
|
|
1820
|
-
textAlign="end"
|
|
1821
|
-
justifyContent="end"
|
|
1822
|
-
>
|
|
1823
|
-
<Text textTransform="capitalize">
|
|
1824
|
-
{targetChainName}
|
|
1825
|
-
</Text>
|
|
1826
|
-
</Table.Cell>
|
|
1827
|
-
</Table.Row>
|
|
1828
|
-
</>
|
|
1829
|
-
)}
|
|
1830
|
-
{operationId && (
|
|
1831
|
-
<Table.Row>
|
|
1832
|
-
<Table.Cell>Operation ID</Table.Cell>
|
|
1833
|
-
<Table.Cell
|
|
1834
|
-
display="flex"
|
|
1835
|
-
textAlign="end"
|
|
1836
|
-
justifyContent="end"
|
|
1837
|
-
>
|
|
1838
|
-
<Text fontSize="sm" color="fg.muted">
|
|
1839
|
-
{operationId}
|
|
1840
|
-
</Text>
|
|
1841
|
-
</Table.Cell>
|
|
1842
|
-
</Table.Row>
|
|
1843
|
-
)}
|
|
1844
|
-
{isCrosschain && txHash && (
|
|
1845
|
-
<Table.Row>
|
|
1846
|
-
<Table.Cell>Bridge TX</Table.Cell>
|
|
1847
|
-
<Table.Cell
|
|
1848
|
-
display="flex"
|
|
1849
|
-
textAlign="end"
|
|
1850
|
-
justifyContent="end"
|
|
1851
|
-
>
|
|
1852
|
-
<Text
|
|
1853
|
-
fontSize="sm"
|
|
1854
|
-
color="blue.500"
|
|
1855
|
-
cursor="pointer"
|
|
1856
|
-
onClick={() =>
|
|
1857
|
-
window.open(
|
|
1858
|
-
`https://layerzeroscan.com/tx/${txHash}`,
|
|
1859
|
-
"_blank",
|
|
1860
|
-
)
|
|
1861
|
-
}
|
|
1862
|
-
>
|
|
1863
|
-
View on LayerZero
|
|
1864
|
-
</Text>
|
|
1865
|
-
</Table.Cell>
|
|
1866
|
-
</Table.Row>
|
|
1867
|
-
)}
|
|
1868
|
-
{isCrosschain && destinationTxHash && (
|
|
1869
|
-
<Table.Row>
|
|
1870
|
-
<Table.Cell>Destination TX</Table.Cell>
|
|
1871
|
-
<Table.Cell
|
|
1872
|
-
display="flex"
|
|
1873
|
-
textAlign="end"
|
|
1874
|
-
justifyContent="end"
|
|
1875
|
-
>
|
|
1876
|
-
<Text
|
|
1877
|
-
fontSize="sm"
|
|
1878
|
-
color="blue.500"
|
|
1879
|
-
cursor="pointer"
|
|
1880
|
-
onClick={() => {
|
|
1881
|
-
const explorer =
|
|
1882
|
-
CHAINS_ETHERSCAN[
|
|
1883
|
-
chainIdOut as keyof typeof CHAINS_ETHERSCAN
|
|
1884
|
-
] || "https://etherscan.io";
|
|
1885
|
-
window.open(
|
|
1886
|
-
`${explorer}/tx/${destinationTxHash}`,
|
|
1887
|
-
"_blank",
|
|
1888
|
-
);
|
|
1889
|
-
}}
|
|
1890
|
-
>
|
|
1891
|
-
View on Explorer
|
|
1892
|
-
</Text>
|
|
1893
|
-
</Table.Cell>
|
|
1894
|
-
</Table.Row>
|
|
1895
|
-
)}
|
|
1896
|
-
{isCrosschain && phase === "bridge" && (
|
|
1897
|
-
<Table.Row>
|
|
1898
|
-
<Table.Cell>Bridge Progress</Table.Cell>
|
|
1899
|
-
<Table.Cell
|
|
1900
|
-
display="flex"
|
|
1901
|
-
textAlign="end"
|
|
1902
|
-
justifyContent="end"
|
|
1903
|
-
>
|
|
1904
|
-
<Text>({lzStatus.step}/4)</Text>
|
|
1905
|
-
</Table.Cell>
|
|
1906
|
-
</Table.Row>
|
|
1907
|
-
)}
|
|
1908
|
-
{isCrosschain && refundDetails && (
|
|
1909
|
-
<Table.Row>
|
|
1910
|
-
<Table.Cell>Refund</Table.Cell>
|
|
1911
|
-
<Table.Cell
|
|
1912
|
-
display="flex"
|
|
1913
|
-
textAlign="end"
|
|
1914
|
-
justifyContent="end"
|
|
1915
|
-
>
|
|
1916
|
-
<Text fontSize="sm" color="orange.500">
|
|
1917
|
-
Funds refunded to smart account
|
|
1918
|
-
</Text>
|
|
1919
|
-
</Table.Cell>
|
|
1920
|
-
</Table.Row>
|
|
1921
|
-
)}
|
|
1922
|
-
</Table.Body>
|
|
1923
|
-
</Table.Root>
|
|
1924
|
-
|
|
1925
|
-
<QuoteParameters />
|
|
1926
|
-
|
|
1927
|
-
<TransactionDetailRow />
|
|
1928
|
-
|
|
1929
|
-
{(phase === "completed" || phase === "failed") && (
|
|
1930
|
-
<Button
|
|
1931
|
-
onClick={() => setStep(WithdrawalStep.CheckSessionKey)}
|
|
1932
|
-
visual="solid"
|
|
1933
|
-
>
|
|
1934
|
-
{phase === "completed" ? "New Deposit" : "Retry Deposit"}
|
|
1935
|
-
</Button>
|
|
1936
|
-
)}
|
|
1937
|
-
</BodyWrapper>
|
|
1938
|
-
);
|
|
1939
|
-
};
|
|
1940
|
-
|
|
1941
820
|
const ExchangeFlow = ({
|
|
1942
821
|
setFlow,
|
|
1943
|
-
initialStep = WithdrawalStep.ChooseExchange,
|
|
1944
822
|
}: {
|
|
1945
|
-
setFlow: (string) => void;
|
|
1946
|
-
initialStep?: WithdrawalStep;
|
|
823
|
+
setFlow: (flow: string) => void;
|
|
1947
824
|
}) => {
|
|
1948
825
|
const { handleClose, enforceFlow } = useContext(CheckoutContext);
|
|
1949
|
-
const [currentStep, setCurrentStep] = useState(
|
|
826
|
+
const [currentStep, setCurrentStep] = useState<WithdrawalStep>(
|
|
827
|
+
WithdrawalStep.ChooseExchange,
|
|
828
|
+
);
|
|
1950
829
|
const [selectedToken, setSelectedToken] = useState<MatchedToken | null>(
|
|
1951
830
|
null,
|
|
1952
831
|
);
|
|
1953
832
|
const [userOp, setUserOp] = useState<any | null>(null);
|
|
1954
|
-
|
|
1955
|
-
(state) => state.setSelectedIntegration,
|
|
1956
|
-
);
|
|
833
|
+
|
|
1957
834
|
const selectedIntegration = useAppStore((s) => s.selectedIntegration);
|
|
1958
835
|
|
|
1959
836
|
useEffect(() => {
|
|
1960
|
-
return () => setSelectedIntegration(null);
|
|
837
|
+
return () => useAppStore.getState().setSelectedIntegration(null);
|
|
1961
838
|
}, []);
|
|
1962
839
|
|
|
1963
840
|
const currentStepComponent = (() => {
|
|
@@ -1973,29 +850,19 @@ const ExchangeFlow = ({
|
|
|
1973
850
|
onTokenSelect={setSelectedToken}
|
|
1974
851
|
/>
|
|
1975
852
|
);
|
|
1976
|
-
case WithdrawalStep.ChooseBalanceAsset:
|
|
1977
|
-
return (
|
|
1978
|
-
<ChooseDelayedBalance
|
|
1979
|
-
setStep={setCurrentStep}
|
|
1980
|
-
onTokenSelect={setSelectedToken}
|
|
1981
|
-
/>
|
|
1982
|
-
);
|
|
1983
853
|
case WithdrawalStep.ChooseAmount:
|
|
1984
854
|
return (
|
|
1985
|
-
<
|
|
855
|
+
<SharedChooseAmountStep
|
|
1986
856
|
setStep={setCurrentStep}
|
|
857
|
+
nextStep={WithdrawalStep.SignUserOp}
|
|
1987
858
|
selectedToken={selectedToken}
|
|
859
|
+
mode="cex"
|
|
1988
860
|
/>
|
|
1989
861
|
);
|
|
1990
862
|
case WithdrawalStep.SignUserOp:
|
|
1991
863
|
return (
|
|
1992
|
-
<
|
|
1993
|
-
nextStep={
|
|
1994
|
-
// skip withdrawal if use existing balance
|
|
1995
|
-
selectedToken.holding
|
|
1996
|
-
? WithdrawalStep.InitiateWithdrawal
|
|
1997
|
-
: WithdrawalStep.TrackUserOp
|
|
1998
|
-
}
|
|
864
|
+
<SharedSignUserOpStep
|
|
865
|
+
nextStep={WithdrawalStep.InitiateWithdrawal}
|
|
1999
866
|
setStep={setCurrentStep}
|
|
2000
867
|
setUserOp={setUserOp}
|
|
2001
868
|
/>
|
|
@@ -2011,9 +878,10 @@ const ExchangeFlow = ({
|
|
|
2011
878
|
case WithdrawalStep.TrackUserOp:
|
|
2012
879
|
return (
|
|
2013
880
|
<TrackUserOpStep
|
|
2014
|
-
selectedToken={selectedToken}
|
|
2015
881
|
userOp={userOp}
|
|
2016
|
-
|
|
882
|
+
onReset={() =>
|
|
883
|
+
setCurrentStep(WithdrawalStep.CheckSessionKey)
|
|
884
|
+
}
|
|
2017
885
|
/>
|
|
2018
886
|
);
|
|
2019
887
|
default:
|
|
@@ -2027,23 +895,17 @@ const ExchangeFlow = ({
|
|
|
2027
895
|
<HeaderWrapper>
|
|
2028
896
|
{!(
|
|
2029
897
|
enforceFlow &&
|
|
2030
|
-
|
|
2031
|
-
currentStep === WithdrawalStep.ChooseBalanceAsset)
|
|
898
|
+
currentStep === WithdrawalStep.ChooseExchange
|
|
2032
899
|
) && (
|
|
2033
900
|
<IconButton
|
|
2034
901
|
minWidth={"16px"}
|
|
2035
902
|
minHeight={"16px"}
|
|
2036
903
|
maxWidth={"16px"}
|
|
2037
904
|
onClick={() => {
|
|
2038
|
-
const
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
).findIndex(
|
|
2043
|
-
(step) => step === currentStep,
|
|
2044
|
-
) - 1;
|
|
2045
|
-
if (index >= 0) {
|
|
2046
|
-
setCurrentStep(withdrawalSteps[index]);
|
|
905
|
+
const previousStep =
|
|
906
|
+
withdrawalPreviousStep[currentStep];
|
|
907
|
+
if (previousStep !== undefined) {
|
|
908
|
+
setCurrentStep(previousStep);
|
|
2047
909
|
} else {
|
|
2048
910
|
setFlow("");
|
|
2049
911
|
}
|
|
@@ -2051,7 +913,7 @@ const ExchangeFlow = ({
|
|
|
2051
913
|
>
|
|
2052
914
|
<Icon
|
|
2053
915
|
as={ChevronLeft}
|
|
2054
|
-
color="
|
|
916
|
+
color="fg.muted"
|
|
2055
917
|
width={"16px"}
|
|
2056
918
|
height={"16px"}
|
|
2057
919
|
/>
|
|
@@ -2080,7 +942,7 @@ const ExchangeFlow = ({
|
|
|
2080
942
|
>
|
|
2081
943
|
<Icon
|
|
2082
944
|
as={X}
|
|
2083
|
-
color="
|
|
945
|
+
color="fg.muted"
|
|
2084
946
|
width={"16px"}
|
|
2085
947
|
height={"16px"}
|
|
2086
948
|
/>
|
|
@@ -2089,33 +951,9 @@ const ExchangeFlow = ({
|
|
|
2089
951
|
</HeaderWrapper>
|
|
2090
952
|
</Modal.Header>
|
|
2091
953
|
<Modal.Body>
|
|
2092
|
-
{
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
{/* display="flex"*/}
|
|
2096
|
-
{/* flexDirection="column"*/}
|
|
2097
|
-
{/* alignItems="center"*/}
|
|
2098
|
-
{/* gap="16px"*/}
|
|
2099
|
-
{/* textAlign="center"*/}
|
|
2100
|
-
{/* >*/}
|
|
2101
|
-
{/* <Text fontSize="16px" fontWeight="600">*/}
|
|
2102
|
-
{/* Wrong Network*/}
|
|
2103
|
-
{/* </Text>*/}
|
|
2104
|
-
{/* <Text fontSize="14px" color="fg.muted">*/}
|
|
2105
|
-
{/* Please switch to {getChainName(chainIdOut)} to*/}
|
|
2106
|
-
{/* continue with your Binance withdrawal.*/}
|
|
2107
|
-
{/* </Text>*/}
|
|
2108
|
-
{/* <Button*/}
|
|
2109
|
-
{/* onClick={() => {*/}
|
|
2110
|
-
{/* switchChain({ chainId: chainIdOut });*/}
|
|
2111
|
-
{/* }}*/}
|
|
2112
|
-
{/* >*/}
|
|
2113
|
-
{/* Switch to {getChainName(chainIdOut)}*/}
|
|
2114
|
-
{/* </Button>*/}
|
|
2115
|
-
{/* </Box>*/}
|
|
2116
|
-
{/* </BodyWrapper>*/}
|
|
2117
|
-
{/*) : */}
|
|
2118
|
-
{currentStepComponent}
|
|
954
|
+
<AnimatedStep key={currentStep}>
|
|
955
|
+
{currentStepComponent}
|
|
956
|
+
</AnimatedStep>
|
|
2119
957
|
</Modal.Body>
|
|
2120
958
|
</>
|
|
2121
959
|
);
|