@xswap-link/sdk 0.13.0 → 0.14.0

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/CHANGELOG.md +15 -0
  2. package/dist/index.d.mts +6 -2
  3. package/dist/index.d.ts +6 -2
  4. package/dist/index.global.js +90 -89
  5. package/dist/index.js +1633 -1519
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +1597 -1480
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +1 -1
  10. package/src/assets/icons/AdjustmentsIcon.tsx +18 -0
  11. package/src/assets/icons/CaretDownIcon.tsx +12 -0
  12. package/src/assets/icons/CaretsUpDownIcon.tsx +13 -0
  13. package/src/assets/icons/ChainlinkMarkIcon.tsx +17 -0
  14. package/src/assets/icons/ClockIcon.tsx +18 -0
  15. package/src/assets/icons/WarningIcon.tsx +16 -0
  16. package/src/assets/icons/index.ts +6 -0
  17. package/src/components/Modal/index.tsx +2 -2
  18. package/src/components/PoweredBy/index.tsx +14 -5
  19. package/src/components/Swap/Header/Controls/index.tsx +50 -36
  20. package/src/components/Swap/Header/index.tsx +12 -5
  21. package/src/components/Swap/HistoryView/ActivityCard/index.tsx +212 -0
  22. package/src/components/Swap/HistoryView/index.tsx +24 -6
  23. package/src/components/Swap/ReorderButton/ReorderButton.tsx +5 -7
  24. package/src/components/Swap/SettingsView/Delivery/index.tsx +27 -28
  25. package/src/components/Swap/SettingsView/InfiniteApproval/index.tsx +27 -24
  26. package/src/components/Swap/SettingsView/Slippage/index.tsx +44 -43
  27. package/src/components/Swap/SettingsView/index.tsx +1 -1
  28. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/index.tsx +2 -2
  29. package/src/components/Swap/SwapView/FeesPanel/DeliveryInfo/index.tsx +9 -19
  30. package/src/components/Swap/SwapView/FeesPanel/Fee/index.tsx +4 -4
  31. package/src/components/Swap/SwapView/FeesPanel/Fees/index.tsx +5 -9
  32. package/src/components/Swap/SwapView/FeesPanel/index.tsx +21 -22
  33. package/src/components/Swap/SwapView/SwapButton/index.tsx +25 -6
  34. package/src/components/Swap/SwapView/SwapPanel/AmountPanel/Balance/index.tsx +56 -12
  35. package/src/components/Swap/SwapView/SwapPanel/AmountPanel/index.tsx +42 -41
  36. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/NetworkFilter/index.tsx +157 -0
  37. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/TokenItem/index.tsx +98 -55
  38. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +160 -239
  39. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/index.tsx +57 -31
  40. package/src/components/Swap/SwapView/SwapPanel/UsdValue/index.tsx +27 -0
  41. package/src/components/Swap/SwapView/SwapPanel/WalletPanel/index.tsx +60 -49
  42. package/src/components/Swap/SwapView/SwapPanel/index.tsx +20 -25
  43. package/src/components/Swap/SwapView/WalletPicker/index.tsx +6 -1
  44. package/src/components/Swap/SwapView/index.tsx +2 -1
  45. package/src/components/Swap/index.tsx +4 -2
  46. package/src/components/ToggleButton/index.tsx +18 -15
  47. package/src/components/TxConfigForm/index.tsx +5 -5
  48. package/src/components/TxWidgetWC/index.tsx +4 -0
  49. package/src/components/TxWidgetWCWrapped/index.tsx +2 -0
  50. package/src/context/HistoryProvider.tsx +1 -0
  51. package/src/context/SwapProvider.tsx +94 -67
  52. package/src/models/TransactionHistory.ts +1 -0
  53. package/src/models/payloads/ModalIntegrationPayload.ts +1 -0
  54. package/src/models/payloads/WidgetIntegrationPayload.ts +1 -0
  55. package/src/utils/index.ts +21 -0
  56. package/tailwind.config.js +2 -2
  57. package/src/components/Swap/SwapView/SwapPanel/ChainPanel/ChainPicker/ChainItem/index.tsx +0 -97
  58. package/src/components/Swap/SwapView/SwapPanel/ChainPanel/ChainPicker/index.tsx +0 -103
  59. package/src/components/Swap/SwapView/SwapPanel/ChainPanel/index.tsx +0 -65
  60. package/src/components/Swap/SwapView/SwapPanel/MaxPanel/index.tsx +0 -43
  61. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/QuickPickTokenItem/index.tsx +0 -37
@@ -1,9 +1,9 @@
1
1
  import { SafeInput, Skeleton } from "@src/components";
2
2
  import { NUMBER_INPUT_REGEX } from "@src/constants";
3
3
  import { useSwapContext } from "@src/context";
4
+ import { weiToHumanReadable } from "@src/utils";
4
5
  import { useMemo } from "react";
5
6
  import { SwapPanelType } from "../../../SwapView";
6
- import { Balance } from "./Balance";
7
7
  import { Ecosystem } from "@src/models";
8
8
 
9
9
  type Props = {
@@ -14,16 +14,29 @@ export const AmountPanel = ({ type }: Props) => {
14
14
  const {
15
15
  srcChain,
16
16
  srcValue,
17
- srcValueUsd,
17
+ srcToken,
18
+ srcTokenBalanceWei,
18
19
  dstToken,
19
20
  dstChain,
20
21
  dstValue,
21
- dstValueUsd,
22
22
  setSrcValue,
23
23
  isFetchingRoute,
24
24
  bridgeUI,
25
25
  } = useSwapContext();
26
26
 
27
+ const onMaxClick = () => {
28
+ if (!srcTokenBalanceWei) {
29
+ return;
30
+ }
31
+ setSrcValue(
32
+ weiToHumanReadable({
33
+ amount: srcTokenBalanceWei,
34
+ decimals: srcToken?.decimals || 18,
35
+ precisionFractionalPlaces: srcToken?.decimals || 18,
36
+ }),
37
+ );
38
+ };
39
+
27
40
  const value = useMemo(() => {
28
41
  if (
29
42
  type === "destination" &&
@@ -45,46 +58,34 @@ export const AmountPanel = ({ type }: Props) => {
45
58
  dstChain,
46
59
  ]);
47
60
 
48
- const valueUsd = useMemo(
49
- () => (type === "source" ? srcValueUsd : dstValueUsd),
50
- [dstValueUsd, srcValueUsd, type],
51
- );
52
-
53
61
  return (
54
- <div className="flex justify-between items-center w-full p-4 gap-8 overflow-hidden">
55
- <div className="flex flex-col font-medium w-full overflow-hidden text-left">
56
- {type === "source" ? (
57
- <>
58
- <SafeInput
59
- id="swap-amount-input"
60
- className={`bg-transparent border-none p-0 text-xl outline-none font-sans-bold font-bold`}
61
- regex={NUMBER_INPUT_REGEX}
62
- value={srcValue}
63
- onChange={(value) => setSrcValue(value)}
64
- />
65
- {valueUsd && (
66
- <div className={`text-sx opacity-60`}>{`$${valueUsd}`}</div>
67
- )}
68
- </>
69
- ) : isFetchingRoute ? (
70
- <div className="flex flex-col gap-1">
71
- <Skeleton className="w-[150px] h-[30px]" />
72
- <Skeleton className="w-[100px] h-[20px]" />
73
- </div>
74
- ) : (
75
- <>
76
- <div
77
- className={`text-xl text-t_text_secondary font-sans-bold font-bold`}
62
+ <div className="flex items-center gap-3 flex-1 min-w-0 overflow-hidden text-left">
63
+ {type === "source" ? (
64
+ <>
65
+ <SafeInput
66
+ id="swap-amount-input"
67
+ className="bg-transparent border-none p-0 text-2xl leading-8 outline-none w-full min-w-0 text-t_text_primary placeholder:text-t_text_primary placeholder:text-opacity-40"
68
+ regex={NUMBER_INPUT_REGEX}
69
+ value={srcValue}
70
+ onChange={(value) => setSrcValue(value)}
71
+ />
72
+ {!!srcTokenBalanceWei && (
73
+ <button
74
+ type="button"
75
+ onClick={onMaxClick}
76
+ className="shrink-0 px-2.5 py-1 rounded-full border border-solid border-t_main_accent_light bg-gradient-to-r from-t_main_accent_light/25 to-t_main_accent_dark/25 text-[10px] font-medium leading-[18px] tracking-[0.08em] text-t_text_primary hover:from-t_main_accent_light/40 hover:to-t_main_accent_dark/40 transition-colors"
78
77
  >
79
- {value || 0}
80
- </div>
81
- {valueUsd && (
82
- <div className={`text-sx opacity-60`}>{`$${valueUsd}`}</div>
83
- )}
84
- </>
85
- )}
86
- </div>
87
- {type === "source" && <Balance />}
78
+ MAX
79
+ </button>
80
+ )}
81
+ </>
82
+ ) : isFetchingRoute ? (
83
+ <Skeleton className="w-[150px] h-[32px]" />
84
+ ) : (
85
+ <div className="text-2xl leading-8 text-t_text_primary opacity-60 truncate">
86
+ {value || 0}
87
+ </div>
88
+ )}
88
89
  </div>
89
90
  );
90
91
  };
@@ -0,0 +1,157 @@
1
+ import { ChevronDownIcon, SearchIcon } from "@src/assets/icons";
2
+ import { useSwapContext } from "@src/context";
3
+ import { SwapPanelType } from "@src/components/Swap/SwapView";
4
+ import { Chain } from "@src/models";
5
+ import { useEffect, useMemo, useRef, useState } from "react";
6
+ import { Virtuoso } from "react-virtuoso";
7
+
8
+ type Props = {
9
+ type: SwapPanelType;
10
+ selectedChain: Chain | undefined;
11
+ onSelect: (chain: Chain | undefined) => void;
12
+ };
13
+
14
+ /**
15
+ * Network filter embedded in the token picker search bar.
16
+ * Selecting a network only narrows the token list — the swap chain is
17
+ * set when a token gets picked.
18
+ */
19
+ export const NetworkFilter = ({ type, selectedChain, onSelect }: Props) => {
20
+ const { srcChainOptions, dstChainOptions } = useSwapContext();
21
+ const [open, setOpen] = useState(false);
22
+ const [searchValue, setSearchValue] = useState("");
23
+ const containerRef = useRef<HTMLDivElement | null>(null);
24
+
25
+ const chains = useMemo(
26
+ () => (type === "source" ? srcChainOptions : dstChainOptions),
27
+ [type, srcChainOptions, dstChainOptions],
28
+ );
29
+
30
+ const filteredChains = useMemo(() => {
31
+ if (!searchValue) return chains;
32
+ const searchLower = searchValue.toLowerCase();
33
+ return chains.filter(
34
+ (chain) =>
35
+ chain.displayName.toLowerCase().includes(searchLower) ||
36
+ chain.chainId.toLowerCase().includes(searchLower),
37
+ );
38
+ }, [chains, searchValue]);
39
+
40
+ useEffect(() => {
41
+ if (!open) {
42
+ setSearchValue("");
43
+ }
44
+ }, [open]);
45
+
46
+ useEffect(() => {
47
+ const onPointerDown = (e: Event) => {
48
+ if (
49
+ containerRef.current &&
50
+ !e.composedPath().includes(containerRef.current)
51
+ ) {
52
+ setOpen(false);
53
+ }
54
+ };
55
+ document.addEventListener("mousedown", onPointerDown);
56
+ return () => document.removeEventListener("mousedown", onPointerDown);
57
+ }, []);
58
+
59
+ const previewChains = chains.slice(0, 4);
60
+
61
+ return (
62
+ <div className="relative" ref={containerRef}>
63
+ <button
64
+ type="button"
65
+ onClick={() => setOpen((state) => !state)}
66
+ className="flex items-center gap-2 h-12 px-3 py-2 rounded-lg bg-t_bg_tertiary bg-opacity-[0.04] hover:bg-opacity-10 transition-colors"
67
+ >
68
+ {selectedChain ? (
69
+ <img
70
+ src={selectedChain.image}
71
+ alt={selectedChain.displayName}
72
+ className="w-5 h-5 rounded-full"
73
+ />
74
+ ) : (
75
+ <div className="grid grid-cols-2 gap-0.5 w-6 shrink-0">
76
+ {previewChains.map((chain) => (
77
+ <img
78
+ key={chain.chainId}
79
+ src={chain.image}
80
+ alt={chain.displayName}
81
+ className="w-[11px] h-[11px] rounded-full"
82
+ />
83
+ ))}
84
+ </div>
85
+ )}
86
+ <p className="text-sm leading-6 tracking-[0.01em] text-t_text_primary whitespace-nowrap max-w-[120px] truncate">
87
+ {selectedChain ? selectedChain.displayName : "All Networks"}
88
+ </p>
89
+ <div
90
+ className={`flex items-center justify-center w-6 h-6 fill-t_text_primary opacity-50 transition-transform [&_svg]:w-3 [&_svg]:h-3 ${
91
+ open ? "rotate-180" : ""
92
+ }`}
93
+ >
94
+ <ChevronDownIcon />
95
+ </div>
96
+ </button>
97
+
98
+ {open && (
99
+ <div className="absolute right-0 top-[calc(100%+8px)] z-20 flex flex-col w-[220px] max-h-[280px] rounded-xl bg-t_bg_primary border border-solid border-t_text_primary border-opacity-10 backdrop-blur-[24px] overflow-hidden shadow-lg">
100
+ <div className="flex items-center gap-2 px-4 py-3 border-b border-solid border-t_text_primary border-opacity-10">
101
+ <div className="fill-t_text_primary opacity-50 [&_svg]:w-3.5 [&_svg]:h-3.5 flex items-center">
102
+ <SearchIcon />
103
+ </div>
104
+ <input
105
+ value={searchValue}
106
+ onChange={(e) => setSearchValue(e.target.value)}
107
+ placeholder={`Browse ${chains.length} Chains...`}
108
+ className="bg-transparent border-none outline-none w-full text-sm text-t_text_primary placeholder:text-t_text_primary placeholder:text-opacity-40"
109
+ />
110
+ </div>
111
+ <div className="h-[200px]">
112
+ <Virtuoso
113
+ style={{ height: "200px" }}
114
+ data={[undefined, ...filteredChains] as (Chain | undefined)[]}
115
+ itemContent={(_, chain) => (
116
+ <button
117
+ type="button"
118
+ onClick={() => {
119
+ onSelect(chain);
120
+ setOpen(false);
121
+ }}
122
+ className={`flex items-center gap-2 w-full px-4 py-2 text-left hover:bg-t_bg_tertiary hover:bg-opacity-10 transition-colors ${
123
+ (chain?.chainId || "") === (selectedChain?.chainId || "")
124
+ ? "bg-t_bg_tertiary bg-opacity-10"
125
+ : ""
126
+ }`}
127
+ >
128
+ {chain ? (
129
+ <img
130
+ src={chain.image}
131
+ alt={chain.displayName}
132
+ className="w-5 h-5 rounded-full"
133
+ />
134
+ ) : (
135
+ <div className="grid grid-cols-2 gap-px w-5 shrink-0">
136
+ {previewChains.map((previewChain) => (
137
+ <img
138
+ key={previewChain.chainId}
139
+ src={previewChain.image}
140
+ alt={previewChain.displayName}
141
+ className="w-[9px] h-[9px] rounded-full"
142
+ />
143
+ ))}
144
+ </div>
145
+ )}
146
+ <p className="text-sm leading-6 text-t_text_primary truncate">
147
+ {chain ? chain.displayName : "All Networks"}
148
+ </p>
149
+ </button>
150
+ )}
151
+ />
152
+ </div>
153
+ </div>
154
+ )}
155
+ </div>
156
+ );
157
+ };
@@ -1,9 +1,9 @@
1
- import { StarsIcon } from "@src/assets/icons";
1
+ import { StarsIcon, WarningIcon } from "@src/assets/icons";
2
2
  import { Skeleton, TokenLogoWithChain } from "@src/components";
3
3
  import { SwapPanelType } from "@src/components/Swap/SwapView";
4
4
  import { useSwapContext } from "@src/context";
5
5
  import { TokenOption } from "@src/models";
6
- import { weiToHumanReadable } from "@src/utils";
6
+ import { shortAddress, weiToHumanReadable } from "@src/utils";
7
7
  import { ethers } from "ethers";
8
8
  import { useMemo } from "react";
9
9
  import { useAccount } from "wagmi";
@@ -14,6 +14,11 @@ type Props = {
14
14
  selectedChainId: string | undefined;
15
15
  onClick: () => void;
16
16
  panelType: SwapPanelType;
17
+ /**
18
+ * When picking this token would disconnect the other side of the swap,
19
+ * names the side that will be reset ("source" | "destination").
20
+ */
21
+ resetWarning?: SwapPanelType | null;
17
22
  };
18
23
  export const TokenItem = ({
19
24
  token,
@@ -21,6 +26,7 @@ export const TokenItem = ({
21
26
  onClick,
22
27
  selectedChainId,
23
28
  panelType,
29
+ resetWarning,
24
30
  }: Props) => {
25
31
  const {
26
32
  supportedChains,
@@ -55,66 +61,103 @@ export const TokenItem = ({
55
61
  return tokenBalance * +tokenPrice;
56
62
  }, [tokenBalance, tokenPrice, hasBalance]);
57
63
 
64
+ const isSelected =
65
+ token.address.toLowerCase() === selectedTokenAddress?.toLowerCase() &&
66
+ selectedChainId &&
67
+ token.chainId === selectedChainId;
68
+
58
69
  return (
59
- <div
60
- className={`token-picker-container ${
61
- token.address.toLowerCase() === selectedTokenAddress?.toLowerCase() &&
62
- selectedChainId &&
63
- token.chainId === selectedChainId
64
- ? "bg-t_bg_tertiary bg-opacity-10"
65
- : ""
66
- }`}
67
- onClick={onClick}
68
- >
69
- <TokenLogoWithChain
70
- token={token}
71
- chain={chain}
72
- tokenLogoClassName="min-w-9 w-9 h-9"
73
- generatedLogoClassName="min-w-9 w-9 h-9 text-[16px]"
74
- chainLogoClassName="w-5"
75
- />
76
- <div className="flex justify-between items-center gap-1 w-full">
77
- <div className="text-left">
78
- <div className="text-sm font-medium text-ellipsis text-t_text_primary flex items-center gap-1">
79
- {token.name}
80
- {integrationConfig?.highlightedDstTokens?.includes(token.symbol) &&
81
- panelType === "destination" && (
70
+ <div className="pb-2">
71
+ <div
72
+ className={`flex gap-3 items-center p-3 sm:p-4 rounded-xl cursor-pointer bg-gradient-to-r from-t_bg_tertiary/[0.02] to-t_bg_tertiary/[0.02] bg-t_bg_secondary bg-opacity-15 hover:bg-t_bg_tertiary hover:bg-opacity-10 transition-colors ${
73
+ isSelected ? "!bg-t_bg_tertiary !bg-opacity-10" : ""
74
+ }`}
75
+ onClick={onClick}
76
+ >
77
+ <div className={`shrink-0 ${resetWarning ? "opacity-40" : ""}`}>
78
+ <TokenLogoWithChain
79
+ token={token}
80
+ chain={chain}
81
+ tokenLogoClassName="min-w-10 w-10 h-10"
82
+ generatedLogoClassName="min-w-10 w-10 h-10 text-[16px]"
83
+ chainLogoClassName="w-[18px]"
84
+ />
85
+ </div>
86
+ <div className="flex justify-between items-center gap-2 w-full min-w-0">
87
+ <div
88
+ className={`text-left min-w-0 ${resetWarning ? "opacity-40" : ""}`}
89
+ >
90
+ <div className="text-sm font-medium leading-5 tracking-[0.01em] text-t_text_primary flex items-center gap-1">
91
+ {token.symbol}
92
+ {integrationConfig?.highlightedDstTokens?.includes(
93
+ token.symbol,
94
+ ) &&
95
+ panelType === "destination" && (
96
+ <div
97
+ className="flex items-center justify-center text-t_text_primary rounded-full w-3.5 h-3.5 text-xs"
98
+ title="AI-powered token"
99
+ >
100
+ <StarsIcon />
101
+ </div>
102
+ )}
103
+ </div>
104
+ <div className="flex gap-2 items-center text-xs leading-[18px] tracking-[0.01em] text-t_text_primary text-opacity-50">
105
+ <span className="whitespace-nowrap">
106
+ {chain?.displayName || token.name}
107
+ </span>
108
+ {!resetWarning && (
109
+ <>
110
+ <span className="w-0.5 h-0.5 bg-t_text_primary opacity-25 shrink-0" />
111
+ <span className="whitespace-nowrap hidden sm:block">
112
+ {shortAddress(token.address)}
113
+ </span>
114
+ </>
115
+ )}
116
+ </div>
117
+ </div>
118
+
119
+ <div className="text-right shrink-0">
120
+ {address && isFetchingBalances ? (
121
+ <Skeleton className="w-[48px] h-[12px]" />
122
+ ) : (
123
+ token.balance &&
124
+ token.decimals &&
125
+ address && (
82
126
  <div
83
- className={`flex items-center justify-center text-white rounded-full border-t_bg_secondary ${"w-3.5 h-3.5 text-xs"}`}
84
- title="AI-powered token"
127
+ className={`text-sm font-medium leading-5 tracking-[0.01em] text-t_text_primary ${
128
+ resetWarning ? "opacity-40" : ""
129
+ }`}
85
130
  >
86
- <StarsIcon />
131
+ {token.balance.eq(0) || tokenBalance! > 0.0001
132
+ ? weiToHumanReadable({
133
+ amount: token.balance.toString(),
134
+ decimals: token.decimals,
135
+ precisionFractionalPlaces: 4,
136
+ })
137
+ : "<0.0001"}
87
138
  </div>
88
- )}
89
- </div>
90
- <div className="text-xs text-t_text_primary text-opacity-50 font-medium">
91
- {token.symbol}
92
- </div>
93
- </div>
94
- {token.balance && token.decimals && address && !isFetchingBalances && (
95
- <div className="text-right">
96
- <div className="text-xs text-t_text_primary">
97
- {token.balance.eq(0) || tokenBalance! > 0.0001
98
- ? weiToHumanReadable({
99
- amount: token.balance.toString(),
100
- decimals: token.decimals,
101
- precisionFractionalPlaces: 4,
102
- })
103
- : "<0.0001"}
104
- </div>
105
- {hasBalance && (
106
- <div className="text-xs text-t_text_primary text-opacity-50">
107
- {tokenValueUsd ? `$${tokenValueUsd.toFixed(2)}` : "$ -"}
139
+ )
140
+ )}
141
+ {resetWarning ? (
142
+ <div className="flex items-center justify-end gap-1.5 text-[#D77F02]">
143
+ <WarningIcon className="w-3.5 h-3.5 shrink-0" />
144
+ <span className="text-xs leading-[18px] tracking-[0.01em] whitespace-nowrap">
145
+ {resetWarning === "source"
146
+ ? "Source will reset after selection."
147
+ : "Destination will reset after selection."}
148
+ </span>
108
149
  </div>
150
+ ) : (
151
+ !isFetchingBalances &&
152
+ hasBalance &&
153
+ address && (
154
+ <div className="text-xs leading-[18px] tracking-[0.01em] text-t_text_primary text-opacity-50">
155
+ {tokenValueUsd ? `${tokenValueUsd.toFixed(2)} USD` : "-"}
156
+ </div>
157
+ )
109
158
  )}
110
159
  </div>
111
- )}
112
-
113
- {address && isFetchingBalances && (
114
- <div>
115
- <Skeleton className="w-[48px] h-[12px]" />
116
- </div>
117
- )}
160
+ </div>
118
161
  </div>
119
162
  </div>
120
163
  );