@ensofinance/checkout-widget 0.1.8 → 0.1.9

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 (61) hide show
  1. package/dist/checkout-widget.es.js +24133 -25422
  2. package/dist/checkout-widget.umd.js +59 -65
  3. package/dist/index.d.ts +1 -3
  4. package/package.json +2 -2
  5. package/src/assets/usdc.webp +0 -0
  6. package/src/assets/usdt.webp +0 -0
  7. package/src/components/AmountInput.tsx +25 -41
  8. package/src/components/ChakraProvider.tsx +13 -36
  9. package/src/components/Checkout.tsx +0 -3
  10. package/src/components/CurrencySwapDisplay.tsx +22 -59
  11. package/src/components/DepositProcessing.tsx +1 -1
  12. package/src/components/ExchangeConfirmSecurity.tsx +1 -1
  13. package/src/components/QuoteParameters.tsx +1 -1
  14. package/src/components/TransactionDetailRow.tsx +2 -2
  15. package/src/components/cards/ExchangeCard.tsx +1 -1
  16. package/src/components/cards/OptionCard.tsx +1 -2
  17. package/src/components/cards/WalletCard.tsx +1 -1
  18. package/src/components/modal.tsx +3 -3
  19. package/src/components/steps/ExchangeFlow.tsx +1404 -231
  20. package/src/components/steps/FlowSelector.tsx +60 -117
  21. package/src/components/steps/WalletFlow/WalletAmountStep.tsx +2 -2
  22. package/src/components/steps/WalletFlow/WalletConfirmStep.tsx +51 -92
  23. package/src/components/steps/WalletFlow/WalletFlow.tsx +16 -17
  24. package/src/components/steps/WalletFlow/WalletQuoteStep.tsx +2 -2
  25. package/src/components/steps/WalletFlow/WalletTokenStep.tsx +4 -6
  26. package/src/components/ui/index.tsx +6 -23
  27. package/src/components/ui/toaster.tsx +1 -2
  28. package/src/types/index.ts +0 -97
  29. package/src/util/constants.tsx +0 -27
  30. package/src/util/enso-hooks.tsx +61 -75
  31. package/dist/checkout-widget.es.js.map +0 -1
  32. package/dist/checkout-widget.umd.js.map +0 -1
  33. package/src/assets/providers/alchemypay.svg +0 -21
  34. package/src/assets/providers/banxa.svg +0 -21
  35. package/src/assets/providers/binanceconnect.svg +0 -14
  36. package/src/assets/providers/kryptonim.svg +0 -6
  37. package/src/assets/providers/mercuryo.svg +0 -21
  38. package/src/assets/providers/moonpay.svg +0 -14
  39. package/src/assets/providers/stripe.svg +0 -16
  40. package/src/assets/providers/swapped.svg +0 -1
  41. package/src/assets/providers/topper.svg +0 -14
  42. package/src/assets/providers/transak.svg +0 -21
  43. package/src/assets/providers/unlimit.svg +0 -21
  44. package/src/components/steps/CardBuyFlow/CardBuyFlow.tsx +0 -412
  45. package/src/components/steps/CardBuyFlow/ChooseAmountStep.tsx +0 -352
  46. package/src/components/steps/CardBuyFlow/OpenWidgetStep.tsx +0 -193
  47. package/src/components/steps/SmartAccountFlow.tsx +0 -372
  48. package/src/components/steps/shared/ChooseAmountStep.tsx +0 -325
  49. package/src/components/steps/shared/SignUserOpStep.tsx +0 -117
  50. package/src/components/steps/shared/TrackUserOpStep.tsx +0 -625
  51. package/src/components/steps/shared/exchangeIntegration.ts +0 -19
  52. package/src/components/steps/shared/types.ts +0 -22
  53. package/src/components/ui/transitions.tsx +0 -16
  54. package/src/enso-api/model/bridgeTransactionResponse.ts +0 -37
  55. package/src/enso-api/model/bridgeTransactionResponseStatus.ts +0 -25
  56. package/src/enso-api/model/ensoEvent.ts +0 -30
  57. package/src/enso-api/model/ensoMetadata.ts +0 -23
  58. package/src/enso-api/model/layerZeroControllerCheckBridgeTransactionParams.ts +0 -21
  59. package/src/enso-api/model/layerZeroMessageStatus.ts +0 -39
  60. package/src/enso-api/model/refundDetails.ts +0 -21
  61. package/src/util/meld-hooks.tsx +0 -533
@@ -1,372 +0,0 @@
1
- import { Center, Spinner, Box, Icon, Text, Flex } from "@chakra-ui/react";
2
- import { ChevronLeft, X } from "lucide-react";
3
- import { useContext, useEffect, useState } from "react";
4
- import { useReadContract } from "wagmi";
5
- import { erc20Abi } from "viem";
6
-
7
- import {
8
- BodyWrapper,
9
- HeaderDescription,
10
- HeaderTitle,
11
- HeaderWrapper,
12
- ListWrapper,
13
- } from "../ui/styled";
14
- import { IconButton, Button } from "../ui";
15
- import { CheckoutContext } from "../Checkout";
16
- import Modal from "../modal";
17
- import { useAppStore } from "@/store";
18
- import { AssetCard } from "../cards";
19
- import { formatNumber, formatUSD, normalizeValue } from "@/util";
20
- import {
21
- useAppDetails,
22
- useSmartAccountAddress,
23
- useSmartAccountBalances,
24
- } from "@/util/enso-hooks";
25
- import ChooseAmountStep from "./shared/ChooseAmountStep";
26
- import SignUserOpStep from "./shared/SignUserOpStep";
27
- import TrackUserOpStep from "./shared/TrackUserOpStep";
28
- import type { MatchedToken } from "./shared/types";
29
- import { AnimatedStep } from "../ui/transitions";
30
-
31
- export enum SmartAccountStep {
32
- ChooseAsset = 0,
33
- ChooseAmount = 1,
34
- SignUserOp = 2,
35
- TrackUserOp = 3,
36
- }
37
-
38
- const smartAccountPreviousStep: Partial<
39
- Record<SmartAccountStep, SmartAccountStep>
40
- > = {
41
- [SmartAccountStep.ChooseAmount]: SmartAccountStep.ChooseAsset,
42
- [SmartAccountStep.SignUserOp]: SmartAccountStep.ChooseAmount,
43
- };
44
-
45
- const ChooseDelayedBalance = ({
46
- setStep,
47
- onTokenSelect,
48
- notice,
49
- }: {
50
- setStep: (step: SmartAccountStep) => void;
51
- onTokenSelect: (token: MatchedToken) => void;
52
- notice?: string | null;
53
- }) => {
54
- const { chainIdIn, setTokenIn, setChainIdIn } = useAppStore();
55
- const [selectedToken, setSelectedToken] = useState<string | null>(null);
56
-
57
- const { holdingsList, total, isLoading } = useSmartAccountBalances(1);
58
-
59
- if (isLoading) {
60
- return (
61
- <Center>
62
- <Spinner m={5} />
63
- </Center>
64
- );
65
- }
66
-
67
- return (
68
- <BodyWrapper>
69
- {notice ? (
70
- <Box mb={3} width="100%" textAlign="left">
71
- <Text fontSize="sm" color="fg.muted">
72
- {notice}
73
- </Text>
74
- </Box>
75
- ) : null}
76
- <Box mb={4} width="100%" textAlign="left">
77
- <HeaderDescription>
78
- Smart Account Balance: {formatUSD(total)}
79
- </HeaderDescription>
80
- </Box>
81
- <Box overflowY={"scroll"} maxH={"400px"}>
82
- <ListWrapper>
83
- {holdingsList?.map((asset) => (
84
- <AssetCard
85
- key={`${asset.token}-${asset.chainId}`}
86
- chainId={asset.chainId}
87
- icon={asset.logoUri}
88
- title={asset.name}
89
- balance={`${formatNumber(
90
- normalizeValue(asset.amount, asset.decimals),
91
- )} ${asset.symbol}`}
92
- usdBalance={formatUSD(asset.total)}
93
- tag=""
94
- loading={false}
95
- selected={
96
- selectedToken === asset.token &&
97
- chainIdIn === asset.chainId
98
- }
99
- onClick={() => {
100
- setSelectedToken(asset.token);
101
- setTokenIn(asset.token);
102
- setChainIdIn(asset.chainId);
103
- const mockMatchedToken: MatchedToken = {
104
- symbol: asset.symbol,
105
- name: asset.name,
106
- networkId: asset.chainId.toString(),
107
- chainId: asset.chainId,
108
- integrationNetworks: [],
109
- tokenAddress: asset.token,
110
- balance: Number(
111
- normalizeValue(
112
- asset.amount,
113
- asset.decimals,
114
- ),
115
- ),
116
- marketValue: asset.total,
117
- };
118
- onTokenSelect(mockMatchedToken);
119
- }}
120
- />
121
- ))}
122
- </ListWrapper>
123
- </Box>
124
- {holdingsList?.length === 0 ? (
125
- <Box textAlign="center" color="fg.subtle" py={8}>
126
- No tokens found in smart account
127
- </Box>
128
- ) : null}
129
- <Button
130
- disabled={!selectedToken}
131
- onClick={() => {
132
- setStep(SmartAccountStep.ChooseAmount);
133
- }}
134
- >
135
- Continue
136
- </Button>
137
- </BodyWrapper>
138
- );
139
- };
140
-
141
- const SmartAccountFlow = ({
142
- setFlow,
143
- presetAmount,
144
- }: {
145
- setFlow: (flow: string) => void;
146
- presetAmount?: boolean;
147
- }) => {
148
- const { handleClose, enforceFlow } = useContext(CheckoutContext);
149
- const [currentStep, setCurrentStep] = useState<SmartAccountStep>(
150
- SmartAccountStep.ChooseAsset,
151
- );
152
- const [selectedToken, setSelectedToken] = useState<MatchedToken | null>(
153
- null,
154
- );
155
- const [userOp, setUserOp] = useState<any | null>(null);
156
- const [launchNotice, setLaunchNotice] = useState<string | null>(null);
157
- const [isBootstrapping, setIsBootstrapping] = useState(!!presetAmount);
158
-
159
- const setSelectedIntegration = useAppStore(
160
- (state) => state.setSelectedIntegration,
161
- );
162
-
163
- const { smartAccountAddress } = useSmartAccountAddress();
164
-
165
- const { tokenInData, tokenInPrice, chainIdIn, tokenIn } = useAppDetails();
166
-
167
- const { data: launchRpcBalance } = useReadContract({
168
- chainId: chainIdIn,
169
- address: tokenIn as `0x${string}`,
170
- abi: erc20Abi,
171
- functionName: "balanceOf",
172
- args: [smartAccountAddress],
173
- query: {
174
- enabled: !!presetAmount && !!smartAccountAddress,
175
- refetchInterval: isBootstrapping ? 3000 : false,
176
- staleTime: 0,
177
- },
178
- });
179
-
180
- // Set delayed integration while mounted
181
- useEffect(() => {
182
- setSelectedIntegration({
183
- type: "delayed",
184
- name: "Smart account",
185
- id: "",
186
- });
187
- return () => setSelectedIntegration(null);
188
- }, [setSelectedIntegration]);
189
-
190
- // Card-buy bootstrap: resolve token and jump to sign step
191
- useEffect(() => {
192
- if (!presetAmount || !isBootstrapping) return;
193
- if (!chainIdIn || !tokenIn || !smartAccountAddress) return;
194
- if (typeof launchRpcBalance !== "bigint") return;
195
- if (!tokenInData) return;
196
-
197
- const decimals = tokenInData.decimals;
198
- const symbol = tokenInData.symbol;
199
- const name = tokenInData.name;
200
-
201
- const normalizedBalance = Number(
202
- normalizeValue(launchRpcBalance, decimals),
203
- );
204
- if (!Number.isFinite(normalizedBalance)) return;
205
-
206
- const resolvedPrice = Number(tokenInPrice ?? 0);
207
- const resolvedMarketValue =
208
- resolvedPrice > 0 ? normalizedBalance * resolvedPrice : 0;
209
-
210
- const synthesizedToken: MatchedToken = {
211
- symbol,
212
- name,
213
- networkId: chainIdIn.toString(),
214
- chainId: chainIdIn,
215
- integrationNetworks: [],
216
- tokenAddress: tokenIn,
217
- balance: normalizedBalance,
218
- marketValue: resolvedMarketValue,
219
- };
220
-
221
- setSelectedToken(synthesizedToken);
222
- setCurrentStep(SmartAccountStep.SignUserOp);
223
- setIsBootstrapping(false);
224
- }, [
225
- isBootstrapping,
226
- presetAmount,
227
- chainIdIn,
228
- tokenIn,
229
- launchRpcBalance,
230
- tokenInData,
231
- tokenInPrice,
232
- smartAccountAddress,
233
- ]);
234
-
235
- // Timeout fallback for bootstrap
236
- useEffect(() => {
237
- if (!presetAmount || !isBootstrapping) return;
238
-
239
- const timer = window.setTimeout(() => {
240
- setIsBootstrapping(false);
241
- setLaunchNotice(
242
- "We could not auto-open the quote yet. Select your Smart Account USDC balance to continue.",
243
- );
244
- setCurrentStep(SmartAccountStep.ChooseAsset);
245
- }, 12000);
246
-
247
- return () => window.clearTimeout(timer);
248
- }, [isBootstrapping, presetAmount]);
249
-
250
- const currentStepComponent = (() => {
251
- if (presetAmount && isBootstrapping) {
252
- return (
253
- <BodyWrapper>
254
- <Flex direction="column" gap={3} align="center" py={8}>
255
- <Spinner />
256
- <Text color="fg.muted" fontSize="sm" textAlign="center">
257
- Preparing your Smart Account quote...
258
- </Text>
259
- </Flex>
260
- </BodyWrapper>
261
- );
262
- }
263
-
264
- switch (currentStep) {
265
- case SmartAccountStep.ChooseAsset:
266
- return (
267
- <ChooseDelayedBalance
268
- setStep={setCurrentStep}
269
- onTokenSelect={setSelectedToken}
270
- notice={launchNotice}
271
- />
272
- );
273
- case SmartAccountStep.ChooseAmount:
274
- return (
275
- <ChooseAmountStep
276
- setStep={setCurrentStep}
277
- nextStep={SmartAccountStep.SignUserOp}
278
- selectedToken={selectedToken}
279
- mode="smart-account"
280
- />
281
- );
282
- case SmartAccountStep.SignUserOp:
283
- return (
284
- <SignUserOpStep
285
- nextStep={SmartAccountStep.TrackUserOp}
286
- setStep={setCurrentStep}
287
- setUserOp={setUserOp}
288
- />
289
- );
290
- case SmartAccountStep.TrackUserOp:
291
- return (
292
- <TrackUserOpStep
293
- userOp={userOp}
294
- onReset={() =>
295
- setCurrentStep(SmartAccountStep.ChooseAsset)
296
- }
297
- />
298
- );
299
- default:
300
- return null;
301
- }
302
- })();
303
-
304
- return (
305
- <>
306
- <Modal.Header>
307
- <HeaderWrapper>
308
- {!(
309
- enforceFlow &&
310
- currentStep === SmartAccountStep.ChooseAsset
311
- ) && (
312
- <IconButton
313
- minWidth={"16px"}
314
- minHeight={"16px"}
315
- maxWidth={"16px"}
316
- onClick={() => {
317
- const previousStep =
318
- smartAccountPreviousStep[currentStep];
319
- if (previousStep !== undefined) {
320
- setCurrentStep(previousStep);
321
- } else {
322
- setFlow("");
323
- }
324
- }}
325
- >
326
- <Icon
327
- as={ChevronLeft}
328
- color="fg.muted"
329
- width={"16px"}
330
- height={"16px"}
331
- />
332
- </IconButton>
333
- )}
334
-
335
- <Box
336
- display="flex"
337
- flexDirection="column"
338
- gap={"4px"}
339
- alignItems={"center"}
340
- width="100%"
341
- >
342
- <HeaderTitle>Deposit from Smart account</HeaderTitle>
343
- </Box>
344
-
345
- {handleClose && (
346
- <IconButton
347
- onClick={handleClose}
348
- minWidth={"16px"}
349
- minHeight={"16px"}
350
- maxWidth={"16px"}
351
- marginLeft={"auto"}
352
- >
353
- <Icon
354
- as={X}
355
- color="fg.muted"
356
- width={"16px"}
357
- height={"16px"}
358
- />
359
- </IconButton>
360
- )}
361
- </HeaderWrapper>
362
- </Modal.Header>
363
- <Modal.Body>
364
- <AnimatedStep key={isBootstrapping ? "bootstrap" : currentStep}>
365
- {currentStepComponent}
366
- </AnimatedStep>
367
- </Modal.Body>
368
- </>
369
- );
370
- };
371
-
372
- export default SmartAccountFlow;
@@ -1,325 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import { Box } from "@chakra-ui/react";
3
-
4
- import { BodyWrapper, HeaderTitle, HeaderDescription } from "../../ui/styled";
5
- import { Button } from "../../ui";
6
- import { AmountInput, AmountInputValue } from "../../AmountInput";
7
- import { useAppStore } from "@/store";
8
- import {
9
- denormalizeValue,
10
- compareCaseInsensitive,
11
- formatNumber,
12
- formatUSD,
13
- normalizeValue,
14
- } from "@/util";
15
- import {
16
- precisionizeNumber,
17
- getPositiveDecimalValue,
18
- } from "@/util/common";
19
- import {
20
- EXCHANGE_MAX_LIMIT_GAP_USD,
21
- EXCHANGE_MIN_LIMIT,
22
- } from "@/util/constants";
23
- import { useAppDetails } from "@/util/enso-hooks";
24
- import type { MatchedToken } from "./types";
25
-
26
- const ChooseAmountStep = ({
27
- setStep,
28
- nextStep,
29
- selectedToken,
30
- prefillAmountToken,
31
- mode,
32
- }: {
33
- setStep: (step: number) => void;
34
- nextStep: number;
35
- selectedToken: MatchedToken | null;
36
- prefillAmountToken?: string | null;
37
- mode: "cex" | "smart-account";
38
- }) => {
39
- const [amountInput, setAmountInput] = useState<AmountInputValue>({
40
- tokenAmount: "",
41
- usdAmount: "",
42
- mode: "usd",
43
- });
44
- const initializedTokenKeyRef = useRef<string | null>(null);
45
- const setAmountIn = useAppStore((s) => s.setAmountIn);
46
- const amountInStore = useAppStore((s) => s.amountIn);
47
- const chainIdInStore = useAppStore((s) => s.chainIdIn);
48
- const tokenInStore = useAppStore((s) => s.tokenIn);
49
- const { tokenInData } = useAppDetails();
50
- const isStable = selectedToken?.symbol.toLowerCase().includes("USD");
51
- const roundingPrecision = isStable ? 2 : 6;
52
- const amount = amountInput.tokenAmount;
53
- const usdValue = amountInput.usdAmount;
54
-
55
- const isCex = mode === "cex";
56
-
57
- const maxUsdAmount = selectedToken
58
- ? isCex
59
- ? (selectedToken.marketValue - EXCHANGE_MAX_LIMIT_GAP_USD).toFixed(2)
60
- : selectedToken.marketValue.toFixed(2)
61
- : 0;
62
-
63
- const tokenPriceUsd = useMemo(() => {
64
- if (!selectedToken || !selectedToken.balance) return undefined;
65
- return selectedToken.marketValue / selectedToken.balance;
66
- }, [selectedToken]);
67
-
68
- const getPercentAmounts = useCallback(
69
- (percent: number) => {
70
- if (!selectedToken) return;
71
-
72
- const targetUsdAmount = (selectedToken.marketValue * percent) / 100;
73
-
74
- let finalUsdAmount: number;
75
- let finalTokenAmount: number;
76
-
77
- if (isCex) {
78
- const minValueForToken =
79
- EXCHANGE_MIN_LIMIT[
80
- selectedToken.symbol as keyof typeof EXCHANGE_MIN_LIMIT
81
- ] || 0;
82
-
83
- finalUsdAmount = Math.max(
84
- minValueForToken,
85
- Math.min(targetUsdAmount, +maxUsdAmount),
86
- );
87
-
88
- const tokenPrice =
89
- selectedToken.marketValue / selectedToken.balance;
90
- finalTokenAmount = Math.min(
91
- finalUsdAmount / tokenPrice,
92
- selectedToken.balance,
93
- );
94
- } else {
95
- finalUsdAmount = targetUsdAmount;
96
- finalTokenAmount = (selectedToken.balance * percent) / 100;
97
- }
98
-
99
- return {
100
- tokenAmount: precisionizeNumber(
101
- finalTokenAmount,
102
- roundingPrecision,
103
- ),
104
- usdAmount: finalUsdAmount.toFixed(2),
105
- };
106
- },
107
- [selectedToken, isCex, maxUsdAmount, roundingPrecision],
108
- );
109
-
110
- // Set initial value on token change.
111
- useEffect(() => {
112
- if (!selectedToken) return;
113
-
114
- const tokenKey = [
115
- selectedToken.chainId,
116
- selectedToken.symbol,
117
- selectedToken.tokenAddress ?? "",
118
- ].join(":");
119
- if (initializedTokenKeyRef.current === tokenKey) return;
120
-
121
- const storeMatchesSelectedToken =
122
- !!selectedToken.tokenAddress &&
123
- !!chainIdInStore &&
124
- chainIdInStore === selectedToken.chainId &&
125
- compareCaseInsensitive(tokenInStore, selectedToken.tokenAddress);
126
-
127
- let tokenAmountToUse: string | null = null;
128
-
129
- if (
130
- storeMatchesSelectedToken &&
131
- tokenInData?.decimals &&
132
- amountInStore &&
133
- amountInStore !== "0"
134
- ) {
135
- const normalizedStoreAmount = normalizeValue(
136
- amountInStore,
137
- tokenInData.decimals,
138
- );
139
- const normalizedNumber = Number(normalizedStoreAmount);
140
- if (Number.isFinite(normalizedNumber) && normalizedNumber > 0) {
141
- tokenAmountToUse = precisionizeNumber(
142
- normalizedNumber,
143
- roundingPrecision,
144
- );
145
- }
146
- }
147
-
148
- if (!tokenAmountToUse && prefillAmountToken) {
149
- const prefillNumber = Number(prefillAmountToken);
150
- if (Number.isFinite(prefillNumber) && prefillNumber > 0) {
151
- tokenAmountToUse = precisionizeNumber(
152
- prefillNumber,
153
- roundingPrecision,
154
- );
155
- }
156
- }
157
-
158
- if (tokenAmountToUse) {
159
- const usdAmountToUse =
160
- tokenPriceUsd && tokenPriceUsd > 0
161
- ? (Number(tokenAmountToUse) * tokenPriceUsd).toFixed(2)
162
- : "";
163
-
164
- setAmountInput((prev) => ({
165
- ...prev,
166
- tokenAmount: tokenAmountToUse as string,
167
- usdAmount: usdAmountToUse,
168
- }));
169
- initializedTokenKeyRef.current = tokenKey;
170
- return;
171
- }
172
-
173
- const percentAmounts = getPercentAmounts(100);
174
- if (!percentAmounts) return;
175
-
176
- setAmountInput((prev) => ({
177
- ...prev,
178
- ...percentAmounts,
179
- }));
180
- initializedTokenKeyRef.current = tokenKey;
181
- }, [
182
- amountInStore,
183
- chainIdInStore,
184
- getPercentAmounts,
185
- prefillAmountToken,
186
- roundingPrecision,
187
- selectedToken,
188
- tokenInData?.decimals,
189
- tokenInStore,
190
- tokenPriceUsd,
191
- ]);
192
-
193
- useEffect(() => {
194
- if (!tokenInData?.decimals) return;
195
-
196
- const normalizedAmount = amount.endsWith(".")
197
- ? amount.slice(0, -1)
198
- : amount;
199
-
200
- if (!normalizedAmount || normalizedAmount === ".") {
201
- setAmountIn("0");
202
- return;
203
- }
204
-
205
- try {
206
- setAmountIn(
207
- denormalizeValue(normalizedAmount, tokenInData.decimals),
208
- );
209
- } catch (error) {
210
- setAmountIn("0");
211
- }
212
- }, [amount, tokenInData?.decimals, setAmountIn]);
213
-
214
- const numericAmount = getPositiveDecimalValue(amount);
215
- const hasPositiveAmount = numericAmount !== null;
216
- const hasUsdValue = !!usdValue && usdValue !== ".";
217
- const notEnoughBalance = selectedToken
218
- ? hasPositiveAmount &&
219
- numericAmount !== null &&
220
- numericAmount > selectedToken.balance
221
- : true;
222
-
223
- // Limits validation logic - only for CEX withdrawals
224
- const currentUsdValue = hasUsdValue
225
- ? (getPositiveDecimalValue(usdValue) ?? 0)
226
- : 0;
227
- const minValueForToken =
228
- isCex && selectedToken
229
- ? EXCHANGE_MIN_LIMIT[
230
- selectedToken.symbol as keyof typeof EXCHANGE_MIN_LIMIT
231
- ]
232
- : 0;
233
-
234
- const isBelowMinAmount =
235
- isCex &&
236
- selectedToken &&
237
- hasPositiveAmount &&
238
- currentUsdValue > 0 &&
239
- minValueForToken &&
240
- numericAmount !== null &&
241
- numericAmount < minValueForToken;
242
- const isAboveMaxAmount =
243
- isCex &&
244
- selectedToken &&
245
- hasPositiveAmount &&
246
- currentUsdValue > 0 &&
247
- currentUsdValue > +maxUsdAmount;
248
-
249
- const isAmountInvalid =
250
- !hasPositiveAmount ||
251
- isBelowMinAmount ||
252
- isAboveMaxAmount ||
253
- notEnoughBalance;
254
-
255
- if (!selectedToken) {
256
- return (
257
- <BodyWrapper>
258
- <Box textAlign="center" color="fg.subtle" py={8}>
259
- No token selected
260
- </Box>
261
- </BodyWrapper>
262
- );
263
- }
264
-
265
- return (
266
- <BodyWrapper>
267
- <Box mb={4} width="100%" textAlign="left">
268
- <HeaderTitle>Enter Amount</HeaderTitle>
269
- <HeaderDescription>
270
- Available: {formatNumber(selectedToken.balance)}{" "}
271
- {selectedToken.symbol} (
272
- {formatUSD(selectedToken.marketValue)})
273
- </HeaderDescription>
274
- </Box>
275
-
276
- <Box
277
- display={"flex"}
278
- flexDirection={"column"}
279
- gap={"8px"}
280
- width="100%"
281
- >
282
- <AmountInput
283
- value={amountInput}
284
- onChange={setAmountInput}
285
- tokenSymbol={selectedToken.symbol}
286
- tokenPriceUsd={tokenPriceUsd}
287
- roundingPrecision={roundingPrecision}
288
- onPercentSelect={getPercentAmounts}
289
- />
290
- </Box>
291
-
292
- {
293
- <Box
294
- textAlign="center"
295
- color="fg.subtle"
296
- fontSize="xs"
297
- h={3}
298
- m={-1}
299
- visibility={isAmountInvalid ? "visible" : "hidden"}
300
- >
301
- {!hasPositiveAmount
302
- ? "Please enter an amount"
303
- : isBelowMinAmount
304
- ? `Minimum amount is ${formatNumber(minValueForToken)} ${selectedToken.symbol}`
305
- : isAboveMaxAmount
306
- ? `Maximum amount is ${formatUSD(maxUsdAmount)} (balance - $${EXCHANGE_MAX_LIMIT_GAP_USD})`
307
- : notEnoughBalance
308
- ? "Amount exceeds available balance"
309
- : ""}
310
- </Box>
311
- }
312
-
313
- <Button
314
- onClick={() =>
315
- isAmountInvalid ? undefined : setStep(nextStep)
316
- }
317
- disabled={isAmountInvalid}
318
- >
319
- Continue
320
- </Button>
321
- </BodyWrapper>
322
- );
323
- };
324
-
325
- export default ChooseAmountStep;