@xswap-link/sdk 0.9.0 → 0.9.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xswap-link/sdk",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "JavaScript SDK for XSwap platform",
5
5
  "homepage": "https://github.com/xswap-link/xswap-sdk",
6
6
  "repository": {
@@ -1,10 +1,9 @@
1
- import { DotGreenIcon, HelpIcon } from "@src/assets/icons";
1
+ import { DotGreenIcon } from "@src/assets/icons";
2
2
  import { SwapPanelType } from "@src/components/Swap/SwapView";
3
- import { Tooltip } from "@src/components/Tooltip";
4
3
  import { useSwapContext } from "@src/context";
5
4
  import useNetworks from "@src/hooks/networkManagement/useNetworks";
6
5
  import { Chain } from "@src/models";
7
- import { useMemo, useRef } from "react";
6
+ import { useMemo } from "react";
8
7
 
9
8
  type Props = {
10
9
  chain: Chain;
@@ -18,8 +17,6 @@ export const ChainItem = ({ chain, type, onClick, disabled }: Props) => {
18
17
  networks: { evm },
19
18
  } = useNetworks();
20
19
 
21
- const tooltipTriggerRef = useRef<SVGSVGElement>(null);
22
-
23
20
  const { srcChain, dstChain, bridgeUI } = useSwapContext();
24
21
 
25
22
  const currentChainId = useMemo(
@@ -40,66 +37,61 @@ export const ChainItem = ({ chain, type, onClick, disabled }: Props) => {
40
37
  return type === "destination" ? srcChain?.chainId : dstChain?.chainId;
41
38
  }, [bridgeUI, dstChain?.chainId, srcChain?.chainId, type]);
42
39
 
43
- return (
44
- <div
45
- onClick={() => {
46
- if (chain.chainId === sameChainId || disabled) {
47
- return;
48
- }
49
- onClick(chain.chainId);
50
- }}
51
- className={`flex justify-between items-center gap-3 mt-2 mb-2 cursor-pointer p-4 border border-solid border-white border-opacity-0 hover:selected-chain-item ${
52
- currentChainId === chain.chainId ? "selected-chain-item" : ""
53
- } ${
54
- chain.chainId === sameChainId || disabled
55
- ? "disabled-chain-item cursor-default"
56
- : ""
57
- }`}
58
- >
59
- <div className="flex gap-3 items-center w-full">
60
- <img src={chain.image} alt={chain.name} className="w-[34px] h-[34px]" />
61
- <p>{chain.displayName}</p>
62
- </div>
40
+ const getTooltipText = () => {
41
+ if (chain.chainId === sameChainId) {
42
+ return "Transfers between the same chains are not available";
43
+ }
44
+ if (disabled) {
45
+ return bridgeUI
46
+ ? "Token is not available on this chain"
47
+ : `Swap from ${
48
+ srcChain?.displayName || "the source chain"
49
+ } to this chain is currently not supported.`;
50
+ }
51
+ return "";
52
+ };
63
53
 
64
- {connected && (
65
- <div className="flex items-baseline gap-1">
66
- <DotGreenIcon />
67
- <div className="text-t_text_green">connected</div>
68
- </div>
69
- )}
54
+ const tooltipText = getTooltipText();
70
55
 
71
- {disabled && chain.chainId !== sameChainId && (
72
- <div className="flex items-center gap-1">
73
- <Tooltip
74
- text={
75
- bridgeUI
76
- ? "Token is not available on this chain"
77
- : `Transfers from ${
78
- srcChain?.name || "the source chain"
79
- } to this chain are currently not supported.`
80
- }
81
- position="BOTTOM"
82
- triggerRef={tooltipTriggerRef}
83
- id="transfers-from-chain-not-supported-tooltip"
56
+ return (
57
+ <div className="group relative">
58
+ <div
59
+ onClick={() => {
60
+ if (chain.chainId === sameChainId || disabled) {
61
+ return;
62
+ }
63
+ onClick(chain.chainId);
64
+ }}
65
+ className={`flex justify-between items-center gap-2 mt-2 mb-2 cursor-pointer p-4 border border-solid border-white border-opacity-0 hover:selected-chain-item ${
66
+ currentChainId === chain.chainId ? "selected-chain-item" : ""
67
+ } ${
68
+ chain.chainId === sameChainId || disabled
69
+ ? "disabled-chain-item cursor-default"
70
+ : ""
71
+ }`}
72
+ >
73
+ <div className="flex gap-3 items-center w-full">
74
+ <img
75
+ src={chain.image}
76
+ alt={chain.name}
77
+ className="w-[34px] h-[34px]"
84
78
  />
79
+ <p>{chain.displayName}</p>
85
80
  </div>
86
- )}
87
81
 
88
- {chain.chainId === sameChainId && (
89
- <div className="flex items-center gap-1">
90
- <Tooltip
91
- text="Transfers between the same chain are not available"
92
- id="transfers-between-same-chains-tooltip"
93
- position="BOTTOM"
94
- triggerRef={tooltipTriggerRef}
95
- />
96
- </div>
97
- )}
82
+ {tooltipText && (
83
+ <div className="w-full text-center opacity-0 group-hover:opacity-100 text-xs transition-opacity duration-200">
84
+ {tooltipText}
85
+ </div>
86
+ )}
98
87
 
99
- <HelpIcon
100
- ref={tooltipTriggerRef}
101
- className="cursor-help fill-t_main_accent_light w-[18px] h-[18px] min-w-[18px] min-h-[18px] max-w-[18px] max-h-[18px]"
102
- />
88
+ {connected && (
89
+ <div className="flex items-baseline gap-1">
90
+ <DotGreenIcon />
91
+ <div className="text-t_text_green">connected</div>
92
+ </div>
93
+ )}
94
+ </div>
103
95
  </div>
104
96
  );
105
97
  };
@@ -18,7 +18,7 @@ export const TokenItem = ({
18
18
  onClick,
19
19
  selectedChainId,
20
20
  }: Props) => {
21
- const { supportedChains } = useSwapContext();
21
+ const { supportedChains, tokenPrices } = useSwapContext();
22
22
  const { address } = useAccount();
23
23
 
24
24
  const chain = useMemo(
@@ -26,6 +26,26 @@ export const TokenItem = ({
26
26
  [supportedChains, token],
27
27
  );
28
28
 
29
+ const tokenPrice = useMemo(() => {
30
+ const prices = tokenPrices[token.chainId];
31
+ return prices?.[token.address];
32
+ }, [token, tokenPrices]);
33
+
34
+ const tokenBalance = useMemo(() => {
35
+ if (!token.balance || !token.decimals) return null;
36
+ return Number(ethers.utils.formatUnits(token.balance, token.decimals));
37
+ }, [token.balance, token.decimals]);
38
+
39
+ const hasBalance = useMemo(
40
+ () => token.balance && !token.balance.eq(0),
41
+ [token.balance],
42
+ );
43
+
44
+ const tokenValueUsd = useMemo(() => {
45
+ if (!tokenBalance || !tokenPrice || !hasBalance) return null;
46
+ return tokenBalance * +tokenPrice;
47
+ }, [tokenBalance, tokenPrice, hasBalance]);
48
+
29
49
  return (
30
50
  <div
31
51
  className={`token-picker-container ${
@@ -55,16 +75,21 @@ export const TokenItem = ({
55
75
  </div>
56
76
 
57
77
  {token.balance && token.decimals ? (
58
- <div className="text-xs text-t_text_primary text-opacity-50">
59
- {token.balance.eq(0) ||
60
- Number(ethers.utils.formatUnits(token.balance, token.decimals)) >
61
- 0.0001
62
- ? weiToHumanReadable({
63
- amount: token.balance.toString(),
64
- decimals: token.decimals,
65
- precisionFractionalPlaces: 4,
66
- })
67
- : "<0.0001"}
78
+ <div className="text-right">
79
+ <div className="text-xs text-t_text_primary">
80
+ {token.balance.eq(0) || tokenBalance! > 0.0001
81
+ ? weiToHumanReadable({
82
+ amount: token.balance.toString(),
83
+ decimals: token.decimals,
84
+ precisionFractionalPlaces: 4,
85
+ })
86
+ : "<0.0001"}
87
+ </div>
88
+ {hasBalance && (
89
+ <div className="text-xs text-t_text_primary text-opacity-50">
90
+ {tokenValueUsd ? `$${tokenValueUsd.toFixed(2)}` : "$ -"}
91
+ </div>
92
+ )}
68
93
  </div>
69
94
  ) : (
70
95
  <>
@@ -51,8 +51,12 @@ export const TokenPicker = ({
51
51
  const currentRouteRequestNonce = useRef<number>(0);
52
52
  const tokenSearchRef = useRef<HTMLInputElement | null>(null);
53
53
 
54
- const { bridgeUI, supportedChains, findTokenDataByAddressAndChain } =
55
- useSwapContext();
54
+ const {
55
+ bridgeUI,
56
+ supportedChains,
57
+ findTokenDataByAddressAndChain,
58
+ tokenPrices,
59
+ } = useSwapContext();
56
60
  const { address } = useAccount();
57
61
  const evmContractApi = useEvmContractApi();
58
62
 
@@ -191,6 +195,15 @@ export const TokenPicker = ({
191
195
  }, [isOpen]);
192
196
 
193
197
  const allTokens = useMemo(() => {
198
+ const calculateTokenValue = (token: TokenOption) => {
199
+ if (!token.balance || !token.decimals) return 0;
200
+ const balance = Number(
201
+ ethers.utils.formatUnits(token.balance, token.decimals),
202
+ );
203
+ const price = tokenPrices[token.chainId]?.[token.address];
204
+ return price ? balance * Number(price) : 0;
205
+ };
206
+
194
207
  const ownedFilteredTokens = [
195
208
  ...filteredTokens.filter((token) =>
196
209
  BigNumber.from(token?.balance || "0").gt(0),
@@ -198,9 +211,16 @@ export const TokenPicker = ({
198
211
  ...otherFilteredTokens.filter((token) =>
199
212
  BigNumber.from(token?.balance || "0").gt(0),
200
213
  ),
201
- ].sort(
202
- (tokenDataA, tokenDataB) => tokenDataB.priority - tokenDataA.priority,
203
- );
214
+ ].sort((tokenA, tokenB) => {
215
+ const valueA = calculateTokenValue(tokenA);
216
+ const valueB = calculateTokenValue(tokenB);
217
+ // First sort by USD value
218
+ if (valueB !== valueA) {
219
+ return valueB - valueA;
220
+ }
221
+ // If USD values are equal (or both 0), sort by priority
222
+ return tokenB.priority - tokenA.priority;
223
+ });
204
224
 
205
225
  const currentChainTokens = filteredTokens
206
226
  .filter((token) => BigNumber.from(token?.balance || "0").eq(0))
@@ -231,7 +251,7 @@ export const TokenPicker = ({
231
251
  ];
232
252
 
233
253
  return groups.flat();
234
- }, [filteredTokens, otherFilteredTokens, chain, address]);
254
+ }, [filteredTokens, otherFilteredTokens, chain, address, tokenPrices]);
235
255
 
236
256
  const ownedTokens = useMemo(
237
257
  () => allTokens.filter((token) => token.balance?.gt(0)),
@@ -113,7 +113,11 @@ export const TokenPanel = ({ chain, token, type, className }: Props) => {
113
113
  <div className="flex flex-col font-medium items-start">
114
114
  <p className="text-xs opacity-60">Token: </p>
115
115
  <p className={`${token ? "" : "opacity-60"}`}>
116
- {token ? token.symbol : "Select"}
116
+ {token
117
+ ? token.symbol
118
+ : type === "destination" && bridgeUI
119
+ ? "-"
120
+ : "Select"}
117
121
  </p>
118
122
  </div>
119
123
  </div>
@@ -38,7 +38,7 @@ export const SwapPanel = ({ type }: Props) => {
38
38
  />
39
39
  </div>
40
40
  <div
41
- className={`flex justify-between ${
41
+ className={`flex justify-between rounded-b-[11px] ${
42
42
  type === "destination" &&
43
43
  "bg-gradient-to-r from-t_main_accent_light/20 to-t_main_accent_dark/20"
44
44
  }`}
@@ -7,8 +7,8 @@ export type SwapPanelType = "source" | "destination";
7
7
 
8
8
  export const SwapView = () => {
9
9
  return (
10
- <div className="flex flex-col gap-2 fill-t_text_primary">
11
- <div className="flex flex-col gap-[10px]">
10
+ <div className="flex flex-col gap-6 fill-t_text_primary">
11
+ <div className="flex flex-col gap-[11px]">
12
12
  <SwapPanel type="source" />
13
13
  <ReorderButton />
14
14
  <SwapPanel type="destination" />
@@ -13,7 +13,7 @@ const positionToClass: Record<Position, string> = {
13
13
 
14
14
  export const Tooltip: FC<{
15
15
  id: string;
16
- position: "TOP" | "BOTTOM" | "LEFT" | "RIGHT";
16
+ position: Position;
17
17
  text: string;
18
18
  triggerRef: RefObject<Element>;
19
19
  }> = ({ id, position, text, triggerRef }) => {
@@ -64,8 +64,7 @@ type SwapContext = {
64
64
  setSrcChain: Dispatch<SetStateAction<Chain | undefined>>;
65
65
  srcChainTokensOptions: TokenOption[];
66
66
  srcChainOtherTokensOptions: TokenOption[];
67
- srcChainTokensPrices: TokenPrices;
68
- dstChainTokensPrices: TokenPrices;
67
+ tokenPrices: TokenPrices;
69
68
  srcToken: Token | undefined;
70
69
  setSrcToken: Dispatch<SetStateAction<Token | undefined>>;
71
70
  dstChain: Chain | undefined;
@@ -128,8 +127,7 @@ const init: SwapContext = {
128
127
  srcChain: undefined,
129
128
  srcChainTokensOptions: [],
130
129
  srcChainOtherTokensOptions: [],
131
- srcChainTokensPrices: {},
132
- dstChainTokensPrices: {},
130
+ tokenPrices: {},
133
131
  dstChain: undefined,
134
132
  dstChainTokensOptions: [],
135
133
  dstChainOtherTokensOptions: [],
@@ -246,12 +244,7 @@ export const SwapProvider = ({
246
244
  const [dstChainTokensOptions, setDstChainTokensOptions] = useState(
247
245
  init.dstChainTokensOptions,
248
246
  );
249
- const [srcChainTokensPrices, setSrcChainTokensPrices] = useState(
250
- init.srcChainTokensPrices,
251
- );
252
- const [dstChainTokensPrices, setDstChainTokensPrices] = useState(
253
- init.dstChainTokensPrices,
254
- );
247
+ const [tokenPrices, setTokenPrices] = useState(init.tokenPrices);
255
248
  const [srcChainOtherTokensOptions, setSrcChainOtherTokensOptions] = useState(
256
249
  init.srcChainOtherTokensOptions,
257
250
  );
@@ -265,6 +258,15 @@ export const SwapProvider = ({
265
258
 
266
259
  const { history } = useHistory();
267
260
 
261
+ useEffect(() => {
262
+ if (supportedChains.length > 0) {
263
+ const chainIds = supportedChains.map((chain) => chain.chainId);
264
+ getPrices({ chainId: chainIds }).then((prices) => {
265
+ setTokenPrices(prices);
266
+ });
267
+ }
268
+ }, [supportedChains]);
269
+
268
270
  const supportedTokens: Record<string, Record<string, Token>> = useMemo(() => {
269
271
  const customTokens = !isServer
270
272
  ? JSON.parse(localStorage.getItem("custom-tokens") || "{}")
@@ -406,36 +408,21 @@ export const SwapProvider = ({
406
408
  [srcToken?.decimals, srcValue],
407
409
  );
408
410
 
409
- // fetch srcTokenPrices
410
- useEffect(() => {
411
- if (srcChain) {
412
- getPrices({ chainId: srcChain.chainId, currency: "USD" }).then((prices) =>
413
- setSrcChainTokensPrices(prices),
414
- );
415
- }
416
- }, [srcChain]);
417
-
418
- // fetch dstTokenPrices
419
- useEffect(() => {
420
- if (dstChain) {
421
- getPrices({ chainId: dstChain.chainId, currency: "USD" }).then((prices) =>
422
- setDstChainTokensPrices(prices),
423
- );
424
- }
425
- }, [dstChain]);
426
-
427
411
  const srcValueUsd = useMemo(() => {
428
412
  if (
429
413
  srcToken &&
430
- srcChainTokensPrices &&
431
- srcChainTokensPrices[srcToken.address]
414
+ srcChain &&
415
+ tokenPrices &&
416
+ tokenPrices[srcChain.chainId] &&
417
+ tokenPrices[srcChain.chainId]?.[srcToken.address]
432
418
  ) {
433
419
  return (
434
- Number(srcValue) * Number(srcChainTokensPrices[srcToken.address])
420
+ Number(srcValue) *
421
+ Number(tokenPrices[srcChain.chainId]?.[srcToken.address])
435
422
  ).toFixed(2);
436
423
  }
437
424
  return null;
438
- }, [srcChainTokensPrices, srcToken, srcValue]);
425
+ }, [tokenPrices, srcToken, srcValue, srcChain]);
439
426
  const dstValue = useMemo(
440
427
  () =>
441
428
  weiToHumanReadable({
@@ -457,15 +444,18 @@ export const SwapProvider = ({
457
444
  const dstValueUsd = useMemo(() => {
458
445
  if (
459
446
  dstToken &&
460
- dstChainTokensPrices &&
461
- dstChainTokensPrices[dstToken.address]
447
+ dstChain &&
448
+ tokenPrices &&
449
+ tokenPrices[dstChain?.chainId] &&
450
+ tokenPrices[dstChain?.chainId]?.[dstToken.address]
462
451
  ) {
463
452
  return (
464
- Number(dstValue) * Number(dstChainTokensPrices[dstToken.address])
453
+ Number(dstValue) *
454
+ Number(tokenPrices[dstChain?.chainId]?.[dstToken.address])
465
455
  ).toFixed(2);
466
456
  }
467
457
  return null;
468
- }, [dstChainTokensPrices, dstToken, dstValue]);
458
+ }, [tokenPrices, dstToken, dstValue, dstChain]);
469
459
  const dstValueMin = useMemo(
470
460
  () =>
471
461
  weiToHumanReadable({
@@ -950,8 +940,7 @@ export const SwapProvider = ({
950
940
  setSrcChain,
951
941
  srcChainTokensOptions,
952
942
  srcChainOtherTokensOptions,
953
- dstChainTokensPrices,
954
- srcChainTokensPrices,
943
+ tokenPrices,
955
944
  dstChain,
956
945
  setDstChain,
957
946
  dstChainTokensOptions,
@@ -40,7 +40,9 @@ export type Prices = {
40
40
  };
41
41
 
42
42
  export type TokenPrices = {
43
- [tokenAddress: string]: string;
43
+ [chainId: string]: {
44
+ [tokenAddress: string]: string;
45
+ };
44
46
  };
45
47
 
46
48
  export type TokenBalances = {
@@ -1,4 +1,4 @@
1
1
  export type GetPricesPayload = {
2
- chainId: string;
2
+ chainId: string[];
3
3
  currency?: string;
4
4
  };