@ensofinance/checkout-widget 0.0.12 → 0.0.14

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 (149) hide show
  1. package/dist/checkout-widget.es.js +14486 -13826
  2. package/dist/checkout-widget.es.js.map +1 -1
  3. package/dist/checkout-widget.umd.js +44 -43
  4. package/dist/checkout-widget.umd.js.map +1 -1
  5. package/dist/index.d.ts +7 -0
  6. package/orval.config.ts +40 -6
  7. package/package.json +1 -1
  8. package/src/components/BridgeFee.tsx +3 -3
  9. package/src/components/Checkout.tsx +5 -53
  10. package/src/components/ExchangeConfirmSecurity.tsx +16 -3
  11. package/src/components/QuoteParameters.tsx +84 -65
  12. package/src/components/TransactionDetailRow.tsx +6 -5
  13. package/src/components/cards/AssetCard.tsx +2 -2
  14. package/src/components/cards/OptionCard.tsx +2 -0
  15. package/src/components/steps/ExchangeFlow.tsx +414 -135
  16. package/src/components/steps/{InitialStep.tsx → FlowSelector.tsx} +76 -33
  17. package/src/components/steps/WalletFlow/WalletAmountStep.tsx +214 -0
  18. package/src/components/steps/{WalletConfirmStep.tsx → WalletFlow/WalletConfirmStep.tsx} +107 -156
  19. package/src/components/steps/WalletFlow/WalletFlow.tsx +116 -0
  20. package/src/components/steps/{QuoteStep.tsx → WalletFlow/WalletQuoteStep.tsx} +9 -58
  21. package/src/components/steps/WalletFlow/WalletTokenStep.tsx +75 -0
  22. package/src/components/ui/index.tsx +0 -3
  23. package/src/enso-api/api.ts +192 -3
  24. package/src/enso-api/custom-instance.ts +1 -0
  25. package/src/enso-api/index.ts +29 -118
  26. package/src/enso-api/model/approveActionDto.ts +17 -0
  27. package/src/enso-api/model/approveArgsDto.ts +22 -0
  28. package/src/enso-api/model/approveArgsDtoAmount.ts +16 -0
  29. package/src/enso-api/model/balanceActionDto.ts +17 -0
  30. package/src/enso-api/model/balanceArgsDto.ts +15 -0
  31. package/src/enso-api/model/borrowActionDto.ts +18 -0
  32. package/src/enso-api/model/borrowArgsDto.ts +22 -0
  33. package/src/enso-api/model/borrowArgsDtoAmountOut.ts +16 -0
  34. package/src/enso-api/model/bridgeActionDto.ts +19 -0
  35. package/src/enso-api/model/bridgeArgsDto.ts +29 -0
  36. package/src/enso-api/model/bridgeArgsDtoAmountIn.ts +16 -0
  37. package/src/enso-api/model/bridgeArgsDtoCallbackItem.ts +64 -0
  38. package/src/enso-api/model/bundleControllerBundleShortcutTransactionBodyItem.ts +69 -0
  39. package/src/enso-api/model/callActionDto.ts +18 -0
  40. package/src/enso-api/model/callArgsDto.ts +29 -0
  41. package/src/enso-api/model/callArgsDtoArgsItem.ts +17 -0
  42. package/src/enso-api/model/callArgsDtoArgsItemAnyOf.ts +12 -0
  43. package/src/enso-api/model/callArgsDtoValue.ts +16 -0
  44. package/src/enso-api/model/callOutput.ts +15 -0
  45. package/src/enso-api/model/depositActionDto.ts +18 -0
  46. package/src/enso-api/model/depositArgsDto.ts +28 -0
  47. package/src/enso-api/model/depositArgsDtoAmountIn.ts +20 -0
  48. package/src/enso-api/model/depositArgsDtoAmountInOneOfItem.ts +13 -0
  49. package/src/enso-api/model/depositArgsDtoTokenIn.ts +15 -0
  50. package/src/enso-api/model/depositArgsDtoTokenOut.ts +15 -0
  51. package/src/enso-api/model/depositCLMMActionDto.ts +18 -0
  52. package/src/enso-api/model/depositCLMMArgsDto.ts +30 -0
  53. package/src/enso-api/model/depositCLMMArgsDtoAmountInItem.ts +17 -0
  54. package/src/enso-api/model/depositCLMMArgsDtoAmountInItemAnyOf.ts +12 -0
  55. package/src/enso-api/model/ensoFeeActionDto.ts +17 -0
  56. package/src/enso-api/model/ensoFeeArgsDto.ts +20 -0
  57. package/src/enso-api/model/ensoFeeArgsDtoAmount.ts +16 -0
  58. package/src/enso-api/model/feeActionDto.ts +17 -0
  59. package/src/enso-api/model/feeArgsDto.ts +22 -0
  60. package/src/enso-api/model/feeArgsDtoAmount.ts +16 -0
  61. package/src/enso-api/model/harvestActionDto.ts +19 -0
  62. package/src/enso-api/model/harvestArgsDto.ts +17 -0
  63. package/src/enso-api/model/index.ts +115 -4
  64. package/src/enso-api/model/mergeActionDto.ts +17 -0
  65. package/src/enso-api/model/mergeArgsDto.ts +22 -0
  66. package/src/enso-api/model/mergeArgsDtoAmountInItem.ts +13 -0
  67. package/src/enso-api/model/minAmountOutActionDto.ts +17 -0
  68. package/src/enso-api/model/minAmountOutArgsDto.ts +19 -0
  69. package/src/enso-api/model/minAmountOutArgsDtoAmountOut.ts +16 -0
  70. package/src/enso-api/model/minAmountOutArgsDtoMinAmountOut.ts +16 -0
  71. package/src/enso-api/model/multiDepositActionDto.ts +18 -0
  72. package/src/enso-api/model/multiDepositArgsDto.ts +24 -0
  73. package/src/enso-api/model/multiDepositArgsDtoAmountInItem.ts +17 -0
  74. package/src/enso-api/model/multiDepositArgsDtoAmountInItemAnyOf.ts +12 -0
  75. package/src/enso-api/model/multiOutSingleDepositActionDto.ts +18 -0
  76. package/src/enso-api/model/multiOutSingleDepositArgsDto.ts +24 -0
  77. package/src/enso-api/model/multiOutSingleDepositArgsDtoAmountIn.ts +16 -0
  78. package/src/enso-api/model/multiRedeemActionDto.ts +18 -0
  79. package/src/enso-api/model/multiRedeemArgs2Dto.ts +24 -0
  80. package/src/enso-api/model/multiRedeemArgs2DtoAmountIn.ts +16 -0
  81. package/src/enso-api/model/nontokenizedControllerRouteNontokenizedShorcutTransactionParams.ts +8 -0
  82. package/src/enso-api/model/nontokenizedControllerRouteNontokenizedShorcutTransactionRoutingStrategy.ts +1 -0
  83. package/src/enso-api/model/nontokenizedRouteShortcutTransaction.ts +31 -0
  84. package/src/enso-api/model/paymasterFeeActionDto.ts +17 -0
  85. package/src/enso-api/model/paymasterFeeArgsDto.ts +18 -0
  86. package/src/enso-api/model/paymasterFeeArgsDtoAmount.ts +16 -0
  87. package/src/enso-api/model/permitTransferFromActionDto.ts +18 -0
  88. package/src/enso-api/model/permitTransferFromArgsDto.ts +29 -0
  89. package/src/enso-api/model/permitTransferFromArgsDtoAmount.ts +20 -0
  90. package/src/enso-api/model/permitTransferFromArgsDtoAmountOneOfItem.ts +13 -0
  91. package/src/enso-api/model/permitTransferFromArgsDtoToken.ts +15 -0
  92. package/src/enso-api/model/positionModel.ts +14 -0
  93. package/src/enso-api/model/redeemActionDto.ts +18 -0
  94. package/src/enso-api/model/redeemArgsDto.ts +25 -0
  95. package/src/enso-api/model/redeemArgsDtoAmountIn.ts +16 -0
  96. package/src/enso-api/model/redeemArgsDtoTokenOut.ts +15 -0
  97. package/src/enso-api/model/redeemCLMMActionDto.ts +18 -0
  98. package/src/enso-api/model/redeemCLMMArgsDto.ts +24 -0
  99. package/src/enso-api/model/redeemCLMMArgsDtoLiquidity.ts +16 -0
  100. package/src/enso-api/model/repayActionDto.ts +18 -0
  101. package/src/enso-api/model/repayArgsDto.ts +22 -0
  102. package/src/enso-api/model/repayArgsDtoAmountIn.ts +16 -0
  103. package/src/enso-api/model/routeActionDto.ts +20 -0
  104. package/src/enso-api/model/routeArgsDto.ts +38 -0
  105. package/src/enso-api/model/routeArgsDtoAmountIn.ts +16 -0
  106. package/src/enso-api/model/singleDepositActionDto.ts +18 -0
  107. package/src/enso-api/model/singleDepositArgsDto.ts +24 -0
  108. package/src/enso-api/model/singleDepositArgsDtoAmountIn.ts +16 -0
  109. package/src/enso-api/model/singleRedeemActionDto.ts +18 -0
  110. package/src/enso-api/model/singleRedeemArgs2Dto.ts +24 -0
  111. package/src/enso-api/model/singleRedeemArgs2DtoAmountIn.ts +16 -0
  112. package/src/enso-api/model/slippageActionDto.ts +17 -0
  113. package/src/enso-api/model/slippageArgsDto.ts +18 -0
  114. package/src/enso-api/model/slippageArgsDtoAmountOut.ts +16 -0
  115. package/src/enso-api/model/splitActionDto.ts +17 -0
  116. package/src/enso-api/model/splitArgsDto.ts +22 -0
  117. package/src/enso-api/model/splitArgsDtoAmountIn.ts +16 -0
  118. package/src/enso-api/model/swapActionDto.ts +18 -0
  119. package/src/enso-api/model/swapArgsDto.ts +30 -0
  120. package/src/enso-api/model/swapArgsDtoAmountIn.ts +16 -0
  121. package/src/enso-api/model/tokenizedMultiDepositActionDto.ts +18 -0
  122. package/src/enso-api/model/tokenizedMultiDepositArgsDto.ts +24 -0
  123. package/src/enso-api/model/tokenizedMultiDepositArgsDtoAmountInItem.ts +17 -0
  124. package/src/enso-api/model/tokenizedMultiDepositArgsDtoAmountInItemAnyOf.ts +14 -0
  125. package/src/enso-api/model/tokenizedMultiRedeemActionDto.ts +18 -0
  126. package/src/enso-api/model/tokenizedMultiRedeemArgsDto.ts +24 -0
  127. package/src/enso-api/model/tokenizedMultiRedeemArgsDtoAmountIn.ts +16 -0
  128. package/src/enso-api/model/tokenizedSingleDepositActionDto.ts +18 -0
  129. package/src/enso-api/model/tokenizedSingleDepositArgsDto.ts +24 -0
  130. package/src/enso-api/model/tokenizedSingleDepositArgsDtoAmountIn.ts +16 -0
  131. package/src/enso-api/model/tokenizedSingleRedeemActionDto.ts +18 -0
  132. package/src/enso-api/model/tokenizedSingleRedeemArgsDto.ts +24 -0
  133. package/src/enso-api/model/tokenizedSingleRedeemArgsDtoAmountIn.ts +16 -0
  134. package/src/enso-api/model/tokensControllerTokensLiquidityType.ts +19 -0
  135. package/src/enso-api/model/tokensControllerTokensParams.ts +6 -0
  136. package/src/enso-api/model/transferActionDto.ts +18 -0
  137. package/src/enso-api/model/transferArgsDto.ts +22 -0
  138. package/src/enso-api/model/transferArgsDtoAmount.ts +16 -0
  139. package/src/enso-api/model/transferFromActionDto.ts +18 -0
  140. package/src/enso-api/model/transferFromArgsDto.ts +24 -0
  141. package/src/enso-api/model/transferFromArgsDtoAmount.ts +16 -0
  142. package/src/index.ts +1 -0
  143. package/src/store.ts +13 -5
  144. package/src/types/index.ts +9 -3
  145. package/src/util/common.tsx +4 -1
  146. package/src/util/enso-hooks.tsx +137 -14
  147. package/src/util/wallet.tsx +2 -69
  148. package/src/components/steps/WalletAmountStep.tsx +0 -267
  149. package/src/components/steps/WalletTokenStep.tsx +0 -128
@@ -11,8 +11,8 @@ import {
11
11
  } from "@chakra-ui/react";
12
12
  import { ChevronLeft, X, ArrowDownUpIcon } from "lucide-react";
13
13
  import { useContext, useEffect, useMemo, useState, useCallback } from "react";
14
- import { IconButton, Button, Tab, Input, Tooltip } from "../ui";
15
- import { useAccount, useSignMessage, useChainId, useSwitchChain } from "wagmi";
14
+ import { IconButton, Button, Tab, Input } from "../ui";
15
+ import { useAccount, useSignMessage } from "wagmi";
16
16
  import { getUserOperationHash } from "viem/account-abstraction";
17
17
  import {
18
18
  BodyWrapper,
@@ -36,40 +36,46 @@ import {
36
36
  formatUSD,
37
37
  normalizeValue,
38
38
  } from "@/util";
39
- import {
40
- useTokenFromListBySymbols,
41
- getChainName,
42
- precisionizeNumber,
43
- } from "@/util/common";
39
+ import { useTokenFromListBySymbols, precisionizeNumber } from "@/util/common";
44
40
  import {
45
41
  EXCHANGE_MAX_LIMIT_GAP_USD,
46
42
  EXCHANGE_MIN_LIMIT,
47
43
  } from "@/util/constants";
48
- import { useAppDetails, useRouteData } from "@/util/enso-hooks";
44
+ import {
45
+ useAppDetails,
46
+ useRouteData,
47
+ useSmartAccountBalances,
48
+ } from "@/util/enso-hooks";
49
49
  import QuoteParameters from "../QuoteParameters";
50
50
  import { TransactionDetailRow } from "../TransactionDetailRow";
51
- // @ts-ignore
52
- import SuccessIcon from "../../assets/success.svg";
53
- // @ts-ignore
54
- import FailIcon from "../../assets/fail.svg";
55
51
  import { CircleTimer } from "../CircleTimer";
56
52
  import { ConfirmExchangeStep } from "../ExchangeConfirmSecurity";
57
53
 
54
+ import SuccessIcon from "@/assets/success.svg";
55
+ import FailIcon from "@/assets/fail.svg";
56
+ import { SupportedExchanges } from "@/types";
57
+
58
58
  const ENTRY_POINT_ADDRESS: `0x${string}` =
59
59
  "0x0000000071727de22e5e9d8baf0edac6f37da032";
60
60
 
61
- // // Styled components
62
- // export const BodyWrapper = chakra("div", {
63
- // base: {
64
- // display: "flex",
65
- // flexDirection: "column",
66
- // width: "100%",
67
- // justifyContent: "center",
68
- // alignItems: "center",
69
- // gap: "16px",
70
- // paddingTop: "16px",
71
- // },
72
- // });
61
+ export const ExchangeToIntegrationType: Record<SupportedExchanges, string> = {
62
+ [SupportedExchanges.Binance]: "binanceInternationalDirect",
63
+ [SupportedExchanges.Kraken]: "krakenDirect",
64
+ [SupportedExchanges.Coinbase]: "coinbase",
65
+ [SupportedExchanges.Bybit]: "bybitDirect",
66
+ };
67
+
68
+ // Map Mesh broker types to icon URLs
69
+ export const EXCHANGE_ICON_BY_TYPE: Record<string, string> = {
70
+ [ExchangeToIntegrationType[SupportedExchanges.Binance]]:
71
+ "https://assets.coingecko.com/markets/images/52/large/binance.jpg",
72
+ [ExchangeToIntegrationType[SupportedExchanges.Kraken]]:
73
+ "https://assets.coingecko.com/markets/images/29/large/kraken.jpg",
74
+ [ExchangeToIntegrationType[SupportedExchanges.Coinbase]]:
75
+ "https://assets.coingecko.com/markets/images/23/large/Coinbase_Coin_Primary.png",
76
+ [ExchangeToIntegrationType[SupportedExchanges.Bybit]]:
77
+ "https://assets.coingecko.com/markets/images/698/large/bybit_spot.png",
78
+ };
73
79
 
74
80
  // Types for Mesh Holdings API response
75
81
  interface CryptocurrencyPosition {
@@ -115,7 +121,7 @@ interface SupportedToken {
115
121
  interface MatchedToken extends SupportedToken {
116
122
  balance: number;
117
123
  marketValue: number;
118
- holding: CryptocurrencyPosition;
124
+ holding?: CryptocurrencyPosition;
119
125
  }
120
126
 
121
127
  // const MESH_API_URL = "http://localhost:8787";
@@ -131,23 +137,30 @@ Withdrawal steps:
131
137
  7. Open transfer modal with amount and token
132
138
  */
133
139
 
134
- enum WithdrawalStep {
140
+ export enum WithdrawalStep {
135
141
  CheckSessionKey,
136
- GetHoldings,
137
- SelectAmount,
138
- GetUserOpSignature,
142
+ ChooseExchange,
143
+ ChooseExchangeAsset,
144
+ ChooseBalanceAsset,
145
+ ChooseAmount,
146
+ SignUserOp,
139
147
  InitiateWithdrawal,
140
148
  TrackUserOp,
141
149
  }
142
150
  const withdrawalSteps = [
151
+ WithdrawalStep.ChooseExchange,
143
152
  WithdrawalStep.CheckSessionKey,
144
- WithdrawalStep.GetHoldings,
145
- WithdrawalStep.SelectAmount,
146
- WithdrawalStep.GetUserOpSignature,
153
+ WithdrawalStep.ChooseExchangeAsset,
154
+ WithdrawalStep.ChooseAmount,
155
+ WithdrawalStep.SignUserOp,
147
156
  WithdrawalStep.InitiateWithdrawal,
148
157
  ];
149
-
150
- const BINANCE_INTEGRATION_ID = "9226e5c2-ebc3-4fdd-94f6-ed52cdce1420";
158
+ const balanceSteps = [
159
+ WithdrawalStep.ChooseBalanceAsset,
160
+ WithdrawalStep.ChooseAmount,
161
+ WithdrawalStep.SignUserOp,
162
+ ];
163
+ // Integration details are fetched dynamically from Mesh API.
151
164
 
152
165
  // Mesh network IDs for EVM chains (from Mesh networks API)
153
166
  const MESH_NETWORK_IDS: { [chainId: number]: string } = {
@@ -171,20 +184,138 @@ const getNetworkId = (chainId: number): string => {
171
184
 
172
185
  const useHandleMeshAccessPayload = () => {
173
186
  const { setMeshAccessToken } = useAppStore();
174
- const deviceKey = useDeviceKey();
175
-
176
- return useCallback((accessTokenPayload: AccessTokenPayload) => {
177
- setMeshAccessToken(accessTokenPayload); // Persist access token and session id for future reloads
178
-
179
- sessionStorage.setItem(
180
- deviceKey,
181
- JSON.stringify({
182
- accessToken: accessTokenPayload.accessToken,
183
- sessionId: accessTokenPayload.content.sessionId,
184
- timestamp: Date.now(),
185
- }),
187
+ const deviceKey = useDeviceId();
188
+ const selectedIntegration = useAppStore((s) => s.selectedIntegration);
189
+
190
+ return useCallback(
191
+ (accessTokenPayload: AccessTokenPayload, sessionId: string) => {
192
+ setMeshAccessToken(accessTokenPayload); // Persist access token and session id for future reloads
193
+
194
+ debugger;
195
+ sessionStorage.setItem(
196
+ `${deviceKey}:${selectedIntegration?.type}`,
197
+ JSON.stringify({
198
+ accessToken:
199
+ accessTokenPayload.accountTokens[0].accessToken,
200
+ sessionId,
201
+ timestamp: Date.now(),
202
+ }),
203
+ );
204
+ },
205
+ [deviceKey, selectedIntegration?.type],
206
+ );
207
+ };
208
+
209
+ type MeshIntegration = {
210
+ id: string;
211
+ type: string; // brokerType
212
+ name: string;
213
+ networks?: {
214
+ id: string;
215
+ nativeSymbol?: string;
216
+ name?: string;
217
+ chainId?: string | number;
218
+ }[];
219
+ };
220
+
221
+ const ChooseExchangeStep = ({
222
+ setStep,
223
+ }: {
224
+ setStep: (step: WithdrawalStep) => void;
225
+ }) => {
226
+ const { chainIdOut } = useAppStore();
227
+ const [integrations, setIntegrations] = useState<MeshIntegration[]>([]);
228
+ const [loading, setLoading] = useState(true);
229
+ const [error, setError] = useState<string | null>(null);
230
+ const setSelectedIntegration = useAppStore(
231
+ (state) => state.setSelectedIntegration,
232
+ );
233
+ const { enableExchange } = useContext(CheckoutContext);
234
+
235
+ useEffect(() => {
236
+ const fetchIntegrations = async () => {
237
+ try {
238
+ const res = await fetch(`${MESH_API_URL}/integrations`);
239
+ const data = await res.json();
240
+ const availableExchanges = enableExchange
241
+ .map((e) => ExchangeToIntegrationType[e])
242
+ .filter(Boolean);
243
+
244
+ // Filter integrations by current chain support
245
+ const filtered = data?.filter(
246
+ (i) =>
247
+ i.networks.some((n) => +n.chainId === chainIdOut) &&
248
+ availableExchanges.includes(i.type),
249
+ );
250
+
251
+ setIntegrations(filtered);
252
+ } catch (e) {
253
+ console.error("Failed to fetch integrations", e);
254
+ setError("Failed to load exchanges");
255
+ } finally {
256
+ setLoading(false);
257
+ }
258
+ };
259
+ fetchIntegrations();
260
+ }, [chainIdOut]);
261
+
262
+ if (loading)
263
+ return (
264
+ <Center>
265
+ <Spinner m={5} />
266
+ </Center>
186
267
  );
187
- }, []);
268
+ if (error)
269
+ return (
270
+ <BodyWrapper>
271
+ <Box p={5} color="red.500">
272
+ {error}
273
+ </Box>
274
+ </BodyWrapper>
275
+ );
276
+
277
+ return (
278
+ <BodyWrapper>
279
+ <Box mb={4} width="100%" textAlign="left">
280
+ <HeaderTitle>Choose Exchange</HeaderTitle>
281
+ </Box>
282
+
283
+ {integrations?.length > 0 ? (
284
+ <ListWrapper>
285
+ {integrations.map((integration) => (
286
+ <AssetCard
287
+ key={integration.id}
288
+ // chainId={chainIdOut || 1}
289
+ icon={EXCHANGE_ICON_BY_TYPE[integration.type]}
290
+ title={integration.name}
291
+ balance={""}
292
+ usdBalance=""
293
+ tag=""
294
+ loading={false}
295
+ selected={false}
296
+ onClick={() => {
297
+ setSelectedIntegration({
298
+ id: integration.id,
299
+ type: integration.type,
300
+ name: integration.name,
301
+ });
302
+ setStep(WithdrawalStep.CheckSessionKey);
303
+ }}
304
+ />
305
+ ))}
306
+ </ListWrapper>
307
+ ) : (
308
+ <Box
309
+ textAlign="center"
310
+ fontSize={"sm"}
311
+ color="fg.subtle"
312
+ py={3}
313
+ >
314
+ No exchanges available for specified target chain
315
+ </Box>
316
+ )}
317
+ </BodyWrapper>
318
+ );
188
319
  };
189
320
 
190
321
  const CheckSessionKeyStep = ({
@@ -192,26 +323,40 @@ const CheckSessionKeyStep = ({
192
323
  }: {
193
324
  setStep: (step: WithdrawalStep) => void;
194
325
  }) => {
195
- const { chainIdOut, setMeshAccessToken, setSessionId } = useAppStore();
326
+ const { chainIdOut, setMeshAccessToken, setSessionId, setChainIdIn } =
327
+ useAppStore();
196
328
  const { address } = useAccount();
197
- const deviceKey = useDeviceKey();
329
+ const deviceKey = useDeviceId();
198
330
  const [showConfirmation, setShowConfirmation] = useState(false);
331
+ const selectedIntegration = useAppStore((s) => s.selectedIntegration);
199
332
 
200
333
  const invalidChainId = chainIdOut && !MESH_NETWORKS.includes(chainIdOut);
201
334
  const handleMeshAccessPayload = useHandleMeshAccessPayload();
202
335
 
203
336
  useEffect(() => {
337
+ setChainIdIn(chainIdOut);
338
+ }, [chainIdOut]);
339
+
340
+ useEffect(() => {
341
+ if (!selectedIntegration) {
342
+ // ensure an exchange is selected
343
+ setStep(WithdrawalStep.ChooseExchange);
344
+ return;
345
+ }
204
346
  if (invalidChainId) return;
205
347
  // If connection is persisted, skip fetching a new link token
206
- const saved = sessionStorage.getItem(deviceKey);
348
+ const saved = sessionStorage.getItem(
349
+ `${deviceKey}:${selectedIntegration.type}`,
350
+ );
207
351
  // On load: check for persisted connection and hydrate state
352
+ debugger;
208
353
  if (saved) {
209
354
  try {
210
355
  const parsed = JSON.parse(saved);
211
356
  if (parsed?.accessToken && parsed?.sessionId) {
212
357
  setMeshAccessToken(parsed.accessToken);
213
358
  setSessionId(parsed.sessionId);
214
- setStep(WithdrawalStep.GetHoldings);
359
+ setStep(WithdrawalStep.ChooseExchangeAsset);
215
360
  return;
216
361
  }
217
362
  } catch (e) {
@@ -222,20 +367,24 @@ const CheckSessionKeyStep = ({
222
367
 
223
368
  // Show confirmation instead of auto-connecting
224
369
  setShowConfirmation(true);
225
- }, [deviceKey, invalidChainId]);
370
+ }, [deviceKey, invalidChainId, selectedIntegration]);
226
371
 
227
372
  const handleConfirmAuth = () => {
228
- fetch(`${MESH_API_URL}/linktoken`, {
229
- method: "POST",
230
- headers: {
231
- "Content-Type": "application/json",
373
+ const brokerType = selectedIntegration?.type;
374
+ fetch(
375
+ `${MESH_API_URL}/linktoken?brokerType=${encodeURIComponent(brokerType)}`,
376
+ {
377
+ method: "POST",
378
+ headers: {
379
+ "Content-Type": "application/json",
380
+ },
381
+ body: JSON.stringify({
382
+ userId: deviceKey,
383
+ integrationId: selectedIntegration?.id,
384
+ address,
385
+ }),
232
386
  },
233
- body: JSON.stringify({
234
- userId: deviceKey,
235
- integrationId: BINANCE_INTEGRATION_ID,
236
- address,
237
- }),
238
- })
387
+ )
239
388
  .then((response) => response.json())
240
389
  .then((response) => {
241
390
  setSessionId(response.content.sessionId);
@@ -245,8 +394,11 @@ const CheckSessionKeyStep = ({
245
394
  onIntegrationConnected: (payload) => {
246
395
  console.log("onIntegrationConnected", payload);
247
396
  meshLink.closeLink();
248
- setStep(WithdrawalStep.GetHoldings);
249
- handleMeshAccessPayload(payload.accessToken); // Persist access token and session id for future reloads
397
+ setStep(WithdrawalStep.ChooseExchangeAsset);
398
+ handleMeshAccessPayload(
399
+ payload.accessToken,
400
+ response.content.sessionId,
401
+ ); // Persist access token and session id for future reloads
250
402
  },
251
403
  onExit: (error) => {
252
404
  console.log("onExit", error);
@@ -287,21 +439,32 @@ const CheckSessionKeyStep = ({
287
439
  }
288
440
 
289
441
  if (showConfirmation) {
290
- return <ConfirmExchangeStep onConfirm={handleConfirmAuth} />;
442
+ return (
443
+ <ConfirmExchangeStep
444
+ onConfirm={handleConfirmAuth}
445
+ exchangeName={selectedIntegration?.name}
446
+ exchangeIcon={
447
+ selectedIntegration?.type
448
+ ? EXCHANGE_ICON_BY_TYPE[selectedIntegration.type]
449
+ : undefined
450
+ }
451
+ />
452
+ );
291
453
  }
292
454
 
293
455
  return <Spinner m={5} />;
294
456
  };
295
457
 
458
+ const DEVICE_ID_KEY = "meshDeviceId";
296
459
  // Generate a unique device ID to use as user id for Mesh
297
- const useDeviceKey = () => {
460
+ const useDeviceId = () => {
298
461
  return useMemo(() => {
299
- const deviceIdKey = "meshDeviceId";
300
- let deviceId = localStorage.getItem(deviceIdKey);
462
+ let deviceId = localStorage.getItem(DEVICE_ID_KEY);
463
+ console.log(deviceId);
301
464
 
302
465
  if (!deviceId) {
303
466
  deviceId = `device_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
304
- localStorage.setItem(deviceIdKey, deviceId);
467
+ localStorage.setItem(DEVICE_ID_KEY, deviceId);
305
468
  }
306
469
 
307
470
  return deviceId;
@@ -335,12 +498,14 @@ const ChooseAssetStep = ({
335
498
  setTokenIn,
336
499
  chainIdIn,
337
500
  } = useAppStore();
501
+ const deviceKey = useDeviceId();
502
+
503
+ const selectedIntegration = useAppStore((s) => s.selectedIntegration);
338
504
 
339
- const deviceKey = useDeviceKey();
340
505
  useEffect(() => {
341
506
  const fetchData = async () => {
342
507
  try {
343
- // Fetch holdings from Binance
508
+ // Fetch holdings for the selected exchange
344
509
  const holdingsResponse = await fetch(
345
510
  `${MESH_API_URL}/holdings`,
346
511
  {
@@ -352,7 +517,7 @@ const ChooseAssetStep = ({
352
517
  body: JSON.stringify({
353
518
  authToken:
354
519
  meshAccessToken.accountTokens[0].accessToken,
355
- brokerType: "binanceInternationalDirect",
520
+ brokerType: selectedIntegration?.type,
356
521
  }),
357
522
  },
358
523
  );
@@ -370,7 +535,9 @@ const ChooseAssetStep = ({
370
535
  setStep(WithdrawalStep.CheckSessionKey);
371
536
  setMeshAccessToken(null);
372
537
  setSessionId(null);
373
- sessionStorage.removeItem(deviceKey);
538
+ sessionStorage.removeItem(
539
+ `${deviceKey}:${selectedIntegration?.type}`,
540
+ );
374
541
  }
375
542
  throw new Error(
376
543
  holdingsData.message || "Failed to fetch holdings",
@@ -379,10 +546,9 @@ const ChooseAssetStep = ({
379
546
 
380
547
  // Fetch supported tokens for current chain
381
548
  const tokensResponse = await fetch(
382
- `${MESH_API_URL}/tokens?chainId=${chainIdIn}`,
549
+ `${MESH_API_URL}/tokens?chainId=${chainIdIn}&brokerType=${encodeURIComponent(selectedIntegration?.type || "")}`,
383
550
  );
384
551
  const tokensData = await tokensResponse.json();
385
- console.log("Tokens data:", tokensData);
386
552
 
387
553
  if (
388
554
  tokensData.status === "success" &&
@@ -433,18 +599,16 @@ const ChooseAssetStep = ({
433
599
  }
434
600
  };
435
601
 
436
- if (meshAccessToken && sessionId && chainIdOut) {
602
+ if (meshAccessToken && sessionId && chainIdOut && selectedIntegration) {
437
603
  fetchData();
438
604
  }
439
- }, [address, chainIdOut, meshAccessToken, sessionId]);
605
+ }, [address, chainIdOut, meshAccessToken, sessionId, selectedIntegration]);
440
606
 
441
607
  const geckoTokens = useTokenFromListBySymbols(
442
608
  matchedTokens.map((token) => token.symbol),
443
609
  chainIdIn,
444
610
  );
445
611
 
446
- console.log("geckoTokens", geckoTokens);
447
-
448
612
  if (loading)
449
613
  return (
450
614
  <Center>
@@ -500,7 +664,7 @@ const ChooseAssetStep = ({
500
664
  <Button
501
665
  disabled={!selectedTokenSymbol}
502
666
  onClick={() => {
503
- setStep(WithdrawalStep.SelectAmount);
667
+ setStep(WithdrawalStep.ChooseAmount);
504
668
  }}
505
669
  >
506
670
  Continue
@@ -510,6 +674,105 @@ const ChooseAssetStep = ({
510
674
  );
511
675
  };
512
676
 
677
+ const ChooseDelayedBalance = ({
678
+ setStep,
679
+ onTokenSelect,
680
+ }: {
681
+ setStep: (step: WithdrawalStep) => void;
682
+ onTokenSelect: (token: MatchedToken) => void;
683
+ }) => {
684
+ const { chainIdIn, setTokenIn, setChainIdIn } = useAppStore();
685
+ const [selectedToken, setSelectedToken] = useState<string | null>(null);
686
+
687
+ // Get smart account balances
688
+ const { holdingsList, total, isLoading } = useSmartAccountBalances(1);
689
+ const setSelectedIntegration = useAppStore(
690
+ (state) => state.setSelectedIntegration,
691
+ );
692
+
693
+ useEffect(() => {
694
+ setSelectedIntegration({
695
+ type: "delayed",
696
+ name: "Smart account",
697
+ id: "",
698
+ });
699
+ }, []);
700
+
701
+ if (isLoading) {
702
+ return (
703
+ <Center>
704
+ <Spinner m={5} />
705
+ </Center>
706
+ );
707
+ }
708
+
709
+ return (
710
+ <BodyWrapper>
711
+ <Box mb={4} width="100%" textAlign="left">
712
+ <HeaderDescription>
713
+ Smart Account Balance: {formatUSD(total)}
714
+ </HeaderDescription>
715
+ </Box>
716
+ <Box overflowY={"scroll"} maxH={"400px"}>
717
+ <ListWrapper>
718
+ {holdingsList?.map((asset) => (
719
+ <AssetCard
720
+ key={`${asset.token}-${asset.chainId}`}
721
+ chainId={asset.chainId}
722
+ icon={asset.logoUri}
723
+ title={asset.name}
724
+ balance={`${formatNumber(
725
+ normalizeValue(asset.amount, asset.decimals),
726
+ )} ${asset.symbol}`}
727
+ usdBalance={formatUSD(asset.total)}
728
+ tag=""
729
+ loading={false}
730
+ selected={
731
+ selectedToken === asset.token &&
732
+ chainIdIn === asset.chainId
733
+ }
734
+ onClick={() => {
735
+ setSelectedToken(asset.token);
736
+ setTokenIn(asset.token);
737
+ setChainIdIn(asset.chainId);
738
+ // Mock MatchedToken from balance data
739
+ const mockMatchedToken: MatchedToken = {
740
+ symbol: asset.symbol,
741
+ name: asset.name,
742
+ networkId: asset.chainId.toString(),
743
+ chainId: asset.chainId,
744
+ integrationNetworks: [],
745
+ balance: Number(
746
+ normalizeValue(
747
+ asset.amount,
748
+ asset.decimals,
749
+ ),
750
+ ),
751
+ marketValue: asset.total,
752
+ };
753
+ onTokenSelect(mockMatchedToken);
754
+ }}
755
+ />
756
+ ))}
757
+ </ListWrapper>
758
+ </Box>
759
+ {holdingsList?.length === 0 && (
760
+ <Box textAlign="center" color="fg.subtle" py={8}>
761
+ No tokens found in smart account
762
+ </Box>
763
+ )}
764
+ <Button
765
+ disabled={!selectedToken}
766
+ onClick={() => {
767
+ setStep(WithdrawalStep.ChooseAmount);
768
+ }}
769
+ >
770
+ Continue
771
+ </Button>
772
+ </BodyWrapper>
773
+ );
774
+ };
775
+
513
776
  const ChooseAmountStep = ({
514
777
  setStep,
515
778
  selectedToken,
@@ -521,7 +784,7 @@ const ChooseAmountStep = ({
521
784
  const [inputMode, setInputMode] = useState<"usd" | "token">("usd");
522
785
  const [usdValue, setUsdValue] = useState<string>("");
523
786
  const { setAmountIn } = useAppStore();
524
- const { tokenInData, tokenIn } = useAppDetails();
787
+ const { tokenInData } = useAppDetails();
525
788
  const isStable = selectedToken?.symbol.toLowerCase().includes("USD");
526
789
  const roundingPrecision = isStable ? 2 : 6;
527
790
 
@@ -770,7 +1033,7 @@ const ChooseAmountStep = ({
770
1033
  onClick={() =>
771
1034
  isAmountInvalid || !amount
772
1035
  ? undefined
773
- : setStep(WithdrawalStep.GetUserOpSignature)
1036
+ : setStep(WithdrawalStep.SignUserOp)
774
1037
  }
775
1038
  disabled={isAmountInvalid || !amount}
776
1039
  >
@@ -783,7 +1046,9 @@ const ChooseAmountStep = ({
783
1046
  const SignUserOpStep = ({
784
1047
  setStep,
785
1048
  setUserOp,
1049
+ nextStep,
786
1050
  }: {
1051
+ nextStep: WithdrawalStep;
787
1052
  setStep: (step: WithdrawalStep) => void;
788
1053
  setUserOp: (userOp: any) => void;
789
1054
  }) => {
@@ -820,8 +1085,6 @@ const SignUserOpStep = ({
820
1085
  chainId: chainIdIn,
821
1086
  });
822
1087
 
823
- console.log("Signing userOpHash:", userOpHash);
824
-
825
1088
  // Sign the userOperation hash directly
826
1089
  const signature = await signMessageAsync({
827
1090
  account: address as `0x${string}`,
@@ -837,9 +1100,7 @@ const SignUserOpStep = ({
837
1100
  console.log("signedUserOp", JSON.stringify(signedUserOp));
838
1101
 
839
1102
  setUserOp(signedUserOp);
840
- if (sessionStorage.getItem("sendUserOp"))
841
- setStep(WithdrawalStep.TrackUserOp);
842
- else setStep(WithdrawalStep.InitiateWithdrawal);
1103
+ setStep(nextStep);
843
1104
  } catch (error) {
844
1105
  console.error("Failed to sign userOperation:", error);
845
1106
  } finally {
@@ -902,10 +1163,12 @@ const InitiateWithdrawalStep = ({
902
1163
  const { meshAccessToken, amountIn, chainIdOut } = useAppStore();
903
1164
  const { address } = useAccount();
904
1165
  const { tokenInData } = useAppDetails();
1166
+ const sessionId = useAppStore((state) => state.sessionId);
905
1167
  const [isLoading, setIsLoading] = useState(true);
1168
+ const selectedIntegration = useAppStore((s) => s.selectedIntegration);
906
1169
 
907
1170
  const handleMeshAccessPayload = useHandleMeshAccessPayload();
908
- const deviceKey = useDeviceKey();
1171
+ const deviceKey = useDeviceId();
909
1172
 
910
1173
  useEffect(() => {
911
1174
  if (!selectedToken || !userOp || !meshAccessToken) {
@@ -933,7 +1196,7 @@ const InitiateWithdrawalStep = ({
933
1196
  const meshData = {
934
1197
  restrictMultipleAccounts: true,
935
1198
  userId: deviceKey,
936
- integrationId: BINANCE_INTEGRATION_ID,
1199
+ integrationId: selectedIntegration?.id,
937
1200
  transferOptions: {
938
1201
  toAddresses,
939
1202
  },
@@ -941,31 +1204,35 @@ const InitiateWithdrawalStep = ({
941
1204
 
942
1205
  console.log("link request body", meshData);
943
1206
 
944
- const response = await fetch(`${MESH_API_URL}/linktoken`, {
945
- method: "POST",
946
- headers: {
947
- "Content-Type": "application/json",
948
- },
949
- body: JSON.stringify({
950
- restrictMultipleAccounts: true,
951
- userId: deviceKey,
952
- integrationId: BINANCE_INTEGRATION_ID,
953
- transferOptions: {
954
- toAddresses,
1207
+ const response = await fetch(
1208
+ `${MESH_API_URL}/linktoken?brokerType=${encodeURIComponent(selectedIntegration?.type || "")}`,
1209
+ {
1210
+ method: "POST",
1211
+ headers: {
1212
+ "Content-Type": "application/json",
955
1213
  },
956
- }),
957
- });
1214
+ body: JSON.stringify({
1215
+ restrictMultipleAccounts: true,
1216
+ userId: deviceKey,
1217
+ integrationId: selectedIntegration?.id,
1218
+ transferOptions: {
1219
+ toAddresses,
1220
+ },
1221
+ }),
1222
+ },
1223
+ );
958
1224
 
959
1225
  const data = await response.json();
960
1226
  console.log("Link token response:", data);
961
1227
 
1228
+ // @ts-ignore
962
1229
  const accessTokens: IntegrationAccessToken[] =
963
1230
  meshAccessToken.accountTokens.map((token) => ({
964
1231
  ...token,
965
1232
  accountId: token.account.accountId,
966
1233
  accountName: token.account.accountName,
967
- brokerName: "Binance",
968
- brokerType: "binanceInternationalDirect",
1234
+ brokerName: selectedIntegration?.name,
1235
+ brokerType: selectedIntegration?.type,
969
1236
  }));
970
1237
 
971
1238
  console.log("accessTokens", accessTokens);
@@ -974,7 +1241,7 @@ const InitiateWithdrawalStep = ({
974
1241
  clientId: address,
975
1242
  accessTokens,
976
1243
  onIntegrationConnected: (payload) => {
977
- handleMeshAccessPayload(payload.accessToken); // Persist access token and session id for future reloads
1244
+ handleMeshAccessPayload(payload.accessToken, sessionId); // Persist access token and session id for future reloads
978
1245
  console.log("Integration connected", payload);
979
1246
  },
980
1247
  onTransferFinished: (transferData) => {
@@ -984,7 +1251,7 @@ const InitiateWithdrawalStep = ({
984
1251
  onExit: (error) => {
985
1252
  console.log("Mesh link exited:", error);
986
1253
  setIsLoading(false);
987
- setStep(WithdrawalStep.GetHoldings);
1254
+ setStep(WithdrawalStep.ChooseExchangeAsset);
988
1255
  },
989
1256
  onEvent: (ev) => {
990
1257
  console.log("Mesh event:", ev);
@@ -1325,54 +1592,64 @@ const TrackUserOpStep = ({
1325
1592
  );
1326
1593
  };
1327
1594
 
1328
- const ExchangeFlow = () => {
1329
- const { handleClose, setFlow, setStep } = useContext(CheckoutContext);
1330
- const [currentStep, setCurrentStep] = useState(
1331
- WithdrawalStep.CheckSessionKey,
1332
- );
1595
+ const ExchangeFlow = ({
1596
+ setFlow,
1597
+ initialStep = WithdrawalStep.ChooseExchange,
1598
+ }: {
1599
+ setFlow: (string) => void;
1600
+ initialStep?: WithdrawalStep;
1601
+ }) => {
1602
+ const { handleClose } = useContext(CheckoutContext);
1603
+ const [currentStep, setCurrentStep] = useState(initialStep);
1333
1604
  const [selectedToken, setSelectedToken] = useState<MatchedToken | null>(
1334
1605
  null,
1335
1606
  );
1336
1607
  const [userOp, setUserOp] = useState<any | null>(null);
1337
- const { chainIdOut } = useAppStore();
1338
- const setIsCheckout = useAppStore((state) => state.setIsCheckout);
1339
- const setChainIdIn = useAppStore((state) => state.setChainIdIn);
1340
- // const walletChainId = useChainId();
1341
- // const { switchChain } = useSwitchChain();
1342
- //
1343
- // const wrongChain = walletChainId !== chainIdOut;
1344
-
1345
- const handleTokenSelect = (token: MatchedToken) => {
1346
- setSelectedToken(token);
1347
- console.log("Selected token:", token);
1348
- };
1608
+ const setSelectedIntegration = useAppStore(
1609
+ (state) => state.setSelectedIntegration,
1610
+ );
1611
+ const selectedIntegration = useAppStore((s) => s.selectedIntegration);
1349
1612
 
1350
1613
  useEffect(() => {
1351
- setChainIdIn(chainIdOut);
1352
- setIsCheckout(true);
1353
- }, [chainIdOut]);
1614
+ return () => setSelectedIntegration(null);
1615
+ }, []);
1354
1616
 
1355
1617
  const currentStepComponent = (() => {
1356
1618
  switch (currentStep) {
1619
+ case WithdrawalStep.ChooseExchange:
1620
+ return <ChooseExchangeStep setStep={setCurrentStep} />;
1357
1621
  case WithdrawalStep.CheckSessionKey:
1358
1622
  return <CheckSessionKeyStep setStep={setCurrentStep} />;
1359
- case WithdrawalStep.GetHoldings:
1623
+ case WithdrawalStep.ChooseExchangeAsset:
1360
1624
  return (
1361
1625
  <ChooseAssetStep
1362
1626
  setStep={setCurrentStep}
1363
- onTokenSelect={handleTokenSelect}
1627
+ onTokenSelect={setSelectedToken}
1364
1628
  />
1365
1629
  );
1366
- case WithdrawalStep.SelectAmount:
1630
+ case WithdrawalStep.ChooseBalanceAsset:
1631
+ return (
1632
+ <ChooseDelayedBalance
1633
+ setStep={setCurrentStep}
1634
+ onTokenSelect={setSelectedToken}
1635
+ />
1636
+ );
1637
+ case WithdrawalStep.ChooseAmount:
1367
1638
  return (
1368
1639
  <ChooseAmountStep
1369
1640
  setStep={setCurrentStep}
1370
1641
  selectedToken={selectedToken}
1371
1642
  />
1372
1643
  );
1373
- case WithdrawalStep.GetUserOpSignature:
1644
+ case WithdrawalStep.SignUserOp:
1374
1645
  return (
1375
1646
  <SignUserOpStep
1647
+ nextStep={
1648
+ // skip withdrawal if use existing balance
1649
+ selectedToken.holding
1650
+ ? WithdrawalStep.InitiateWithdrawal
1651
+ : WithdrawalStep.TrackUserOp
1652
+ }
1376
1653
  setStep={setCurrentStep}
1377
1654
  setUserOp={setUserOp}
1378
1655
  />
@@ -1408,14 +1685,14 @@ const ExchangeFlow = () => {
1408
1685
  maxWidth={"16px"}
1409
1686
  onClick={() => {
1410
1687
  const index =
1411
- withdrawalSteps.findIndex(
1412
- (step) => step === currentStep,
1413
- ) - 1;
1688
+ (selectedIntegration?.type === "delayed"
1689
+ ? balanceSteps
1690
+ : withdrawalSteps
1691
+ ).findIndex((step) => step === currentStep) - 1;
1414
1692
  if (index > 0) {
1415
1693
  setCurrentStep(withdrawalSteps[index]);
1416
1694
  } else {
1417
1695
  setFlow("");
1418
- setStep("");
1419
1696
  }
1420
1697
  }}
1421
1698
  >
@@ -1434,7 +1711,9 @@ const ExchangeFlow = () => {
1434
1711
  alignItems={"center"}
1435
1712
  width="100%"
1436
1713
  >
1437
- <HeaderTitle>Deposit from Binance</HeaderTitle>
1714
+ <HeaderTitle>
1715
+ Deposit from {selectedIntegration?.name}
1716
+ </HeaderTitle>
1438
1717
  </Box>
1439
1718
 
1440
1719
  {handleClose && (