@xswap-link/sdk 0.1.0 → 0.1.2

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 (42) hide show
  1. package/.github/workflows/main.yml +1 -0
  2. package/.github/workflows/publish.yml +1 -0
  3. package/CHANGELOG.md +24 -0
  4. package/dist/index.css +128 -32
  5. package/dist/index.d.mts +22 -19
  6. package/dist/index.d.ts +22 -19
  7. package/dist/index.js +1167 -32978
  8. package/dist/index.mjs +1152 -32988
  9. package/nodemon.json +5 -0
  10. package/package.json +17 -6
  11. package/src/components/TxConfigForm/BalanceComponent.tsx +51 -0
  12. package/src/components/TxConfigForm/ChainListElement.tsx +36 -0
  13. package/src/components/TxConfigForm/Description.tsx +15 -0
  14. package/src/components/TxConfigForm/ErrorField.tsx +17 -0
  15. package/src/components/TxConfigForm/FeesDetails.tsx +105 -0
  16. package/src/components/TxConfigForm/Form.tsx +129 -447
  17. package/src/components/TxConfigForm/History.tsx +67 -122
  18. package/src/components/TxConfigForm/HistoryCard.tsx +3 -2
  19. package/src/components/TxConfigForm/Settings.tsx +137 -0
  20. package/src/components/TxConfigForm/Summary.tsx +57 -0
  21. package/src/components/TxConfigForm/SwapPanel.tsx +191 -0
  22. package/src/components/TxConfigForm/TokenPicker.tsx +76 -67
  23. package/src/components/TxConfigForm/TopBar.tsx +53 -0
  24. package/src/components/TxConfigForm/index.tsx +121 -0
  25. package/src/components/icons/ArrowDownIcon.tsx +16 -0
  26. package/src/components/icons/ChainlinkCCIPIcon.tsx +23 -0
  27. package/src/components/icons/ChevronDownIcon.tsx +2 -2
  28. package/src/components/icons/ChevronUpIcon.tsx +2 -2
  29. package/src/components/icons/CloseIcon.tsx +2 -2
  30. package/src/components/icons/CoinsIcon.tsx +8 -6
  31. package/src/components/icons/HistoryIcon.tsx +8 -5
  32. package/src/components/icons/InfoIcon.tsx +13 -0
  33. package/src/components/icons/SettingsIcon.tsx +16 -0
  34. package/src/components/icons/TimerIcon.tsx +15 -0
  35. package/src/components/icons/XSwapBadgeIcon.tsx +23 -0
  36. package/src/components/icons/index.ts +6 -0
  37. package/src/config/index.ts +1 -0
  38. package/src/config/wagmiConfig.ts +45 -0
  39. package/src/constants/index.ts +6 -3
  40. package/tailwind.config.js +11 -2
  41. package/src/components/TxConfigForm/index.jsx +0 -100
  42. package/src/components/global.d.ts +0 -6
@@ -1,80 +1,75 @@
1
1
  import "@src/global.css";
2
- import React, { FC, FormEvent, useEffect, useState } from "react";
2
+ import React, { FormEvent, useEffect, useState } from "react";
3
3
  import { ethers } from "ethers";
4
- import {
5
- Chain,
6
- ContractCall,
7
- Route,
8
- Token,
9
- TokenBalances,
10
- TokenPrices,
11
- } from "@src/models";
4
+ import { useAccount, useSwitchChain } from "wagmi";
5
+ import { Chain, Route, Token, TokenBalances, TokenPrices } from "@src/models";
12
6
  import { getPrices, getRoute } from "@src/services";
13
- import {
14
- getBalances,
15
- safeBigNumberFrom,
16
- shortAddress,
17
- weiToHumanReadable,
18
- } from "@src/utils";
19
- import {
20
- ChevronDownIcon,
21
- ChevronUpIcon,
22
- CoinsIcon,
23
- DownArrowIcon,
24
- HistoryIcon,
25
- PercentageIcon,
26
- } from "@src/components/icons";
27
- import { SLIPPAGE_PRESETS, NUMBER_INPUT_REGEX } from "@src/constants";
28
- import { History } from "./History";
29
- import { Alert } from "@src/components";
7
+ import { getBalances } from "@src/utils";
8
+ import { ROUTE_TIMEOUT_MS } from "@src/constants";
9
+ import { TxConfigFormProps } from "@src/components";
30
10
  import { useDebounce } from "@src/hooks";
31
- import { TokenPicker } from "./TokenPicker";
32
- import { Skeleton } from "@src/components/Skeleton";
33
11
  import { ADDRESSES } from "@src/contracts";
34
-
35
- interface Props {
36
- dstChainId: string;
37
- dstTokenAddr: string;
38
- customContractCalls: ContractCall[];
39
- desc: string;
40
- supportedChains: Chain[];
41
- onSubmit: (route: Route) => void;
42
- }
43
-
44
- export const Form: FC<Props> = ({
12
+ import { History } from "./History";
13
+ import { TopBar } from "./TopBar";
14
+ import { Settings } from "./Settings";
15
+ import { FeesDetails } from "./FeesDetails";
16
+ import { Summary } from "./Summary";
17
+ import { SwapPanel } from "./SwapPanel";
18
+ import { ErrorField } from "./ErrorField";
19
+ import { Description } from "./Description";
20
+ import { DEFAULT_SOURCE_CHAIN_ID } from "@src/constants";
21
+
22
+ export const Form = ({
45
23
  dstChainId,
46
24
  dstTokenAddr,
47
25
  customContractCalls,
48
26
  desc,
49
27
  supportedChains,
50
28
  onSubmit,
29
+ }: TxConfigFormProps & {
30
+ onSubmit: (route: Route) => void;
51
31
  }) => {
52
- const [signer, setSigner] = useState<string>("");
32
+ const [signer, setSigner] = useState<string | undefined>();
53
33
  const [dstChain, setDstChain] = useState<Chain>();
54
34
  const [dstToken, setDstToken] = useState<Token>();
55
35
  const [srcChain, setSrcChain] = useState<Chain>();
56
36
  const [srcToken, setSrcToken] = useState<Token>();
57
37
  const [amount, setAmount] = useState<string>("");
58
38
  const [paymentToken, setPaymentToken] = useState<Token>();
59
- const [slippage, setSlippage] = useState<number>();
60
- const [slippageActivePresetIndex, setSlippageActivePresetIndex] = useState(1);
61
- const [usingSlippageInput, setUsingSlippageInput] = useState(false);
62
- const [chainListShown, setChainListShown] = useState(false);
63
- const [tokenListShown, setTokenListShown] = useState(false);
64
39
  const [route, setRoute] = useState<Route>();
65
40
  const [expressChecked, setExpressChecked] = useState(true);
66
- const [feesDetailsShown, setFeesDetailsShown] = useState(false);
67
41
  const [historyTabShown, setHistoryTabShown] = useState(false);
68
42
  const [isGettingRoute, setIsGettingRoute] = useState(false);
69
43
  const [prices, setPrices] = useState<TokenPrices>();
70
44
  const [balances, setBalances] = useState<TokenBalances>();
45
+ const [settingsShown, setSettingsShown] = useState(false);
46
+ const [formError, setFormError] = useState("");
47
+ const [slippage, setSlippage] = useState<string>("1.5");
48
+ const [feesDetailsShown, setFeesDetailsShown] = useState(false);
71
49
 
72
50
  const debouncedAmount = useDebounce(amount, 1000);
73
- const { ethereum } = window;
51
+ const account = useAccount();
52
+ const { switchChainAsync } = useSwitchChain();
53
+
54
+ useEffect(() => {
55
+ setSigner(account.address);
56
+ }, [account.address]);
57
+
58
+ useEffect(() => {
59
+ const defaultChain = supportedChains.find(
60
+ ({ chainId }) => chainId === account.chainId?.toString(),
61
+ );
62
+ setSrcChain(
63
+ defaultChain ??
64
+ supportedChains.find(
65
+ ({ chainId }) => chainId === DEFAULT_SOURCE_CHAIN_ID,
66
+ ),
67
+ );
68
+ }, [account.chainId]);
74
69
 
75
70
  useEffect(() => {
76
71
  setDstChain(supportedChains.find((chain) => chain.chainId === dstChainId));
77
- }, [supportedChains]);
72
+ }, [supportedChains, dstChainId]);
78
73
 
79
74
  useEffect(() => {
80
75
  setDstToken(
@@ -84,12 +79,6 @@ export const Form: FC<Props> = ({
84
79
  );
85
80
  }, [dstChain]);
86
81
 
87
- useEffect(() => {
88
- if (!usingSlippageInput) {
89
- setSlippage(SLIPPAGE_PRESETS[slippageActivePresetIndex]);
90
- }
91
- }, [slippageActivePresetIndex, usingSlippageInput]);
92
-
93
82
  useEffect(() => {
94
83
  if (
95
84
  srcChain &&
@@ -99,6 +88,10 @@ export const Form: FC<Props> = ({
99
88
  paymentToken &&
100
89
  slippage
101
90
  ) {
91
+ setFormError("");
92
+ const timeout = setTimeout(() => {
93
+ setFormError("Getting Route is taking longer than usually");
94
+ }, ROUTE_TIMEOUT_MS);
102
95
  setIsGettingRoute(true);
103
96
  setFeesDetailsShown(false);
104
97
  getRoute({
@@ -112,12 +105,21 @@ export const Form: FC<Props> = ({
112
105
  toChain: dstChainId,
113
106
  toToken: dstTokenAddr,
114
107
  paymentToken: paymentToken.address,
115
- slippage,
108
+ slippage: Number(slippage),
116
109
  expressDelivery: expressChecked,
117
110
  customContractCalls,
118
111
  })
119
- .then((response) => setRoute(response))
120
- .finally(() => setIsGettingRoute(false));
112
+ .then((response) => {
113
+ setRoute(response);
114
+ setFormError("");
115
+ })
116
+ .catch((err) => {
117
+ setFormError(err.message);
118
+ })
119
+ .finally(() => {
120
+ setIsGettingRoute(false);
121
+ clearTimeout(timeout);
122
+ });
121
123
  }
122
124
  }, [
123
125
  signer,
@@ -129,52 +131,6 @@ export const Form: FC<Props> = ({
129
131
  expressChecked,
130
132
  ]);
131
133
 
132
- useEffect(() => {
133
- if (ethereum) {
134
- ethereum
135
- .request({ method: "eth_accounts" })
136
- .then((accounts: string[]) => {
137
- if (accounts[0]) {
138
- setSigner(accounts[0]);
139
- }
140
- });
141
-
142
- ethereum.on("accountsChanged", (accounts: string[]) => {
143
- if (accounts[0]) {
144
- setSigner(accounts[0]);
145
- }
146
- });
147
- return () => ethereum.removeListener("accountsChanged", () => {});
148
- }
149
- }, [ethereum]);
150
-
151
- useEffect(() => {
152
- if (
153
- ethereum &&
154
- ethereum.request &&
155
- supportedChains &&
156
- supportedChains.length > 0
157
- ) {
158
- ethereum.request({ method: "eth_chainId" }).then((chainId: string) => {
159
- setSrcChain(
160
- supportedChains.find(
161
- (chain) => chain.chainId === parseInt(chainId, 16).toString(),
162
- ),
163
- );
164
- });
165
-
166
- ethereum.on("chainChanged", (chainId: string) => {
167
- setSrcChain(
168
- supportedChains.find(
169
- (chain) => chain.chainId === parseInt(chainId, 16).toString(),
170
- ),
171
- );
172
- });
173
-
174
- return () => ethereum.removeListener("chainChanged", () => {});
175
- }
176
- }, [ethereum, supportedChains]);
177
-
178
134
  useEffect(() => {
179
135
  if (srcChain) {
180
136
  setPaymentToken(
@@ -188,9 +144,12 @@ export const Form: FC<Props> = ({
188
144
  }, [srcChain]);
189
145
 
190
146
  useEffect(() => {
191
- if (srcChain && !srcToken) {
192
- setSrcToken(srcChain?.tokens[0]);
193
- }
147
+ setAmount("");
148
+ setFormError("");
149
+ }, [srcToken]);
150
+
151
+ useEffect(() => {
152
+ setSrcToken(srcChain?.tokens[0]);
194
153
  }, [srcChain]);
195
154
 
196
155
  useEffect(() => {
@@ -209,357 +168,80 @@ export const Form: FC<Props> = ({
209
168
 
210
169
  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
211
170
  event.preventDefault();
212
- if (route) {
213
- onSubmit(route);
214
- } else {
215
- // TODO error handling (cannot submit if no values)
216
- }
171
+ if (route) onSubmit(route);
172
+ };
173
+
174
+ const handleSwitchChain = async () => {
175
+ await switchChainAsync({ chainId: Number(srcChain?.chainId) });
217
176
  };
218
177
 
219
178
  return (
220
179
  <form
221
- className="flex flex-col gap-2 z-10 my-0 mx-auto p-2 rounded-3xl overflow-hidden w-96 sm:w-[482px] min-h-[562px]"
180
+ className="flex flex-col gap-2 z-10 my-0 mx-auto p-4 rounded-3xl overflow-hidden font-light w-x_mobile sm:w-x_desktop"
222
181
  onSubmit={handleSubmit}
223
182
  >
224
- <div className="flex w-4/5 justify-between mx-auto">
225
- <div className="rounded-lg text-[rgba(255,255,255,0.6)] py-1 px-2 text-sm whitespace-nowrap globalBorder">
226
- {signer
227
- ? `Connected wallet: ${shortAddress(signer)}`
228
- : `Disconnected`}
229
- </div>
230
- {signer && (
231
- <div
232
- onClick={() => setHistoryTabShown((x) => !x)}
233
- className="flex items-center text-xs gap-1 cursor-pointer"
234
- >
235
- {!historyTabShown ? (
236
- <>
237
- <HistoryIcon />
238
- <div>History</div>
239
- </>
240
- ) : (
241
- <div>Back</div>
242
- )}
243
- </div>
244
- )}
245
- </div>
183
+ <TopBar
184
+ signer={signer}
185
+ historyTabShown={historyTabShown}
186
+ setHistoryTabShown={setHistoryTabShown}
187
+ setSettingsShown={setSettingsShown}
188
+ />
189
+ {settingsShown && (
190
+ <Settings
191
+ expressChecked={expressChecked}
192
+ setExpressChecked={setExpressChecked}
193
+ setSettingsShown={setSettingsShown}
194
+ slippage={slippage}
195
+ setSlippage={setSlippage}
196
+ />
197
+ )}
246
198
  {historyTabShown ? (
247
- <div className="flex items-center justify-center w-full">
248
- <History signer={signer} supportedChains={supportedChains} />
249
- </div>
199
+ <History signer={signer} supportedChains={supportedChains} />
250
200
  ) : (
251
201
  <div className="flex flex-col gap-2">
252
- {desc && (
253
- <div className="flex flex-col items-start globalBorder rounded-lg bg-[rgba(15,15,15,1)] p-4">
254
- <div className="w-full flex gap-4 items-start justify-between mb-4">
255
- {desc}
256
- </div>
257
- </div>
202
+ {desc && <Description description={desc} />}
203
+ <SwapPanel
204
+ amount={amount}
205
+ setAmount={setAmount}
206
+ srcToken={srcToken}
207
+ setSrcToken={setSrcToken}
208
+ srcChain={srcChain}
209
+ signer={signer}
210
+ balances={balances}
211
+ prices={prices}
212
+ supportedChains={supportedChains}
213
+ setSrcChain={setSrcChain}
214
+ />
215
+ <Summary
216
+ isGettingRoute={isGettingRoute}
217
+ amount={amount}
218
+ route={route}
219
+ dstToken={dstToken}
220
+ dstChain={dstChain}
221
+ />
222
+ <FeesDetails
223
+ route={route}
224
+ isGettingRoute={isGettingRoute}
225
+ paymentToken={paymentToken}
226
+ dstToken={dstToken}
227
+ expressChecked={expressChecked}
228
+ slippage={slippage}
229
+ feesDetailsShown={feesDetailsShown}
230
+ setFeesDetailsShown={setFeesDetailsShown}
231
+ />
232
+ {formError.length > 0 && <ErrorField error={formError} />}
233
+ {srcChain && srcChain.chainId !== account.chainId?.toString() ? (
234
+ <button type="button" onClick={handleSwitchChain}>
235
+ Switch chain
236
+ </button>
237
+ ) : (
238
+ <button
239
+ type="submit"
240
+ disabled={!signer || !route || isGettingRoute}
241
+ >
242
+ Submit
243
+ </button>
258
244
  )}
259
- <div className="flex flex-col items-start globalBorder rounded-lg bg-[rgba(15,15,15,1)] p-4">
260
- <div className="w-full flex gap-4 items-start justify-between mb-4">
261
- <div className="flex flex-col overflow-hidden w-1/2">
262
- <input
263
- className="p-0 border-none bg-transparent text-2xl overflow-ellipsis"
264
- value={amount}
265
- onChange={(e) => {
266
- if (e.target.value === ".") return;
267
- if (NUMBER_INPUT_REGEX.test(e.target.value)) {
268
- setIsGettingRoute(true);
269
- setFeesDetailsShown(false);
270
- setAmount(e.target.value.replace(/,/g, "."));
271
- }
272
- }}
273
- inputMode="decimal"
274
- spellCheck="false"
275
- autoComplete="off"
276
- autoCorrect="off"
277
- placeholder="0"
278
- />
279
- </div>
280
- <div className="flex flex-col relative items-end gap-2.5 w-1/2">
281
- <div
282
- className="flex gap-2 items-center p-2 max-w-full cursor-pointer bg-black text-white text-xs globalBorder rounded-3xl whitespace-nowrap"
283
- onClick={() => {
284
- setTokenListShown((state) => !state);
285
- }}
286
- >
287
- <img
288
- height={16}
289
- width={16}
290
- src={srcToken?.image}
291
- style={{ marginRight: "4px" }}
292
- alt={srcToken?.name}
293
- />
294
- <div>{srcToken?.symbol}</div>
295
- <DownArrowIcon />
296
- </div>
297
-
298
- {tokenListShown && (
299
- <TokenPicker
300
- onCloseClick={() => setTokenListShown(false)}
301
- tokens={srcChain?.tokens}
302
- signer={signer}
303
- selectedToken={srcToken}
304
- setSelectedToken={(token) => setSrcToken(token)}
305
- balances={balances}
306
- />
307
- )}
308
-
309
- <div className="flex gap-2 items-center text-xs font-medium text-white opacity-60 py-0.5 px-2">
310
- {signer && (
311
- <>
312
- Balance:{" "}
313
- {srcToken && balances ? (
314
- weiToHumanReadable({
315
- amount: balances[srcToken.address]?.toString() || "0",
316
- decimals: srcToken.decimals,
317
- precisionFractionalPlaces: 4,
318
- })
319
- ) : (
320
- <Skeleton w="w-12" h="h-3" />
321
- )}
322
- </>
323
- )}
324
- </div>
325
- </div>
326
- </div>
327
-
328
- <div className="flex justify-between items-center w-full text-xs text-[rgba(255,255,255,0.6)] relative">
329
- {srcToken ? (
330
- prices && prices[srcToken.address] ? (
331
- <div>
332
- {`$${(
333
- Number(amount) * Number(prices[srcToken.address])
334
- ).toFixed(2)}`}
335
- </div>
336
- ) : (
337
- <Alert desc="Unknown price" />
338
- )
339
- ) : (
340
- <>$0.00</>
341
- )}
342
- <div
343
- className="bg-black globalBorder rounded-2xl whitespace-nowrap flex items-center p-2 cursor-pointer gap-1"
344
- onClick={() => setChainListShown((prev) => !prev)}
345
- >
346
- <img
347
- width={12}
348
- height={12}
349
- src={srcChain?.image}
350
- alt={srcChain?.name}
351
- />
352
- <div>{srcChain?.displayName}</div>
353
- <DownArrowIcon />
354
- </div>
355
-
356
- {chainListShown && (
357
- <ul className="bg-black globalBorder rounded-lg whitespace-nowrap z-1 right-0 top-10 px-2 max-w-full max-h-[40vh] overflow-auto absolute text-sm z-20">
358
- {supportedChains
359
- .filter((chain) => chain.chainId !== srcChain?.chainId)
360
- .map((chain, index) => (
361
- <div key={`${chain.ecosystem}-${chain.chainId}`}>
362
- <li
363
- className="bg-transparent border-none flex gap-1 items-center cursor-pointer py-2 w-full"
364
- key={index}
365
- onClick={() => {
366
- setSrcChain(chain);
367
- setChainListShown(false);
368
- }}
369
- >
370
- <img
371
- width={12}
372
- height={12}
373
- src={chain.image}
374
- alt={chain.displayName}
375
- />
376
- {chain.displayName}
377
- </li>
378
- {index !== supportedChains.length - 1 && (
379
- <div className="h-px mx-2 bg-[rgba(255,255,255,0.15)]"></div>
380
- )}
381
- </div>
382
- ))}
383
- </ul>
384
- )}
385
- </div>
386
- </div>
387
- <div className="flex flex-col items-start globalBorder rounded-lg bg-[rgba(15,15,15,1)] p-4">
388
- <div className="flex justify-around sm:justify-start gap-4 items-center w-full text-xs text-[rgba(255,255,255,0.6)]">
389
- <div className="flex flex-col items-center sm:flex-row gap-1">
390
- You're getting:
391
- {isGettingRoute && amount.length > 0 ? (
392
- <div className="bg-current rounded animate-pulse w-[190px] h-7" />
393
- ) : (
394
- <div className="flex gap-1">
395
- <div className="text-white text-lg">
396
- {weiToHumanReadable({
397
- amount: route?.estAmountOut || "0",
398
- decimals: dstToken?.decimals || 18,
399
- precisionFractionalPlaces: 5,
400
- })}
401
- </div>
402
- <div className="flex items-center gap-1 text-white">
403
- <div className="w-4 h-4">
404
- <img src={dstToken?.image} alt={dstToken?.name} />
405
- </div>
406
- <div className="text-lg">{dstToken?.symbol}</div>
407
- </div>
408
- </div>
409
- )}
410
- </div>
411
- <div className="flex flex-col sm:flex-row gap-1">
412
- on chain:
413
- <div className="flex gap-1 text-white">
414
- <div className="w-4 h-4">
415
- <img src={dstChain?.image} alt={dstChain?.name} />
416
- </div>
417
- <div>{dstChain?.displayName}</div>
418
- </div>
419
- </div>
420
- </div>
421
- </div>
422
- <div className="flex flex-col gap-2 globalBorder rounded-lg bg-[rgba(15,15,15,1)] p-4">
423
- <div className="flex w-full items-center justify-between">
424
- <div className="flex gap-1 text-xs font-medium text-white opacity-60">
425
- <CoinsIcon />
426
- <div className="mr-1">Fees:</div>
427
- {isGettingRoute ? (
428
- <div className="bg-current rounded animate-pulse w-20 h-4" />
429
- ) : (
430
- ` ${weiToHumanReadable({
431
- amount: safeBigNumberFrom(
432
- route?.xSwapFees.xSwapFee.nativeFee || "0",
433
- )
434
- .add(safeBigNumberFrom(route?.xSwapFees.ccipFee || "0"))
435
- .add(
436
- safeBigNumberFrom(
437
- route?.xSwapFees.expressDeliveryFee || "0",
438
- ),
439
- )
440
- .toString(),
441
- decimals: 18,
442
- precisionFractionalPlaces: 5,
443
- })} ${paymentToken?.symbol}`
444
- )}
445
- </div>
446
- <div
447
- onClick={() => setFeesDetailsShown((x) => !x)}
448
- className="text-xs font-medium text-white opacity-60 cursor-pointer"
449
- >
450
- {feesDetailsShown ? <ChevronUpIcon /> : <ChevronDownIcon />}
451
- </div>
452
- </div>
453
- {feesDetailsShown && (
454
- <div className="flex w-full items-center justify-between ">
455
- <div className="flex flex-col text-[10px] ">
456
- <div className="font-medium text-white opacity-60">
457
- CCIP Fee:
458
- {` ${weiToHumanReadable({
459
- amount: route?.xSwapFees.ccipFee || "0",
460
- decimals: 18,
461
- precisionFractionalPlaces: 5,
462
- })} ${paymentToken?.symbol}`}
463
- </div>
464
- <div className="font-medium text-white opacity-60">
465
- Native Fee:
466
- {` ${weiToHumanReadable({
467
- amount: route?.xSwapFees.xSwapFee.nativeFee || "0",
468
- decimals: 18,
469
- precisionFractionalPlaces: 5,
470
- })} ${paymentToken?.symbol}`}
471
- </div>
472
- </div>
473
- <div className="flex flex-col text-[10px] ">
474
- <div className="font-medium text-white opacity-60">
475
- Slippage: {`${slippage}%`}
476
- </div>
477
- <div className="font-medium text-white opacity-60">
478
- Min amount out:{" "}
479
- {weiToHumanReadable({
480
- amount: route?.minAmountOut || "0",
481
- decimals: dstToken?.decimals || 18,
482
- precisionFractionalPlaces: 5,
483
- })}{" "}
484
- {dstToken?.symbol}
485
- </div>
486
- </div>
487
- </div>
488
- )}
489
- </div>
490
- <div className="flex justify-between">
491
- <div className="flex flex-col gap-2 ">
492
- <div className=" text-[rgba(255,255,255,0.6)]">Slippage:</div>
493
- <div className="flex gap-2">
494
- <div className="flex items-center bg-[rgba(15,15,15,1)] p-1 globalBorder rounded-xl">
495
- {SLIPPAGE_PRESETS.map((preset, index) => (
496
- <div
497
- key={index}
498
- className={`transition-all cursor-pointer block rounded-lg p-2 border-none text-sm ${
499
- index === slippageActivePresetIndex &&
500
- !usingSlippageInput
501
- ? "text-white"
502
- : "text-[rgba(255,255,255,0.2)]"
503
- } ${
504
- index === slippageActivePresetIndex &&
505
- !usingSlippageInput
506
- ? "bg-gradient-to-r from-[#3681c6] to-[#2b4a9d]"
507
- : ""
508
- }`}
509
- onClick={() => {
510
- setUsingSlippageInput(false);
511
- setSlippageActivePresetIndex(index);
512
- }}
513
- >
514
- {`${preset}%`}
515
- </div>
516
- ))}
517
- </div>
518
- <div className="bg-[rgba(39,39,39,1)] p-2 border border-solid border-[rgba(82,82,82,1)] rounded-xl text-white flex items-center">
519
- <input
520
- className="text-white border-none bg-[rgba(39,39,39,1)] w-10 placeholder:text-[rgba(255,255,255,0.2)] focus:outline-none"
521
- placeholder="1.5"
522
- value={usingSlippageInput ? slippage : ""}
523
- onChange={(e) => {
524
- if (e.target.value.length > 0) {
525
- if (NUMBER_INPUT_REGEX.test(e.target.value)) {
526
- setUsingSlippageInput(true);
527
- setSlippage(Number(e.target.value));
528
- }
529
- } else {
530
- setUsingSlippageInput(false);
531
- setSlippageActivePresetIndex(1);
532
- }
533
- }}
534
- />
535
- <PercentageIcon />
536
- </div>
537
- </div>
538
- </div>
539
-
540
- <div className="flex gap-2 items-center">
541
- <div className=" text-[rgba(255,255,255,0.6)]">
542
- Express delivery:
543
- </div>
544
- <div>
545
- <span
546
- className="inline-flex w-14 h-9 p-3 relative align-middle box-border overflow-hidden cursor-pointer"
547
- onClick={() => {
548
- setExpressChecked((x) => !x);
549
- }}
550
- >
551
- <span className="h-full, w-full rounded-lg bg-[rgba(255,255,255,0.3)]"></span>
552
- <span
553
- className={`transition-all w-5 h-5 rounded-full absolute translate-y-[-4px]
554
- ${
555
- expressChecked ? "translate-x-[12px] bg-x_blue" : "bg-white"
556
- } `}
557
- ></span>
558
- </span>
559
- </div>
560
- </div>
561
- </div>
562
- <button disabled={!signer || !route || isGettingRoute}>Submit</button>
563
245
  </div>
564
246
  )}
565
247
  </form>