@xswap-link/sdk 0.8.7 → 0.9.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.
- package/.github/workflows/main.yml +2 -1
- package/CHANGELOG.md +6 -0
- package/babel.config.cjs +8 -0
- package/dist/index.global.js +128 -131
- package/dist/index.js +9 -9
- package/dist/index.mjs +10 -10
- package/jest.config.ts +37 -0
- package/package.json +17 -5
- package/src/components/Modal/index.tsx +1 -1
- package/src/components/Swap/Header/index.tsx +1 -1
- package/src/components/Swap/ReorderButton/ReorderButton.tsx +12 -10
- package/src/components/Swap/SwapView/FeesPanel/index.tsx +1 -1
- package/src/components/Swap/SwapView/SwapButton/index.tsx +23 -21
- package/src/components/Swap/SwapView/SwapPanel/AmountPanel/index.tsx +4 -2
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/TokenItem/index.tsx +11 -2
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +116 -33
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/index.tsx +1 -1
- package/src/components/Swap/SwapView/index.tsx +1 -1
- package/src/context/HistoryProvider.tsx +2 -2
- package/src/context/SwapProvider.tsx +147 -62
- package/src/context/TransactionProvider.tsx +1 -1
- package/src/utils/validation.ts +50 -16
- package/tailwind.config.js +1 -0
- package/test/context/SwapProvider.test.tsx +851 -0
- package/test/fileMock.ts +1 -0
- package/test/fixtures/bridgeTokens.mock.ts +1318 -0
- package/test/fixtures/bridgeTokensDictionary.mock.ts +1272 -0
- package/test/fixtures/integrationConfig.mock.ts +10 -0
- package/test/fixtures/supportedChains.mock.ts +32950 -0
- package/test/{api.test.ts → services/getChains.test.ts} +6 -5
- package/test/setup.ts +13 -0
- package/test/styleMock.ts +1 -0
- package/jest.config.json +0 -8
- package/test/api.mock.ts +0 -106
- 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.
|
|
3
|
+
"version": "0.9.0",
|
|
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.
|
|
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.
|
|
47
|
-
"wagmi": "^2.
|
|
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
|
|
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
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<div className="
|
|
32
|
-
<
|
|
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
|
-
</
|
|
35
|
-
</
|
|
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 (!
|
|
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
|
|
115
|
-
: "Connect
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 && (
|
|
@@ -9,9 +9,15 @@ 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 = ({
|
|
15
|
+
export const TokenItem = ({
|
|
16
|
+
token,
|
|
17
|
+
selectedTokenAddress,
|
|
18
|
+
onClick,
|
|
19
|
+
selectedChainId,
|
|
20
|
+
}: Props) => {
|
|
15
21
|
const { supportedChains } = useSwapContext();
|
|
16
22
|
const { address } = useAccount();
|
|
17
23
|
|
|
@@ -24,7 +30,10 @@ export const TokenItem = ({ token, selectedTokenAddress, onClick }: Props) => {
|
|
|
24
30
|
<div
|
|
25
31
|
className={`token-picker-container ${
|
|
26
32
|
token.address.toLowerCase() === selectedTokenAddress?.toLowerCase() &&
|
|
27
|
-
|
|
33
|
+
selectedChainId &&
|
|
34
|
+
token.chainId === selectedChainId
|
|
35
|
+
? "bg-t_bg_tertiary bg-opacity-10"
|
|
36
|
+
: ""
|
|
28
37
|
}`}
|
|
29
38
|
onClick={onClick}
|
|
30
39
|
>
|
|
@@ -19,6 +19,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
19
19
|
import { GroupedVirtuoso } from "react-virtuoso";
|
|
20
20
|
import { QuickPickTokenItem } from "./QuickPickTokenItem";
|
|
21
21
|
import { TokenItem } from "./TokenItem";
|
|
22
|
+
import { useAccount } from "wagmi";
|
|
22
23
|
|
|
23
24
|
type Props = {
|
|
24
25
|
type: SwapPanelType;
|
|
@@ -46,11 +47,13 @@ export const TokenPicker = ({
|
|
|
46
47
|
const [loadingCustomTokenData, setLoadingCustomTokenData] = useState(false);
|
|
47
48
|
const [customTokenData, setCustomTokenData] =
|
|
48
49
|
useState<ImportedTokenData | null>(null);
|
|
50
|
+
const [showQuickPicks, setShowQuickPicks] = useState(true);
|
|
49
51
|
const currentRouteRequestNonce = useRef<number>(0);
|
|
50
52
|
const tokenSearchRef = useRef<HTMLInputElement | null>(null);
|
|
51
53
|
|
|
52
54
|
const { bridgeUI, supportedChains, findTokenDataByAddressAndChain } =
|
|
53
55
|
useSwapContext();
|
|
56
|
+
const { address } = useAccount();
|
|
54
57
|
const evmContractApi = useEvmContractApi();
|
|
55
58
|
|
|
56
59
|
useEffect(() => {
|
|
@@ -188,35 +191,63 @@ export const TokenPicker = ({
|
|
|
188
191
|
}, [isOpen]);
|
|
189
192
|
|
|
190
193
|
const allTokens = useMemo(() => {
|
|
191
|
-
const
|
|
192
|
-
...filteredTokens
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}),
|
|
202
|
-
];
|
|
203
|
-
const otherFilteredTokensSorted = [
|
|
204
|
-
...otherFilteredTokens
|
|
205
|
-
.filter((token) => BigNumber.from(token?.balance || "0").gt(0))
|
|
206
|
-
.sort((tokenDataA, tokenDataB) => {
|
|
207
|
-
return tokenDataB.priority - tokenDataA.priority;
|
|
208
|
-
}),
|
|
209
|
-
...otherFilteredTokens
|
|
210
|
-
.filter((token) => BigNumber.from(token?.balance || "0").eq(0))
|
|
211
|
-
.sort((tokenDataA, tokenDataB) => {
|
|
212
|
-
return tokenDataB.priority - tokenDataA.priority;
|
|
213
|
-
}),
|
|
214
|
-
];
|
|
194
|
+
const ownedFilteredTokens = [
|
|
195
|
+
...filteredTokens.filter((token) =>
|
|
196
|
+
BigNumber.from(token?.balance || "0").gt(0),
|
|
197
|
+
),
|
|
198
|
+
...otherFilteredTokens.filter((token) =>
|
|
199
|
+
BigNumber.from(token?.balance || "0").gt(0),
|
|
200
|
+
),
|
|
201
|
+
].sort(
|
|
202
|
+
(tokenDataA, tokenDataB) => tokenDataB.priority - tokenDataA.priority,
|
|
203
|
+
);
|
|
215
204
|
|
|
216
|
-
|
|
217
|
-
|
|
205
|
+
const currentChainTokens = filteredTokens
|
|
206
|
+
.filter((token) => BigNumber.from(token?.balance || "0").eq(0))
|
|
207
|
+
.sort(
|
|
208
|
+
(tokenDataA, tokenDataB) => tokenDataB.priority - tokenDataA.priority,
|
|
209
|
+
);
|
|
218
210
|
|
|
219
|
-
|
|
211
|
+
const otherChainTokens = otherFilteredTokens
|
|
212
|
+
.filter((token) => BigNumber.from(token?.balance || "0").eq(0))
|
|
213
|
+
.sort(
|
|
214
|
+
(tokenDataA, tokenDataB) => tokenDataB.priority - tokenDataA.priority,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// Create groups based on chain and wallet connection status
|
|
218
|
+
const groups = chain
|
|
219
|
+
? [
|
|
220
|
+
...(!!address && ownedFilteredTokens.length
|
|
221
|
+
? [ownedFilteredTokens]
|
|
222
|
+
: []),
|
|
223
|
+
currentChainTokens,
|
|
224
|
+
otherChainTokens,
|
|
225
|
+
]
|
|
226
|
+
: [
|
|
227
|
+
...(!!address && ownedFilteredTokens.length
|
|
228
|
+
? [ownedFilteredTokens]
|
|
229
|
+
: []),
|
|
230
|
+
[...currentChainTokens, ...otherChainTokens],
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
return groups.flat();
|
|
234
|
+
}, [filteredTokens, otherFilteredTokens, chain, address]);
|
|
235
|
+
|
|
236
|
+
const ownedTokens = useMemo(
|
|
237
|
+
() => allTokens.filter((token) => token.balance?.gt(0)),
|
|
238
|
+
[allTokens],
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const tokenGroups = chain
|
|
242
|
+
? [
|
|
243
|
+
...(!!address && ownedTokens.length ? [ownedTokens] : []),
|
|
244
|
+
filteredTokens,
|
|
245
|
+
otherFilteredTokens,
|
|
246
|
+
]
|
|
247
|
+
: [
|
|
248
|
+
...(!!address && ownedTokens.length ? [ownedTokens] : []),
|
|
249
|
+
otherFilteredTokens,
|
|
250
|
+
];
|
|
220
251
|
|
|
221
252
|
return showImportWarning ? (
|
|
222
253
|
<>
|
|
@@ -313,6 +344,7 @@ export const TokenPicker = ({
|
|
|
313
344
|
<CloseIcon />
|
|
314
345
|
</div>
|
|
315
346
|
</div>
|
|
347
|
+
|
|
316
348
|
<TextInput
|
|
317
349
|
ref={tokenSearchRef}
|
|
318
350
|
inputIcon={
|
|
@@ -324,7 +356,13 @@ export const TokenPicker = ({
|
|
|
324
356
|
value={searchValue}
|
|
325
357
|
handleChange={setSearchValue}
|
|
326
358
|
/>
|
|
327
|
-
<div
|
|
359
|
+
<div
|
|
360
|
+
className={`flex flex-wrap gap-1 my-4 mx-0 transition-all duration-300 ${
|
|
361
|
+
showQuickPicks
|
|
362
|
+
? "opacity-100 max-h-[200px]"
|
|
363
|
+
: "opacity-0 max-h-0 overflow-hidden"
|
|
364
|
+
}`}
|
|
365
|
+
>
|
|
328
366
|
{tokens
|
|
329
367
|
.filter((token) => token?.quickPick)
|
|
330
368
|
.map((token) => (
|
|
@@ -383,17 +421,61 @@ export const TokenPicker = ({
|
|
|
383
421
|
<GroupedVirtuoso
|
|
384
422
|
style={{ maxHeight: "400px", height: "100%" }}
|
|
385
423
|
groupCounts={tokenGroups.map((group) => group?.length)}
|
|
424
|
+
scrollerRef={(ref) => {
|
|
425
|
+
if (ref) {
|
|
426
|
+
ref.addEventListener("scroll", (e) => {
|
|
427
|
+
const target = e.target as HTMLDivElement;
|
|
428
|
+
setShowQuickPicks(target.scrollTop < 10);
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}}
|
|
386
432
|
groupContent={(index) => (
|
|
387
433
|
<div className="py-1 bg-t_bg_primary text-t_text_primary">
|
|
388
434
|
<div className="px-4 text-left">
|
|
389
435
|
{(() => {
|
|
390
436
|
if (chain) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
:
|
|
437
|
+
// chain is selected
|
|
438
|
+
switch (index) {
|
|
439
|
+
case 0: {
|
|
440
|
+
if (!address) {
|
|
441
|
+
return `${chain.displayName} network`;
|
|
442
|
+
}
|
|
443
|
+
return ownedTokens.length
|
|
444
|
+
? "My tokens"
|
|
445
|
+
: `${chain.displayName} network`;
|
|
446
|
+
}
|
|
447
|
+
case 1: {
|
|
448
|
+
if (!address) {
|
|
449
|
+
return "Other networks";
|
|
450
|
+
}
|
|
451
|
+
return `${chain.displayName} network`;
|
|
452
|
+
}
|
|
453
|
+
case 2: {
|
|
454
|
+
return "Other networks";
|
|
455
|
+
}
|
|
456
|
+
default: {
|
|
457
|
+
return "Undefined group";
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
// chain is not selected
|
|
462
|
+
switch (index) {
|
|
463
|
+
case 0: {
|
|
464
|
+
if (!address) {
|
|
465
|
+
return "All networks";
|
|
466
|
+
}
|
|
467
|
+
return ownedTokens.length
|
|
468
|
+
? "My tokens"
|
|
469
|
+
: "All networks";
|
|
470
|
+
}
|
|
471
|
+
case 1: {
|
|
472
|
+
return "All networks";
|
|
473
|
+
}
|
|
474
|
+
default: {
|
|
475
|
+
return "Undefined group";
|
|
476
|
+
}
|
|
477
|
+
}
|
|
394
478
|
}
|
|
395
|
-
|
|
396
|
-
return "All networks";
|
|
397
479
|
})()}
|
|
398
480
|
</div>
|
|
399
481
|
<div className="horizontal-separator" />
|
|
@@ -407,6 +489,7 @@ export const TokenPicker = ({
|
|
|
407
489
|
allTokens[index] && (
|
|
408
490
|
<TokenItem
|
|
409
491
|
token={allTokens[index]}
|
|
492
|
+
selectedChainId={chain?.chainId}
|
|
410
493
|
selectedTokenAddress={selectedTokenAddress}
|
|
411
494
|
onClick={() => {
|
|
412
495
|
onSelect(allTokens[index]!);
|
|
@@ -77,7 +77,7 @@ export const TokenPanel = ({ chain, token, type, className }: Props) => {
|
|
|
77
77
|
return (
|
|
78
78
|
<>
|
|
79
79
|
{tokenPickerModalOpen && (
|
|
80
|
-
<Modal onClose={() => setTokenPickerModalOpen(false)}>
|
|
80
|
+
<Modal onClose={() => setTokenPickerModalOpen(false)} className="z-50">
|
|
81
81
|
<TokenPicker
|
|
82
82
|
type={type}
|
|
83
83
|
chain={chain!}
|
|
@@ -8,7 +8,7 @@ export type SwapPanelType = "source" | "destination";
|
|
|
8
8
|
export const SwapView = () => {
|
|
9
9
|
return (
|
|
10
10
|
<div className="flex flex-col gap-2 fill-t_text_primary">
|
|
11
|
-
<div className="flex flex-col gap-
|
|
11
|
+
<div className="flex flex-col gap-[10px]">
|
|
12
12
|
<SwapPanel type="source" />
|
|
13
13
|
<ReorderButton />
|
|
14
14
|
<SwapPanel type="destination" />
|
|
@@ -10,7 +10,7 @@ import { Transaction, TransactionStatus } from "@src/models";
|
|
|
10
10
|
import { getHistory } from "@src/services";
|
|
11
11
|
import { isServer } from "@src/utils";
|
|
12
12
|
import BigNumberJS from "bignumber.js";
|
|
13
|
-
import {
|
|
13
|
+
import { useSnackbar } from "notistack";
|
|
14
14
|
import {
|
|
15
15
|
Context,
|
|
16
16
|
ReactNode,
|
|
@@ -69,7 +69,7 @@ export const HistoryProvider = ({ children }: HistoryProviderProps) => {
|
|
|
69
69
|
const [history, setHistory] = useState<Transaction[]>([]);
|
|
70
70
|
const [historyLoadedOnce, setHistoryLoadedOnce] = useState(false);
|
|
71
71
|
|
|
72
|
-
const { enqueueSnackbar } = useSnackbar();
|
|
72
|
+
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
|
|
73
73
|
const { debounce: getHistoryDebounce } = useDebounce({
|
|
74
74
|
delayMs: 600,
|
|
75
75
|
});
|