@xswap-link/sdk 0.8.7 → 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.
Files changed (42) hide show
  1. package/.github/workflows/main.yml +2 -1
  2. package/CHANGELOG.md +12 -0
  3. package/babel.config.cjs +8 -0
  4. package/dist/index.d.mts +4 -2
  5. package/dist/index.d.ts +4 -2
  6. package/dist/index.global.js +132 -135
  7. package/dist/index.js +9 -9
  8. package/dist/index.mjs +10 -10
  9. package/jest.config.ts +37 -0
  10. package/package.json +17 -5
  11. package/src/components/Modal/index.tsx +1 -1
  12. package/src/components/Swap/Header/index.tsx +1 -1
  13. package/src/components/Swap/ReorderButton/ReorderButton.tsx +12 -10
  14. package/src/components/Swap/SwapView/FeesPanel/index.tsx +1 -1
  15. package/src/components/Swap/SwapView/SwapButton/index.tsx +23 -21
  16. package/src/components/Swap/SwapView/SwapPanel/AmountPanel/index.tsx +4 -2
  17. package/src/components/Swap/SwapView/SwapPanel/ChainPanel/ChainPicker/ChainItem/index.tsx +51 -59
  18. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/TokenItem/index.tsx +47 -13
  19. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +138 -35
  20. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/index.tsx +6 -2
  21. package/src/components/Swap/SwapView/SwapPanel/index.tsx +1 -1
  22. package/src/components/Swap/SwapView/index.tsx +2 -2
  23. package/src/components/Tooltip/index.tsx +1 -1
  24. package/src/context/HistoryProvider.tsx +2 -2
  25. package/src/context/SwapProvider.tsx +174 -100
  26. package/src/context/TransactionProvider.tsx +1 -1
  27. package/src/models/TokenData.ts +3 -1
  28. package/src/models/payloads/GetPricesPayload.ts +1 -1
  29. package/src/utils/validation.ts +50 -16
  30. package/tailwind.config.js +1 -0
  31. package/test/context/SwapProvider.test.tsx +851 -0
  32. package/test/fileMock.ts +1 -0
  33. package/test/fixtures/bridgeTokens.mock.ts +1318 -0
  34. package/test/fixtures/bridgeTokensDictionary.mock.ts +1272 -0
  35. package/test/fixtures/integrationConfig.mock.ts +10 -0
  36. package/test/fixtures/supportedChains.mock.ts +32950 -0
  37. package/test/{api.test.ts → services/getChains.test.ts} +6 -5
  38. package/test/setup.ts +13 -0
  39. package/test/styleMock.ts +1 -0
  40. package/jest.config.json +0 -8
  41. package/test/api.mock.ts +0 -106
  42. package/test/setupTests.ts +0 -3
package/jest.config.ts ADDED
@@ -0,0 +1,37 @@
1
+ import type { JestConfigWithTsJest } from "ts-jest";
2
+
3
+ const esmModules = [
4
+ "uuid",
5
+ "wagmi",
6
+ "@wagmi",
7
+ "viem",
8
+ "@viem",
9
+ "@tanstack/react-query",
10
+ ];
11
+
12
+ const config: JestConfigWithTsJest = {
13
+ extensionsToTreatAsEsm: [".ts", ".tsx"],
14
+ verbose: true,
15
+ preset: "ts-jest",
16
+ testEnvironment: "jsdom",
17
+ setupFilesAfterEnv: ["<rootDir>/test/setup.ts"],
18
+ transform: {
19
+ "^.+\\.tsx?$": ["ts-jest", { useESM: true }],
20
+ "\\.jsx?$": "babel-jest",
21
+ },
22
+ transformIgnorePatterns: [
23
+ `node_modules/(?!(?:.pnpm/)?(${esmModules.join("|")}))`,
24
+ ],
25
+ testPathIgnorePatterns: ["./dist"],
26
+ moduleNameMapper: {
27
+ "^@src/(.*)$": "<rootDir>/src/$1",
28
+ "^@/(.*)$": "<rootDir>/src/$1",
29
+ "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
30
+ "<rootDir>/test/fileMock.ts",
31
+ "\\.(css)$": "<rootDir>/test/styleMock.ts",
32
+ },
33
+ automock: false,
34
+ resetMocks: false,
35
+ };
36
+
37
+ export default config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xswap-link/sdk",
3
- "version": "0.8.7",
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": {
@@ -35,7 +35,7 @@
35
35
  "@fortawesome/free-solid-svg-icons": "^6.4.2",
36
36
  "@fortawesome/react-fontawesome": "^0.2.0",
37
37
  "@r2wc/core": "^1.1.0",
38
- "@tanstack/react-query": "^5.35.5",
38
+ "@tanstack/react-query": "^5.64.2",
39
39
  "async-retry": "^1.3.3",
40
40
  "bignumber.js": "4.0.4",
41
41
  "date-fns": "^3.6.0",
@@ -43,11 +43,18 @@
43
43
  "notistack": "^3.0.1",
44
44
  "react-error-boundary": "^4.1.0",
45
45
  "react-virtuoso": "^4.7.12",
46
- "viem": "2.x",
47
- "wagmi": "^2.8.5"
46
+ "viem": "~2.22.12",
47
+ "wagmi": "^2.14.9"
48
48
  },
49
49
  "devDependencies": {
50
+ "@babel/core": "^7.26.0",
51
+ "@babel/preset-env": "^7.26.0",
52
+ "@babel/preset-react": "^7.26.3",
53
+ "@babel/preset-typescript": "^7.26.0",
50
54
  "@changesets/cli": "^2.27.1",
55
+ "@testing-library/jest-dom": "^6.4.2",
56
+ "@testing-library/react": "^14.2.1",
57
+ "@testing-library/user-event": "^14.5.2",
51
58
  "@types/async-retry": "^1.4.8",
52
59
  "@types/jest": "^29.5.12",
53
60
  "@types/node": "^20.11.17",
@@ -55,9 +62,13 @@
55
62
  "@types/react-dom": "^18.2.0",
56
63
  "@typescript-eslint/eslint-plugin": "^7.1.0",
57
64
  "@typescript-eslint/parser": "^7.1.0",
65
+ "babel-jest": "^29.7.0",
66
+ "cross-fetch": "^4.1.0",
58
67
  "eslint": "^8.57.0",
59
68
  "eslint-plugin-react-hooks": "^4.6.2",
69
+ "identity-obj-proxy": "^3.0.0",
60
70
  "jest": "^29.7.0",
71
+ "jest-environment-jsdom": "^29.7.0",
61
72
  "jest-fetch": "^1.1.1",
62
73
  "jest-fetch-mock": "^3.0.3",
63
74
  "nodemon": "^3.1.0",
@@ -67,6 +78,7 @@
67
78
  "react-dom": "^18.2.0",
68
79
  "tailwindcss": "^3.4.3",
69
80
  "ts-jest": "^29.1.2",
81
+ "ts-node": "^10.9.2",
70
82
  "tsup": "^8.1.0",
71
83
  "typescript": "^5.3.3"
72
84
  },
@@ -84,6 +96,6 @@
84
96
  "watch": "tsup && nodemon",
85
97
  "release": "tsup && changeset publish",
86
98
  "lint": "tsc && eslint . --ext .ts",
87
- "test": "jest"
99
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
88
100
  }
89
101
  }
@@ -45,7 +45,7 @@ export const Modal: FC<{
45
45
  }}
46
46
  >
47
47
  <div
48
- className="flex flex-col bg-t_bg_primary x-border rounded-3xl p-4 w-full min-w-[340px]"
48
+ className="flex flex-col bg-t_bg_primary rounded-3xl p-4 w-full min-w-[340px]"
49
49
  style={{
50
50
  height: overlay ? "600px" : "100%",
51
51
  maxWidth: "480px",
@@ -9,7 +9,7 @@ export const Header = ({ onClose }: Props) => {
9
9
 
10
10
  return (
11
11
  <div className="flex justify-between">
12
- <div className="text-xl">
12
+ <div className="text-xl font-sans-bold font-bold">
13
13
  {tab === "Swap" && bridgeUI ? "Bridge" : tab}
14
14
  </div>
15
15
  <Controls onClose={onClose} />
@@ -22,16 +22,18 @@ export const ReorderButton: FC = () => {
22
22
  };
23
23
 
24
24
  return (
25
- <button
26
- onClick={onReorderClick}
27
- aria-label="Switch target token with the source token"
28
- className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-14 p-1 border border-solid border-t_text_primary border-opacity-10 rounded-xl bg-t_bg_secondary"
29
- >
30
- <div className="relative bg-gradient-to-r from-t_main_accent_light to-t_main_accent_dark w-8 h-8 rounded-lg">
31
- <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 fill-white">
32
- <ArrowsExchange />
25
+ <div className="h-[1px] w-full">
26
+ <button
27
+ onClick={onReorderClick}
28
+ aria-label="Switch target token with the source token"
29
+ className="absolute -translate-y-5 left-1/2 -translate-x-5 p-1 border border-solid border-t_text_primary border-opacity-10 rounded-xl bg-t_bg_secondary"
30
+ >
31
+ <div className="relative bg-gradient-to-r from-t_main_accent_light to-t_main_accent_dark w-8 h-8 rounded-lg">
32
+ <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 fill-white">
33
+ <ArrowsExchange />
34
+ </div>
33
35
  </div>
34
- </div>
35
- </button>
36
+ </button>
37
+ </div>
36
38
  );
37
39
  };
@@ -42,7 +42,7 @@ export const FeesPanel = () => {
42
42
  {isFetchingRoute ? (
43
43
  <Skeleton className="w-[100px] h-full" />
44
44
  ) : (
45
- <p>{`${totalFee} ${feeToken?.symbol}`}</p>
45
+ <p>{`${totalFee} ${feeToken?.symbol || ""}`}</p>
46
46
  )}
47
47
  </div>
48
48
  <div className="flex gap-2 items-center fill-t_text_primary">
@@ -33,6 +33,7 @@ export const SwapButton = () => {
33
33
  srcValueWei,
34
34
  srcTokenBalanceWei,
35
35
  isFetchingRoute,
36
+ isAsyncDataLoaded,
36
37
  } = useSwapContext();
37
38
 
38
39
  const { setTxStatus, setTxMsg, setTxModalOpen, enqueueTransaction } =
@@ -92,7 +93,7 @@ export const SwapButton = () => {
92
93
  }, [setTxModalOpen, setTxMsg, setTxStatus]);
93
94
 
94
95
  const button: Button = useMemo(() => {
95
- if (!srcChainId || !dstChainId || !srcTokenAddress || !dstTokenAddress) {
96
+ if (!isAsyncDataLoaded) {
96
97
  return {
97
98
  text: "Loading XPay...",
98
99
  fn: () => {},
@@ -111,8 +112,8 @@ export const SwapButton = () => {
111
112
  if (!address) {
112
113
  return {
113
114
  text: integrationConfig.defaultWalletPicker
114
- ? "Connect wallet"
115
- : "Connect wallet first",
115
+ ? "Connect Wallet"
116
+ : "Connect Wallet First",
116
117
  fn: () => {
117
118
  setConnectWalletModalOpen(true);
118
119
  },
@@ -120,12 +121,20 @@ export const SwapButton = () => {
120
121
  };
121
122
  }
122
123
 
124
+ if (!srcChainId || !dstChainId || !srcTokenAddress || !dstTokenAddress) {
125
+ return {
126
+ text: "Select Tokens",
127
+ fn: () => {},
128
+ disabled: true,
129
+ };
130
+ }
131
+
123
132
  if (
124
133
  srcChainId === dstChainId &&
125
134
  srcTokenAddress.toLowerCase() === dstTokenAddress.toLowerCase()
126
135
  ) {
127
136
  return {
128
- text: "Change token",
137
+ text: "Change Token",
129
138
  fn: () => {},
130
139
  disabled: true,
131
140
  };
@@ -133,7 +142,7 @@ export const SwapButton = () => {
133
142
 
134
143
  if (chainId?.toString() !== srcChainId) {
135
144
  return {
136
- text: "Change chain",
145
+ text: "Change Chain",
137
146
  fn: async () => await switchChainAsync({ chainId: Number(srcChainId) }),
138
147
  disabled: false,
139
148
  };
@@ -145,15 +154,7 @@ export const SwapButton = () => {
145
154
  safeBigNumberFrom(srcValueWei).gt(safeBigNumberFrom(srcTokenBalanceWei))
146
155
  ) {
147
156
  return {
148
- text: "Insufficient balance",
149
- fn: () => {},
150
- disabled: true,
151
- };
152
- }
153
-
154
- if (!srcTokenAddress || !dstTokenAddress) {
155
- return {
156
- text: `Select token`,
157
+ text: "Insufficient Balance",
157
158
  fn: () => {},
158
159
  disabled: true,
159
160
  };
@@ -161,7 +162,7 @@ export const SwapButton = () => {
161
162
 
162
163
  if (safeBigNumberFrom(srcValueWei || "0").lte(safeBigNumberFrom("0"))) {
163
164
  return {
164
- text: `Enter token amount`,
165
+ text: `Enter Token Amount`,
165
166
  fn: () => {},
166
167
  disabled: true,
167
168
  };
@@ -169,7 +170,7 @@ export const SwapButton = () => {
169
170
 
170
171
  if (isFetchingRoute) {
171
172
  return {
172
- text: `Fetching route`,
173
+ text: `Fetching Route`,
173
174
  fn: () => {},
174
175
  disabled: true,
175
176
  };
@@ -181,7 +182,7 @@ export const SwapButton = () => {
181
182
  BigNumber.from(currentTokenAllowance).lt(BigNumber.from(srcValueWei))
182
183
  ) {
183
184
  return {
184
- text: "Reset approval",
185
+ text: "Reset Approval",
185
186
  fn: async () => {
186
187
  enqueueTransaction({
187
188
  executeTransaction: () => approve("0", routerAddress),
@@ -207,18 +208,19 @@ export const SwapButton = () => {
207
208
  disabled: false,
208
209
  };
209
210
  }, [
210
- bridgeUI,
211
+ isAsyncDataLoaded,
212
+ error,
213
+ address,
211
214
  srcChainId,
212
215
  dstChainId,
213
216
  srcTokenAddress,
214
217
  dstTokenAddress,
215
- error,
216
- address,
217
218
  chainId,
218
219
  srcValueWei,
219
220
  srcTokenBalanceWei,
220
221
  isFetchingRoute,
221
222
  currentTokenAllowance,
223
+ bridgeUI,
222
224
  integrationConfig.defaultWalletPicker,
223
225
  switchChainAsync,
224
226
  enqueueTransaction,
@@ -241,7 +243,7 @@ export const SwapButton = () => {
241
243
  type="button"
242
244
  disabled={button.disabled}
243
245
  onClick={button.fn}
244
- className={`text-xl w-full py-5 cursor-pointer disabled:cursor-not-allowed rounded-2xl border-none ${
246
+ className={`text-xl font-sans-bold font-bold w-full py-5 cursor-pointer disabled:cursor-not-allowed rounded-2xl border-none ${
245
247
  button.disabled
246
248
  ? "bg-t_button_pr_off_bg text-t_button_pr_off_text"
247
249
  : "bg-gradient-to-r from-t_main_accent_light to-t_main_accent_dark text-t_button_pr_text"
@@ -31,7 +31,7 @@ export const AmountPanel = ({ type }: Props) => {
31
31
  <>
32
32
  <SafeInput
33
33
  id="swap-amount-input"
34
- className={`bg-transparent border-none p-0 text-xl outline-0`}
34
+ className={`bg-transparent border-none p-0 text-xl outline-0 font-sans-bold font-bold`}
35
35
  regex={NUMBER_INPUT_REGEX}
36
36
  value={srcValue}
37
37
  onChange={(value) => setSrcValue(value)}
@@ -47,7 +47,9 @@ export const AmountPanel = ({ type }: Props) => {
47
47
  </div>
48
48
  ) : (
49
49
  <>
50
- <div className={`text-xl text-t_text_secondary`}>
50
+ <div
51
+ className={`text-xl text-t_text_secondary font-sans-bold font-bold`}
52
+ >
51
53
  {dstValue || 0}
52
54
  </div>
53
55
  {valueUsd && (
@@ -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
  };
@@ -9,10 +9,16 @@ import { useAccount } from "wagmi";
9
9
  type Props = {
10
10
  token: TokenOption;
11
11
  selectedTokenAddress: string | undefined;
12
+ selectedChainId: string | undefined;
12
13
  onClick: () => void;
13
14
  };
14
- export const TokenItem = ({ token, selectedTokenAddress, onClick }: Props) => {
15
- const { supportedChains } = useSwapContext();
15
+ export const TokenItem = ({
16
+ token,
17
+ selectedTokenAddress,
18
+ onClick,
19
+ selectedChainId,
20
+ }: Props) => {
21
+ const { supportedChains, tokenPrices } = useSwapContext();
16
22
  const { address } = useAccount();
17
23
 
18
24
  const chain = useMemo(
@@ -20,11 +26,34 @@ export const TokenItem = ({ token, selectedTokenAddress, onClick }: Props) => {
20
26
  [supportedChains, token],
21
27
  );
22
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
+
23
49
  return (
24
50
  <div
25
51
  className={`token-picker-container ${
26
52
  token.address.toLowerCase() === selectedTokenAddress?.toLowerCase() &&
27
- "bg-t_bg_tertiary bg-opacity-10"
53
+ selectedChainId &&
54
+ token.chainId === selectedChainId
55
+ ? "bg-t_bg_tertiary bg-opacity-10"
56
+ : ""
28
57
  }`}
29
58
  onClick={onClick}
30
59
  >
@@ -46,16 +75,21 @@ export const TokenItem = ({ token, selectedTokenAddress, onClick }: Props) => {
46
75
  </div>
47
76
 
48
77
  {token.balance && token.decimals ? (
49
- <div className="text-xs text-t_text_primary text-opacity-50">
50
- {token.balance.eq(0) ||
51
- Number(ethers.utils.formatUnits(token.balance, token.decimals)) >
52
- 0.0001
53
- ? weiToHumanReadable({
54
- amount: token.balance.toString(),
55
- decimals: token.decimals,
56
- precisionFractionalPlaces: 4,
57
- })
58
- : "<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
+ )}
59
93
  </div>
60
94
  ) : (
61
95
  <>