@ensofinance/checkout-widget 0.1.1 → 0.1.3
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 +5250 -5172
- package/dist/checkout-widget.es.js.map +1 -1
- package/dist/checkout-widget.umd.js +51 -46
- package/dist/checkout-widget.umd.js.map +1 -1
- package/package.json +2 -1
- package/src/components/AmountInput.tsx +233 -0
- package/src/components/steps/ExchangeFlow.tsx +99 -132
- package/src/components/steps/WalletFlow/WalletAmountStep.tsx +75 -155
- package/src/util/common.tsx +23 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ensofinance/checkout-widget",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"homepage": "https://www.enso.build/",
|
|
6
6
|
"repository": {
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"globals": "^15.14.0",
|
|
61
61
|
"orval": "^7.10.0",
|
|
62
62
|
"prettier": "^3.4.2",
|
|
63
|
+
"source-map-explorer": "^2.5.3",
|
|
63
64
|
"typescript": "~5.8.2",
|
|
64
65
|
"typescript-eslint": "^8.18.2",
|
|
65
66
|
"vite": "^6.0.5",
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { Box, Icon, Text } from "@chakra-ui/react";
|
|
2
|
+
import { ArrowDownUpIcon } from "lucide-react";
|
|
3
|
+
import { Input, IconButton, Tab } from "@/components/ui";
|
|
4
|
+
import { formatNumber, formatUSD } from "@/util";
|
|
5
|
+
import { precisionizeNumber, sanitizeDecimalInput } from "@/util/common";
|
|
6
|
+
|
|
7
|
+
export type InputMode = "usd" | "token";
|
|
8
|
+
|
|
9
|
+
export type AmountInputValue = {
|
|
10
|
+
tokenAmount: string;
|
|
11
|
+
usdAmount: string;
|
|
12
|
+
mode: InputMode;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type PercentOption = {
|
|
16
|
+
label: string;
|
|
17
|
+
value: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type AmountInputProps = {
|
|
21
|
+
value: AmountInputValue;
|
|
22
|
+
onChange: (value: AmountInputValue) => void;
|
|
23
|
+
tokenSymbol?: string;
|
|
24
|
+
tokenPriceUsd?: number;
|
|
25
|
+
tokenBalance?: number;
|
|
26
|
+
roundingPrecision?: number;
|
|
27
|
+
usdPrecision?: number;
|
|
28
|
+
percentOptions?: PercentOption[];
|
|
29
|
+
onPercentSelect?: (
|
|
30
|
+
percent: number,
|
|
31
|
+
) => { tokenAmount: string; usdAmount: string } | null | undefined;
|
|
32
|
+
showPercentTabs?: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const defaultPercentOptions: PercentOption[] = [
|
|
36
|
+
{ label: "25%", value: 25 },
|
|
37
|
+
{ label: "50%", value: 50 },
|
|
38
|
+
{ label: "75%", value: 75 },
|
|
39
|
+
{ label: "Max", value: 100 },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
export const AmountInput = ({
|
|
43
|
+
value,
|
|
44
|
+
onChange,
|
|
45
|
+
tokenSymbol,
|
|
46
|
+
tokenPriceUsd,
|
|
47
|
+
tokenBalance,
|
|
48
|
+
roundingPrecision = 6,
|
|
49
|
+
usdPrecision = 2,
|
|
50
|
+
percentOptions = defaultPercentOptions,
|
|
51
|
+
onPercentSelect,
|
|
52
|
+
showPercentTabs = true,
|
|
53
|
+
}: AmountInputProps) => {
|
|
54
|
+
const updateValue = (next: Partial<AmountInputValue>) => {
|
|
55
|
+
onChange({ ...value, ...next });
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleInputChange = (rawValue: string) => {
|
|
59
|
+
if (value.mode === "usd") {
|
|
60
|
+
const cleanUsd = sanitizeDecimalInput(
|
|
61
|
+
rawValue.replace("$", ""),
|
|
62
|
+
usdPrecision,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (!cleanUsd || cleanUsd === ".") {
|
|
66
|
+
updateValue({ usdAmount: cleanUsd, tokenAmount: "" });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!tokenPriceUsd || tokenPriceUsd <= 0) {
|
|
71
|
+
updateValue({ usdAmount: cleanUsd, tokenAmount: "" });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const tokenAmountFromUsd = precisionizeNumber(
|
|
76
|
+
parseFloat(cleanUsd) / tokenPriceUsd,
|
|
77
|
+
roundingPrecision,
|
|
78
|
+
);
|
|
79
|
+
updateValue({
|
|
80
|
+
usdAmount: cleanUsd,
|
|
81
|
+
tokenAmount: tokenAmountFromUsd,
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const cleanTokenAmount = sanitizeDecimalInput(
|
|
87
|
+
rawValue,
|
|
88
|
+
roundingPrecision,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (!cleanTokenAmount || cleanTokenAmount === ".") {
|
|
92
|
+
updateValue({ tokenAmount: cleanTokenAmount, usdAmount: "" });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!tokenPriceUsd || tokenPriceUsd <= 0) {
|
|
97
|
+
updateValue({ tokenAmount: cleanTokenAmount });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
updateValue({
|
|
102
|
+
tokenAmount: cleanTokenAmount,
|
|
103
|
+
usdAmount: (
|
|
104
|
+
parseFloat(cleanTokenAmount) * tokenPriceUsd
|
|
105
|
+
).toFixed(usdPrecision),
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleToggleMode = () => {
|
|
110
|
+
updateValue({
|
|
111
|
+
mode: value.mode === "usd" ? "token" : "usd",
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const handlePercentSelect = (percent: number) => {
|
|
116
|
+
if (onPercentSelect) {
|
|
117
|
+
const nextValues = onPercentSelect(percent);
|
|
118
|
+
if (!nextValues) return;
|
|
119
|
+
|
|
120
|
+
updateValue(nextValues);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!tokenBalance || !tokenPriceUsd || tokenPriceUsd <= 0) return;
|
|
125
|
+
|
|
126
|
+
const tokenAmount = precisionizeNumber(
|
|
127
|
+
(tokenBalance * percent) / 100,
|
|
128
|
+
roundingPrecision,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
updateValue({
|
|
132
|
+
tokenAmount,
|
|
133
|
+
usdAmount: (
|
|
134
|
+
parseFloat(tokenAmount || "0") * tokenPriceUsd
|
|
135
|
+
).toFixed(usdPrecision),
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const placeholder = value.mode === "usd" ? "$10.00" : "0.00";
|
|
140
|
+
const displayValue =
|
|
141
|
+
value.mode === "usd"
|
|
142
|
+
? value.usdAmount
|
|
143
|
+
? `$${value.usdAmount}`
|
|
144
|
+
: ""
|
|
145
|
+
: value.tokenAmount;
|
|
146
|
+
|
|
147
|
+
const hasTokenValue = !!value.tokenAmount && value.tokenAmount !== ".";
|
|
148
|
+
const hasUsdValue = !!value.usdAmount && value.usdAmount !== ".";
|
|
149
|
+
|
|
150
|
+
const formattedTokenValue = hasTokenValue
|
|
151
|
+
? formatNumber(value.tokenAmount)
|
|
152
|
+
: "";
|
|
153
|
+
const tokenSuffix = tokenSymbol ? ` ${tokenSymbol}` : "";
|
|
154
|
+
const equivalentValue =
|
|
155
|
+
value.mode === "usd"
|
|
156
|
+
? hasTokenValue
|
|
157
|
+
? `${formattedTokenValue}${tokenSuffix}`
|
|
158
|
+
: "—"
|
|
159
|
+
: hasUsdValue
|
|
160
|
+
? formatUSD(value.usdAmount)
|
|
161
|
+
: "—";
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<Box display={"flex"} flexDirection={"column"} gap={"8px"}>
|
|
165
|
+
<Box
|
|
166
|
+
display={"flex"}
|
|
167
|
+
flexDirection={"column"}
|
|
168
|
+
gap={"8px"}
|
|
169
|
+
alignItems={"center"}
|
|
170
|
+
padding={"25.5px"}
|
|
171
|
+
>
|
|
172
|
+
{/* Main Input */}
|
|
173
|
+
<Input
|
|
174
|
+
inputMode="decimal"
|
|
175
|
+
marginY={"8px"}
|
|
176
|
+
variant={"text"}
|
|
177
|
+
placeholder={placeholder}
|
|
178
|
+
value={displayValue}
|
|
179
|
+
onChange={(event) => handleInputChange(event.target.value)}
|
|
180
|
+
/>
|
|
181
|
+
|
|
182
|
+
{/* Toggle Button and Equivalent Display */}
|
|
183
|
+
<Box
|
|
184
|
+
display={"flex"}
|
|
185
|
+
gap={"3"}
|
|
186
|
+
alignItems={"center"}
|
|
187
|
+
onClick={handleToggleMode}
|
|
188
|
+
_hover={{ background: "bg.subtle" }}
|
|
189
|
+
cursor={"pointer"}
|
|
190
|
+
borderRadius={"lg"}
|
|
191
|
+
px={"3"}
|
|
192
|
+
>
|
|
193
|
+
<IconButton
|
|
194
|
+
minWidth={"24px"}
|
|
195
|
+
minHeight={"24px"}
|
|
196
|
+
maxWidth={"24px"}
|
|
197
|
+
background={"transparent"}
|
|
198
|
+
>
|
|
199
|
+
<Icon
|
|
200
|
+
as={ArrowDownUpIcon}
|
|
201
|
+
color="gray"
|
|
202
|
+
width={"16px"}
|
|
203
|
+
height={"16px"}
|
|
204
|
+
/>
|
|
205
|
+
</IconButton>
|
|
206
|
+
|
|
207
|
+
{/* Small equivalent value display */}
|
|
208
|
+
<Text fontSize="sm" color="fg.muted">
|
|
209
|
+
{equivalentValue}
|
|
210
|
+
</Text>
|
|
211
|
+
</Box>
|
|
212
|
+
</Box>
|
|
213
|
+
|
|
214
|
+
{showPercentTabs && percentOptions.length > 0 && (
|
|
215
|
+
<Box
|
|
216
|
+
display={"flex"}
|
|
217
|
+
gap={"4px"}
|
|
218
|
+
justifyContent={"center"}
|
|
219
|
+
paddingBottom={"35px"}
|
|
220
|
+
>
|
|
221
|
+
{percentOptions.map((option) => (
|
|
222
|
+
<Tab
|
|
223
|
+
key={option.label}
|
|
224
|
+
onClick={() => handlePercentSelect(option.value)}
|
|
225
|
+
>
|
|
226
|
+
{option.label}
|
|
227
|
+
</Tab>
|
|
228
|
+
))}
|
|
229
|
+
</Box>
|
|
230
|
+
)}
|
|
231
|
+
</Box>
|
|
232
|
+
);
|
|
233
|
+
};
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
Image,
|
|
10
10
|
Table,
|
|
11
11
|
} from "@chakra-ui/react";
|
|
12
|
-
import { ChevronLeft, X,
|
|
12
|
+
import { ChevronLeft, X, TriangleAlert } from "lucide-react";
|
|
13
13
|
import { useContext, useEffect, useMemo, useState, useCallback } from "react";
|
|
14
14
|
import { useAccount, useSignMessage } from "wagmi";
|
|
15
15
|
import { getUserOperationHash } from "viem/account-abstraction";
|
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
HeaderWrapper,
|
|
21
21
|
ListWrapper,
|
|
22
22
|
} from "../ui/styled";
|
|
23
|
-
import { IconButton, Button,
|
|
23
|
+
import { IconButton, Button, Input } from "../ui";
|
|
24
|
+
import { AmountInput, AmountInputValue } from "../AmountInput";
|
|
24
25
|
import { CheckoutContext } from "../Checkout";
|
|
25
26
|
import Modal from "../modal";
|
|
26
27
|
import {
|
|
@@ -797,13 +798,17 @@ const ChooseAmountStep = ({
|
|
|
797
798
|
setStep: (step: WithdrawalStep) => void;
|
|
798
799
|
selectedToken: MatchedToken | null;
|
|
799
800
|
}) => {
|
|
800
|
-
const [
|
|
801
|
-
|
|
802
|
-
|
|
801
|
+
const [amountInput, setAmountInput] = useState<AmountInputValue>({
|
|
802
|
+
tokenAmount: "",
|
|
803
|
+
usdAmount: "",
|
|
804
|
+
mode: "usd",
|
|
805
|
+
});
|
|
803
806
|
const setAmountIn = useAppStore((s) => s.setAmountIn);
|
|
804
807
|
const { tokenInData, selectedIntegration } = useAppDetails();
|
|
805
808
|
const isStable = selectedToken?.symbol.toLowerCase().includes("USD");
|
|
806
809
|
const roundingPrecision = isStable ? 2 : 6;
|
|
810
|
+
const amount = amountInput.tokenAmount;
|
|
811
|
+
const usdValue = amountInput.usdAmount;
|
|
807
812
|
|
|
808
813
|
// Only apply CEX withdrawal limits if using a CEX holding (not smart account)
|
|
809
814
|
const isWithdrawal = !isDelayedBalanceUsed(selectedIntegration.type);
|
|
@@ -816,8 +821,13 @@ const ChooseAmountStep = ({
|
|
|
816
821
|
: selectedToken.marketValue.toFixed(2)
|
|
817
822
|
: 0;
|
|
818
823
|
|
|
824
|
+
const tokenPriceUsd = useMemo(() => {
|
|
825
|
+
if (!selectedToken || !selectedToken.balance) return undefined;
|
|
826
|
+
return selectedToken.marketValue / selectedToken.balance;
|
|
827
|
+
}, [selectedToken]);
|
|
828
|
+
|
|
819
829
|
// Handle percentage selection with limits (only for CEX withdrawals)
|
|
820
|
-
const
|
|
830
|
+
const getPercentAmounts = useCallback(
|
|
821
831
|
(percent: number) => {
|
|
822
832
|
if (!selectedToken) return;
|
|
823
833
|
|
|
@@ -851,8 +861,13 @@ const ChooseAmountStep = ({
|
|
|
851
861
|
finalTokenAmount = (selectedToken.balance * percent) / 100;
|
|
852
862
|
}
|
|
853
863
|
|
|
854
|
-
|
|
855
|
-
|
|
864
|
+
return {
|
|
865
|
+
tokenAmount: precisionizeNumber(
|
|
866
|
+
finalTokenAmount,
|
|
867
|
+
roundingPrecision,
|
|
868
|
+
),
|
|
869
|
+
usdAmount: finalUsdAmount.toFixed(2),
|
|
870
|
+
};
|
|
856
871
|
},
|
|
857
872
|
[selectedToken, isWithdrawal, maxUsdAmount, roundingPrecision],
|
|
858
873
|
);
|
|
@@ -860,72 +875,43 @@ const ChooseAmountStep = ({
|
|
|
860
875
|
// Set max value on load
|
|
861
876
|
useEffect(() => {
|
|
862
877
|
if (selectedToken) {
|
|
863
|
-
|
|
878
|
+
const percentAmounts = getPercentAmounts(100);
|
|
879
|
+
if (!percentAmounts) return;
|
|
880
|
+
|
|
881
|
+
setAmountInput((prev) => ({
|
|
882
|
+
...prev,
|
|
883
|
+
...percentAmounts,
|
|
884
|
+
}));
|
|
864
885
|
}
|
|
865
|
-
}, [selectedToken,
|
|
886
|
+
}, [selectedToken, getPercentAmounts]);
|
|
866
887
|
|
|
867
888
|
useEffect(() => {
|
|
868
|
-
if (tokenInData?.decimals)
|
|
869
|
-
setAmountIn(
|
|
870
|
-
Number(
|
|
871
|
-
denormalizeValue(amount || "0", tokenInData?.decimals),
|
|
872
|
-
).toFixed(),
|
|
873
|
-
);
|
|
874
|
-
}, [amount, tokenInData?.decimals]);
|
|
875
|
-
|
|
876
|
-
// Handle input change based on current mode
|
|
877
|
-
const handleInputChange = (value: string) => {
|
|
878
|
-
if (!selectedToken) return;
|
|
879
|
-
|
|
880
|
-
if (inputMode === "usd") {
|
|
881
|
-
const cleanUsd = value.replace("$", "") || "";
|
|
882
|
-
setUsdValue(cleanUsd);
|
|
883
|
-
// Calculate token amount from USD value
|
|
884
|
-
const tokenPrice =
|
|
885
|
-
selectedToken.marketValue / selectedToken.balance;
|
|
886
|
-
const tokenAmount = parseFloat(cleanUsd || "0") / tokenPrice;
|
|
887
|
-
setAmount(precisionizeNumber(tokenAmount, roundingPrecision));
|
|
888
|
-
} else {
|
|
889
|
-
setAmount(precisionizeNumber(value, roundingPrecision));
|
|
890
|
-
// Calculate USD value from token amount
|
|
891
|
-
const tokenPrice =
|
|
892
|
-
selectedToken.marketValue / selectedToken.balance;
|
|
893
|
-
const usdAmount = parseFloat(value) * tokenPrice;
|
|
894
|
-
setUsdValue(usdAmount.toFixed(2));
|
|
895
|
-
}
|
|
896
|
-
};
|
|
889
|
+
if (!tokenInData?.decimals) return;
|
|
897
890
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
};
|
|
891
|
+
const normalizedAmount = amount.endsWith(".")
|
|
892
|
+
? amount.slice(0, -1)
|
|
893
|
+
: amount;
|
|
902
894
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
return {
|
|
907
|
-
placeholder: "$10.00",
|
|
908
|
-
displayValue: usdValue ? `$${usdValue}` : "",
|
|
909
|
-
equivalentValue: amount
|
|
910
|
-
? `${formatNumber(parseFloat(amount))} ${selectedToken?.symbol}`
|
|
911
|
-
: "—",
|
|
912
|
-
};
|
|
913
|
-
} else {
|
|
914
|
-
return {
|
|
915
|
-
placeholder: "0.00",
|
|
916
|
-
displayValue: amount,
|
|
917
|
-
equivalentValue: usdValue ? `$${usdValue}` : "—",
|
|
918
|
-
};
|
|
895
|
+
if (!normalizedAmount || normalizedAmount === ".") {
|
|
896
|
+
setAmountIn("0");
|
|
897
|
+
return;
|
|
919
898
|
}
|
|
920
|
-
};
|
|
921
899
|
|
|
922
|
-
|
|
900
|
+
try {
|
|
901
|
+
setAmountIn(denormalizeValue(normalizedAmount, tokenInData.decimals));
|
|
902
|
+
} catch (error) {
|
|
903
|
+
setAmountIn("0");
|
|
904
|
+
}
|
|
905
|
+
}, [amount, tokenInData?.decimals, setAmountIn]);
|
|
906
|
+
|
|
907
|
+
const hasAmount = !!amount && amount !== ".";
|
|
908
|
+
const hasUsdValue = !!usdValue && usdValue !== ".";
|
|
923
909
|
const notEnoughBalance = selectedToken
|
|
924
|
-
? parseFloat(amount) > selectedToken.balance
|
|
910
|
+
? hasAmount && parseFloat(amount) > selectedToken.balance
|
|
925
911
|
: true;
|
|
926
912
|
|
|
927
913
|
// Limits validation logic - only for CEX withdrawals
|
|
928
|
-
const currentUsdValue = parseFloat(usdValue);
|
|
914
|
+
const currentUsdValue = hasUsdValue ? parseFloat(usdValue) : 0;
|
|
929
915
|
const minValueForToken =
|
|
930
916
|
isWithdrawal && selectedToken
|
|
931
917
|
? EXCHANGE_MIN_LIMIT[
|
|
@@ -936,17 +922,19 @@ const ChooseAmountStep = ({
|
|
|
936
922
|
const isBelowMinAmount =
|
|
937
923
|
isWithdrawal &&
|
|
938
924
|
selectedToken &&
|
|
925
|
+
hasAmount &&
|
|
939
926
|
currentUsdValue > 0 &&
|
|
940
927
|
minValueForToken &&
|
|
941
928
|
+amount < minValueForToken;
|
|
942
929
|
const isAboveMaxAmount =
|
|
943
930
|
isWithdrawal &&
|
|
944
931
|
selectedToken &&
|
|
932
|
+
hasAmount &&
|
|
945
933
|
currentUsdValue > 0 &&
|
|
946
934
|
currentUsdValue > +maxUsdAmount;
|
|
947
935
|
|
|
948
936
|
const isAmountInvalid =
|
|
949
|
-
isBelowMinAmount || isAboveMaxAmount || notEnoughBalance;
|
|
937
|
+
!hasAmount || isBelowMinAmount || isAboveMaxAmount || notEnoughBalance;
|
|
950
938
|
|
|
951
939
|
if (!selectedToken) {
|
|
952
940
|
return (
|
|
@@ -975,70 +963,14 @@ const ChooseAmountStep = ({
|
|
|
975
963
|
gap={"8px"}
|
|
976
964
|
width="100%"
|
|
977
965
|
>
|
|
978
|
-
<
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
<Input
|
|
987
|
-
inputMode="decimal"
|
|
988
|
-
marginY={"8px"}
|
|
989
|
-
variant={"text"}
|
|
990
|
-
placeholder={placeholder}
|
|
991
|
-
value={displayValue}
|
|
992
|
-
onChange={(e) => handleInputChange(e.target.value)}
|
|
993
|
-
/>
|
|
994
|
-
|
|
995
|
-
{/* Toggle Button and Equivalent Display */}
|
|
996
|
-
<Box
|
|
997
|
-
display={"flex"}
|
|
998
|
-
gap={"3"}
|
|
999
|
-
alignItems={"center"}
|
|
1000
|
-
onClick={handleToggleMode}
|
|
1001
|
-
_hover={{ background: "bg.subtle" }}
|
|
1002
|
-
cursor={"pointer"}
|
|
1003
|
-
borderRadius={"lg"}
|
|
1004
|
-
px={"3"}
|
|
1005
|
-
>
|
|
1006
|
-
<IconButton
|
|
1007
|
-
minWidth={"24px"}
|
|
1008
|
-
minHeight={"24px"}
|
|
1009
|
-
maxWidth={"24px"}
|
|
1010
|
-
background={"transparent"}
|
|
1011
|
-
>
|
|
1012
|
-
<Icon
|
|
1013
|
-
as={ArrowDownUpIcon}
|
|
1014
|
-
color="gray"
|
|
1015
|
-
width={"16px"}
|
|
1016
|
-
height={"16px"}
|
|
1017
|
-
/>
|
|
1018
|
-
</IconButton>
|
|
1019
|
-
|
|
1020
|
-
{/* Small equivalent value display */}
|
|
1021
|
-
<Text fontSize="sm" color="fg.muted">
|
|
1022
|
-
{equivalentValue}
|
|
1023
|
-
</Text>
|
|
1024
|
-
</Box>
|
|
1025
|
-
</Box>
|
|
1026
|
-
|
|
1027
|
-
<Box
|
|
1028
|
-
display={"flex"}
|
|
1029
|
-
gap={"4px"}
|
|
1030
|
-
justifyContent={"center"}
|
|
1031
|
-
paddingBottom={"35px"}
|
|
1032
|
-
>
|
|
1033
|
-
{[25, 50, 75, 100].map((percent) => (
|
|
1034
|
-
<Tab
|
|
1035
|
-
key={percent}
|
|
1036
|
-
onClick={() => handlePercentageSelect(percent)}
|
|
1037
|
-
>
|
|
1038
|
-
{percent === 100 ? "Max" : `${percent}%`}
|
|
1039
|
-
</Tab>
|
|
1040
|
-
))}
|
|
1041
|
-
</Box>
|
|
966
|
+
<AmountInput
|
|
967
|
+
value={amountInput}
|
|
968
|
+
onChange={setAmountInput}
|
|
969
|
+
tokenSymbol={selectedToken.symbol}
|
|
970
|
+
tokenPriceUsd={tokenPriceUsd}
|
|
971
|
+
roundingPrecision={roundingPrecision}
|
|
972
|
+
onPercentSelect={getPercentAmounts}
|
|
973
|
+
/>
|
|
1042
974
|
</Box>
|
|
1043
975
|
|
|
1044
976
|
{
|
|
@@ -1049,10 +981,10 @@ const ChooseAmountStep = ({
|
|
|
1049
981
|
h={3}
|
|
1050
982
|
m={-1}
|
|
1051
983
|
visibility={
|
|
1052
|
-
isAmountInvalid
|
|
984
|
+
isAmountInvalid ? "visible" : "hidden"
|
|
1053
985
|
}
|
|
1054
986
|
>
|
|
1055
|
-
{!
|
|
987
|
+
{!hasAmount
|
|
1056
988
|
? "Please enter an amount"
|
|
1057
989
|
: isBelowMinAmount
|
|
1058
990
|
? `Minimum amount is ${formatNumber(minValueForToken)} ${selectedToken.symbol}`
|
|
@@ -1066,11 +998,11 @@ const ChooseAmountStep = ({
|
|
|
1066
998
|
|
|
1067
999
|
<Button
|
|
1068
1000
|
onClick={() =>
|
|
1069
|
-
isAmountInvalid
|
|
1001
|
+
isAmountInvalid
|
|
1070
1002
|
? undefined
|
|
1071
1003
|
: setStep(WithdrawalStep.SignUserOp)
|
|
1072
1004
|
}
|
|
1073
|
-
disabled={isAmountInvalid
|
|
1005
|
+
disabled={isAmountInvalid}
|
|
1074
1006
|
>
|
|
1075
1007
|
Continue
|
|
1076
1008
|
</Button>
|
|
@@ -1200,6 +1132,7 @@ const InitiateWithdrawalStep = ({
|
|
|
1200
1132
|
const { tokenInData } = useAppDetails();
|
|
1201
1133
|
const sessionId = useAppStore((state) => state.sessionId);
|
|
1202
1134
|
const [isLoading, setIsLoading] = useState(true);
|
|
1135
|
+
const [error, setError] = useState<string | null>(null);
|
|
1203
1136
|
const selectedIntegration = useAppStore((s) => s.selectedIntegration);
|
|
1204
1137
|
|
|
1205
1138
|
const handleMeshAccessPayload = useHandleMeshAccessPayload();
|
|
@@ -1272,6 +1205,7 @@ const InitiateWithdrawalStep = ({
|
|
|
1272
1205
|
|
|
1273
1206
|
console.log("accessTokens", accessTokens);
|
|
1274
1207
|
|
|
1208
|
+
let handledByEvent = false;
|
|
1275
1209
|
const link = createLink({
|
|
1276
1210
|
clientId: address,
|
|
1277
1211
|
accessTokens,
|
|
@@ -1285,6 +1219,7 @@ const InitiateWithdrawalStep = ({
|
|
|
1285
1219
|
},
|
|
1286
1220
|
onExit: (error) => {
|
|
1287
1221
|
console.log("Mesh link exited:", error);
|
|
1222
|
+
if (handledByEvent) return;
|
|
1288
1223
|
setIsLoading(false);
|
|
1289
1224
|
setStep(WithdrawalStep.ChooseExchangeAsset);
|
|
1290
1225
|
},
|
|
@@ -1294,8 +1229,18 @@ const InitiateWithdrawalStep = ({
|
|
|
1294
1229
|
console.log(
|
|
1295
1230
|
"Transfer executed, closing mesh link and moving to TrackUserOp step",
|
|
1296
1231
|
);
|
|
1232
|
+
handledByEvent = true;
|
|
1297
1233
|
link.closeLink();
|
|
1298
1234
|
setStep(WithdrawalStep.TrackUserOp);
|
|
1235
|
+
} else if (ev.type === "transferExecutionError") {
|
|
1236
|
+
const errorMessage =
|
|
1237
|
+
ev.payload?.errorMessage ||
|
|
1238
|
+
"Transfer failed. Please try again.";
|
|
1239
|
+
console.error("Mesh transfer error:", errorMessage);
|
|
1240
|
+
handledByEvent = true;
|
|
1241
|
+
link.closeLink();
|
|
1242
|
+
setError(errorMessage);
|
|
1243
|
+
setIsLoading(false);
|
|
1299
1244
|
}
|
|
1300
1245
|
},
|
|
1301
1246
|
});
|
|
@@ -1310,6 +1255,28 @@ const InitiateWithdrawalStep = ({
|
|
|
1310
1255
|
fetchLinkTokenAndOpen();
|
|
1311
1256
|
}, [selectedToken, userOp]);
|
|
1312
1257
|
|
|
1258
|
+
if (error) {
|
|
1259
|
+
return (
|
|
1260
|
+
<BodyWrapper>
|
|
1261
|
+
<Flex direction="column" align="center" justify="center" flex={1} p={6} gap={4}>
|
|
1262
|
+
<Flex
|
|
1263
|
+
align="center"
|
|
1264
|
+
justify="center"
|
|
1265
|
+
w={12}
|
|
1266
|
+
h={12}
|
|
1267
|
+
borderRadius="full"
|
|
1268
|
+
bg="orange.100"
|
|
1269
|
+
>
|
|
1270
|
+
<Icon as={TriangleAlert} boxSize={6} color="orange.500" />
|
|
1271
|
+
</Flex>
|
|
1272
|
+
<Text fontSize="md" textAlign="center" color="gray.700">
|
|
1273
|
+
{error}
|
|
1274
|
+
</Text>
|
|
1275
|
+
</Flex>
|
|
1276
|
+
</BodyWrapper>
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1313
1280
|
return (
|
|
1314
1281
|
<BodyWrapper>
|
|
1315
1282
|
<Center>
|