@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.
- package/.github/workflows/main.yml +2 -1
- package/CHANGELOG.md +12 -0
- package/babel.config.cjs +8 -0
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.global.js +132 -135
- 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/ChainPanel/ChainPicker/ChainItem/index.tsx +51 -59
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/TokenItem/index.tsx +47 -13
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +138 -35
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/index.tsx +6 -2
- package/src/components/Swap/SwapView/SwapPanel/index.tsx +1 -1
- package/src/components/Swap/SwapView/index.tsx +2 -2
- package/src/components/Tooltip/index.tsx +1 -1
- package/src/context/HistoryProvider.tsx +2 -2
- package/src/context/SwapProvider.tsx +174 -100
- package/src/context/TransactionProvider.tsx +1 -1
- package/src/models/TokenData.ts +3 -1
- package/src/models/payloads/GetPricesPayload.ts +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
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { renderHook } from "@testing-library/react";
|
|
3
|
+
import { act } from "react";
|
|
4
|
+
import { SwapProvider, useSwapContext } from "../../src/context/SwapProvider";
|
|
5
|
+
import { supportedChains } from "../fixtures/supportedChains.mock";
|
|
6
|
+
import { bridgeTokens } from "../fixtures/bridgeTokens.mock";
|
|
7
|
+
import { integrationConfig } from "../fixtures/integrationConfig.mock";
|
|
8
|
+
import { getWagmiConfig } from "../../src/config/wagmiConfig";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_SOURCE_CHAIN_ID,
|
|
11
|
+
DEFAULT_SOURCE_TOKEN_ADDR,
|
|
12
|
+
DEFAULT_DESTINATION_CHAIN_ID,
|
|
13
|
+
DEFAULT_DESTINATION_TOKEN_ADDR,
|
|
14
|
+
} from "../../src/constants";
|
|
15
|
+
import { WagmiProvider } from "wagmi";
|
|
16
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
17
|
+
import fetch from "jest-fetch-mock";
|
|
18
|
+
import { ethers } from "ethers";
|
|
19
|
+
// import { Chain, Token } from "@src/models";
|
|
20
|
+
|
|
21
|
+
const NATIVE_TOKEN_ADDR: string = "0x0000000000000000000000000000000000000000";
|
|
22
|
+
// swap
|
|
23
|
+
const SRC_SWAP_CHAIN_ID: string = "10"; // optimism
|
|
24
|
+
const SRC_SWAP_TOKEN_ADDR: string =
|
|
25
|
+
"0x8700daec35af8ff88c16bdf0418774cb3d7599b4"; // synthetix
|
|
26
|
+
const DST_SWAP_CHAIN_ID: string = "137"; // polygon
|
|
27
|
+
const DST_SWAP_TOKEN_ADDR: string =
|
|
28
|
+
"0x750e4c4984a9e0f12978ea6742bc1c5d248f40ed"; // axlUSDC
|
|
29
|
+
|
|
30
|
+
const INVALID_SRC_CHAIN_ID: string = "592"; // astar
|
|
31
|
+
const INVALID_SRC_TOKEN_ADDR: string =
|
|
32
|
+
"0xe785f763d30f583ee6666fa0e84f8bc32e9d57b9";
|
|
33
|
+
const INVALID_DST_CHAIN_ID: string = "1088"; // metis
|
|
34
|
+
const INVALID_DST_TOKEN_ADDR: string =
|
|
35
|
+
"0x75cb093e4d61d2a2e65d8e0bbb01de8d89b53481";
|
|
36
|
+
|
|
37
|
+
// bridge
|
|
38
|
+
const SRC_BRIDGE_CHAIN_ID: string = "10";
|
|
39
|
+
const SRC_BRIDGE_TOKEN_ADDR: string =
|
|
40
|
+
"0x0b2c639c533813f4aa9d7837caf62653d097ff85";
|
|
41
|
+
const DST_BRIDGE_CHAIN_ID: string = "137";
|
|
42
|
+
const DST_BRIDGE_TOKEN_ADDR: string =
|
|
43
|
+
"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359";
|
|
44
|
+
|
|
45
|
+
// const INVALID_SRC_BRIDGE_CHAIN_ID: string = "592";
|
|
46
|
+
const INVALID_SRC_BRIDGE_TOKEN_ADDR: string =
|
|
47
|
+
"0x0000000000000000000000000000000000000000";
|
|
48
|
+
const INVALID_DST_BRIDGE_CHAIN_ID: string = "1088";
|
|
49
|
+
const INVALID_DST_BRIDGE_TOKEN_ADDR: string =
|
|
50
|
+
"0x0000000000000000000000000000000000000000";
|
|
51
|
+
|
|
52
|
+
const queryClient = new QueryClient();
|
|
53
|
+
const wagmiConfig = getWagmiConfig(supportedChains);
|
|
54
|
+
|
|
55
|
+
describe("SwapProvider", () => {
|
|
56
|
+
// mock the API
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
fetch.resetMocks();
|
|
59
|
+
fetchMock.mockIf(/^https?:\/\/xswap.link\/api.*$/, async (req) => {
|
|
60
|
+
if (req.url.endsWith("/bridgeTokens")) {
|
|
61
|
+
return Promise.resolve(JSON.stringify(bridgeTokens));
|
|
62
|
+
} else {
|
|
63
|
+
return Promise.resolve(JSON.stringify({}));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// create a wrapper for the SwapProvider
|
|
69
|
+
const getWrapper = (integrationConfig, supportedChains, wagmiConfig) => {
|
|
70
|
+
return ({ children }: { children: React.ReactNode }) => (
|
|
71
|
+
<WagmiProvider config={wagmiConfig}>
|
|
72
|
+
<QueryClientProvider client={queryClient}>
|
|
73
|
+
<SwapProvider
|
|
74
|
+
integrationConfig={integrationConfig}
|
|
75
|
+
supportedChains={supportedChains}
|
|
76
|
+
wagmiConfig={wagmiConfig}
|
|
77
|
+
overlay={false}
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
</SwapProvider>
|
|
81
|
+
</QueryClientProvider>
|
|
82
|
+
</WagmiProvider>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
it("The default values should not be used for tests", async () => {
|
|
87
|
+
if (SRC_SWAP_CHAIN_ID === DEFAULT_SOURCE_CHAIN_ID) {
|
|
88
|
+
throw new Error("SRC_SWAP_CHAIN_ID can't be the default chain id");
|
|
89
|
+
}
|
|
90
|
+
if (SRC_SWAP_TOKEN_ADDR === DEFAULT_SOURCE_TOKEN_ADDR) {
|
|
91
|
+
throw new Error("SRC_SWAP_TOKEN_ADDR can't be the default token address");
|
|
92
|
+
}
|
|
93
|
+
if (DST_SWAP_CHAIN_ID === DEFAULT_DESTINATION_CHAIN_ID) {
|
|
94
|
+
throw new Error("DST_SWAP_CHAIN_ID can't be the default chain id");
|
|
95
|
+
}
|
|
96
|
+
if (DST_SWAP_TOKEN_ADDR === DEFAULT_DESTINATION_TOKEN_ADDR) {
|
|
97
|
+
throw new Error("DST_SWAP_TOKEN_ADDR can't be the default token address");
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("Initial state setup", () => {
|
|
102
|
+
it("should initialize for bridge with no values", async () => {
|
|
103
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
104
|
+
wrapper: getWrapper(
|
|
105
|
+
{ ...integrationConfig, bridge: true },
|
|
106
|
+
supportedChains,
|
|
107
|
+
wagmiConfig,
|
|
108
|
+
),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Wait for async state updates to complete
|
|
112
|
+
await act(async () => {
|
|
113
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(result.current.srcChain?.chainId).toBe(undefined);
|
|
117
|
+
expect(result.current.srcToken?.address.toLowerCase()).toBe(undefined);
|
|
118
|
+
expect(result.current.dstChain?.chainId).toBe(undefined);
|
|
119
|
+
expect(result.current.dstToken?.address.toLowerCase()).toBe(undefined);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should initialize for swap with default values", async () => {
|
|
123
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
124
|
+
wrapper: getWrapper(
|
|
125
|
+
{ ...integrationConfig, bridge: false },
|
|
126
|
+
supportedChains,
|
|
127
|
+
wagmiConfig,
|
|
128
|
+
),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Wait for async state updates to complete
|
|
132
|
+
await act(async () => {
|
|
133
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(result.current.srcChain?.chainId).toBe(DEFAULT_SOURCE_CHAIN_ID);
|
|
137
|
+
expect(result.current.srcToken?.address.toLowerCase()).toBe(
|
|
138
|
+
DEFAULT_SOURCE_TOKEN_ADDR.toLowerCase(),
|
|
139
|
+
);
|
|
140
|
+
expect(result.current.dstChain?.chainId).toBe(
|
|
141
|
+
DEFAULT_DESTINATION_CHAIN_ID,
|
|
142
|
+
);
|
|
143
|
+
expect(result.current.dstToken?.address.toLowerCase()).toBe(
|
|
144
|
+
DEFAULT_DESTINATION_TOKEN_ADDR.toLowerCase(),
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should initialize for swap with custom valid values single chain", async () => {
|
|
149
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
150
|
+
wrapper: getWrapper(
|
|
151
|
+
{
|
|
152
|
+
...integrationConfig,
|
|
153
|
+
bridge: false,
|
|
154
|
+
srcChainId: SRC_SWAP_CHAIN_ID,
|
|
155
|
+
srcTokenAddr: SRC_SWAP_TOKEN_ADDR,
|
|
156
|
+
dstChainId: SRC_SWAP_CHAIN_ID,
|
|
157
|
+
dstTokenAddr: NATIVE_TOKEN_ADDR,
|
|
158
|
+
},
|
|
159
|
+
supportedChains,
|
|
160
|
+
wagmiConfig,
|
|
161
|
+
),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Wait for async state updates to complete
|
|
165
|
+
await act(async () => {
|
|
166
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(result.current.srcChain?.chainId).toBe(SRC_SWAP_CHAIN_ID);
|
|
170
|
+
expect(result.current.srcToken?.address.toLowerCase()).toBe(
|
|
171
|
+
SRC_SWAP_TOKEN_ADDR?.toLowerCase(),
|
|
172
|
+
);
|
|
173
|
+
expect(result.current.dstChain?.chainId).toBe(SRC_SWAP_CHAIN_ID);
|
|
174
|
+
expect(result.current.dstToken?.address.toLowerCase()).toBe(
|
|
175
|
+
NATIVE_TOKEN_ADDR?.toLowerCase(),
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should initialize for swap with custom valid values cross chain", async () => {
|
|
180
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
181
|
+
wrapper: getWrapper(
|
|
182
|
+
{
|
|
183
|
+
...integrationConfig,
|
|
184
|
+
bridge: false,
|
|
185
|
+
srcChainId: SRC_SWAP_CHAIN_ID,
|
|
186
|
+
srcTokenAddr: SRC_SWAP_TOKEN_ADDR,
|
|
187
|
+
dstChainId: DST_SWAP_CHAIN_ID,
|
|
188
|
+
dstTokenAddr: DST_SWAP_TOKEN_ADDR,
|
|
189
|
+
},
|
|
190
|
+
supportedChains,
|
|
191
|
+
wagmiConfig,
|
|
192
|
+
),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await act(async () => {
|
|
196
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(result.current.srcChain?.chainId).toBe(SRC_SWAP_CHAIN_ID);
|
|
200
|
+
expect(result.current.srcToken?.address.toLowerCase()).toBe(
|
|
201
|
+
SRC_SWAP_TOKEN_ADDR?.toLowerCase(),
|
|
202
|
+
);
|
|
203
|
+
expect(result.current.dstChain?.chainId).toBe(DST_SWAP_CHAIN_ID);
|
|
204
|
+
expect(result.current.dstToken?.address.toLowerCase()).toBe(
|
|
205
|
+
DST_SWAP_TOKEN_ADDR?.toLowerCase(),
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should handle invalid chain IDs gracefully", async () => {
|
|
210
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
211
|
+
wrapper: getWrapper(
|
|
212
|
+
{
|
|
213
|
+
...integrationConfig,
|
|
214
|
+
bridge: false,
|
|
215
|
+
srcChainId: "99999999999",
|
|
216
|
+
dstChainId: "99999999999",
|
|
217
|
+
},
|
|
218
|
+
supportedChains,
|
|
219
|
+
wagmiConfig,
|
|
220
|
+
),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
await act(async () => {
|
|
224
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Should fall back to defaults
|
|
228
|
+
expect(result.current.srcChain?.chainId).toBe(DEFAULT_SOURCE_CHAIN_ID);
|
|
229
|
+
expect(result.current.dstChain?.chainId).toBe(
|
|
230
|
+
DEFAULT_DESTINATION_CHAIN_ID,
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe("State changes", () => {
|
|
236
|
+
it("should handle source chain change", async () => {
|
|
237
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
238
|
+
wrapper: getWrapper(
|
|
239
|
+
{ ...integrationConfig, bridge: false },
|
|
240
|
+
supportedChains,
|
|
241
|
+
wagmiConfig,
|
|
242
|
+
),
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
await act(async () => {
|
|
246
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const newChain = supportedChains.find(
|
|
250
|
+
(chain) => chain.chainId === SRC_SWAP_CHAIN_ID,
|
|
251
|
+
);
|
|
252
|
+
if (!newChain) throw new Error("Chain not found");
|
|
253
|
+
|
|
254
|
+
await act(async () => {
|
|
255
|
+
result.current.setSrcChain(newChain);
|
|
256
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(result.current.srcChain?.chainId).toBe(SRC_SWAP_CHAIN_ID);
|
|
260
|
+
// Should set a valid token for the new chain
|
|
261
|
+
expect(result.current.srcToken?.address).toBeDefined();
|
|
262
|
+
});
|
|
263
|
+
it("should handle destination chain change", async () => {
|
|
264
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
265
|
+
wrapper: getWrapper(
|
|
266
|
+
{ ...integrationConfig, bridge: false },
|
|
267
|
+
supportedChains,
|
|
268
|
+
wagmiConfig,
|
|
269
|
+
),
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
await act(async () => {
|
|
273
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const newChain = supportedChains.find(
|
|
277
|
+
(chain) => chain.chainId === DST_SWAP_CHAIN_ID,
|
|
278
|
+
);
|
|
279
|
+
if (!newChain) throw new Error("Chain not found");
|
|
280
|
+
|
|
281
|
+
await act(async () => {
|
|
282
|
+
result.current.setDstChain(newChain);
|
|
283
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
expect(result.current.dstChain?.chainId).toBe(DST_SWAP_CHAIN_ID);
|
|
287
|
+
// Should set a valid token for the new chain
|
|
288
|
+
expect(result.current.dstToken?.address).toBeDefined();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("should handle source token change", async () => {
|
|
292
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
293
|
+
wrapper: getWrapper(
|
|
294
|
+
{ ...integrationConfig, bridge: false },
|
|
295
|
+
supportedChains,
|
|
296
|
+
wagmiConfig,
|
|
297
|
+
),
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
await act(async () => {
|
|
301
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const newChain = supportedChains.find(
|
|
305
|
+
(chain) => chain.chainId === SRC_SWAP_CHAIN_ID,
|
|
306
|
+
);
|
|
307
|
+
if (!newChain) throw new Error("Source chain not set");
|
|
308
|
+
|
|
309
|
+
const newToken = newChain.tokens.find(
|
|
310
|
+
(token) =>
|
|
311
|
+
token.address.toLowerCase() === SRC_SWAP_TOKEN_ADDR.toLowerCase(),
|
|
312
|
+
);
|
|
313
|
+
if (!newToken) throw new Error("Token not found");
|
|
314
|
+
|
|
315
|
+
await act(async () => {
|
|
316
|
+
result.current.setSrcToken(newToken);
|
|
317
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(result.current.srcToken?.address).toBe(SRC_SWAP_TOKEN_ADDR);
|
|
321
|
+
});
|
|
322
|
+
it("should handle destination token change", async () => {
|
|
323
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
324
|
+
wrapper: getWrapper(
|
|
325
|
+
{ ...integrationConfig, bridge: false },
|
|
326
|
+
supportedChains,
|
|
327
|
+
wagmiConfig,
|
|
328
|
+
),
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
await act(async () => {
|
|
332
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const newChain = supportedChains.find(
|
|
336
|
+
(chain) => chain.chainId === DST_SWAP_CHAIN_ID,
|
|
337
|
+
);
|
|
338
|
+
if (!newChain) throw new Error("Source chain not set");
|
|
339
|
+
|
|
340
|
+
const newToken = newChain.tokens.find(
|
|
341
|
+
(token) =>
|
|
342
|
+
token.address.toLowerCase() === DST_SWAP_TOKEN_ADDR.toLowerCase(),
|
|
343
|
+
);
|
|
344
|
+
if (!newToken) throw new Error("Token not found");
|
|
345
|
+
|
|
346
|
+
await act(async () => {
|
|
347
|
+
result.current.setDstToken(newToken);
|
|
348
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
expect(result.current.dstToken?.address).toBe(DST_SWAP_TOKEN_ADDR);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("should prevent same token selection on same chain", async () => {
|
|
355
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
356
|
+
wrapper: getWrapper(
|
|
357
|
+
{
|
|
358
|
+
...integrationConfig,
|
|
359
|
+
bridge: false,
|
|
360
|
+
srcChainId: SRC_SWAP_CHAIN_ID,
|
|
361
|
+
dstChainId: SRC_SWAP_CHAIN_ID,
|
|
362
|
+
srcTokenAddr: SRC_SWAP_TOKEN_ADDR,
|
|
363
|
+
},
|
|
364
|
+
supportedChains,
|
|
365
|
+
wagmiConfig,
|
|
366
|
+
),
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
await act(async () => {
|
|
370
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const chain = supportedChains.find(
|
|
374
|
+
(chain) => chain.chainId === SRC_SWAP_CHAIN_ID,
|
|
375
|
+
);
|
|
376
|
+
if (!chain) throw new Error("Chain not found");
|
|
377
|
+
|
|
378
|
+
const token = chain.tokens.find(
|
|
379
|
+
(token) =>
|
|
380
|
+
token.address.toLowerCase() === SRC_SWAP_TOKEN_ADDR.toLowerCase(),
|
|
381
|
+
);
|
|
382
|
+
if (!token) throw new Error("Token not found");
|
|
383
|
+
|
|
384
|
+
await act(async () => {
|
|
385
|
+
result.current.setDstToken(token);
|
|
386
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
expect(result.current.srcToken?.address.toLowerCase()).not.toBe(
|
|
390
|
+
result.current.dstToken?.address.toLowerCase(),
|
|
391
|
+
);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it("should handle token support status", async () => {
|
|
395
|
+
// Create a modified chain with an unsupported token
|
|
396
|
+
const modifiedChain = {
|
|
397
|
+
...supportedChains[0],
|
|
398
|
+
tokens: [
|
|
399
|
+
...(supportedChains[0]?.tokens || []),
|
|
400
|
+
{
|
|
401
|
+
address: INVALID_SRC_TOKEN_ADDR,
|
|
402
|
+
supported: false,
|
|
403
|
+
decimals: 18,
|
|
404
|
+
symbol: "INVALID",
|
|
405
|
+
name: "Invalid Token",
|
|
406
|
+
tokenId: "INVALID",
|
|
407
|
+
priority: 0,
|
|
408
|
+
quickPick: false,
|
|
409
|
+
},
|
|
410
|
+
],
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
414
|
+
wrapper: getWrapper(
|
|
415
|
+
{ ...integrationConfig, bridge: false },
|
|
416
|
+
[modifiedChain],
|
|
417
|
+
wagmiConfig,
|
|
418
|
+
),
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
await act(async () => {
|
|
422
|
+
const unsupportedToken = modifiedChain.tokens.find((t) => !t.supported);
|
|
423
|
+
if (!unsupportedToken) throw new Error("Test setup failed");
|
|
424
|
+
result.current.setSrcToken(unsupportedToken);
|
|
425
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
expect(result.current.srcToken?.address.toLowerCase()).not.toBe(
|
|
429
|
+
INVALID_SRC_TOKEN_ADDR.toLowerCase(),
|
|
430
|
+
);
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
describe("Bridge mode validation", () => {
|
|
435
|
+
it("should enforce bridgeable tokens between chains", async () => {
|
|
436
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
437
|
+
wrapper: getWrapper(
|
|
438
|
+
{ ...integrationConfig, bridge: true },
|
|
439
|
+
supportedChains,
|
|
440
|
+
wagmiConfig,
|
|
441
|
+
),
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
await act(async () => {
|
|
445
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const srcChain = supportedChains.find(
|
|
449
|
+
(chain) => chain.chainId === SRC_BRIDGE_CHAIN_ID,
|
|
450
|
+
);
|
|
451
|
+
const dstChain = supportedChains.find(
|
|
452
|
+
(chain) => chain.chainId === DST_BRIDGE_CHAIN_ID,
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
if (!srcChain || !dstChain) throw new Error("Bridge chains not found");
|
|
456
|
+
|
|
457
|
+
const srcToken = srcChain.tokens.find(
|
|
458
|
+
(token) =>
|
|
459
|
+
token.address.toLowerCase() === SRC_BRIDGE_TOKEN_ADDR.toLowerCase(),
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
if (!srcToken) throw new Error("Bridge token not found");
|
|
463
|
+
|
|
464
|
+
await act(async () => {
|
|
465
|
+
result.current.setSrcChain(srcChain);
|
|
466
|
+
result.current.setDstChain(dstChain);
|
|
467
|
+
result.current.setSrcToken(srcToken);
|
|
468
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// Verify destination token is the corresponding bridged token
|
|
472
|
+
expect(result.current.dstToken?.address.toLowerCase()).toBe(
|
|
473
|
+
DST_BRIDGE_TOKEN_ADDR.toLowerCase(),
|
|
474
|
+
);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it("should prevent selecting non-bridgeable tokens", async () => {
|
|
478
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
479
|
+
wrapper: getWrapper(
|
|
480
|
+
{ ...integrationConfig, bridge: true },
|
|
481
|
+
supportedChains,
|
|
482
|
+
wagmiConfig,
|
|
483
|
+
),
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
await act(async () => {
|
|
487
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const srcChain = supportedChains.find(
|
|
491
|
+
(chain) => chain.chainId === SRC_BRIDGE_CHAIN_ID,
|
|
492
|
+
);
|
|
493
|
+
const dstChain = supportedChains.find(
|
|
494
|
+
(chain) => chain.chainId === DST_BRIDGE_CHAIN_ID,
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
if (!srcChain || !dstChain) throw new Error("Bridge chains not found");
|
|
498
|
+
|
|
499
|
+
const invalidToken = srcChain.tokens.find(
|
|
500
|
+
(token) =>
|
|
501
|
+
token.address.toLowerCase() ===
|
|
502
|
+
INVALID_SRC_BRIDGE_TOKEN_ADDR.toLowerCase(),
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
if (!invalidToken) throw new Error("Invalid token not found");
|
|
506
|
+
|
|
507
|
+
await act(async () => {
|
|
508
|
+
result.current.setSrcChain(srcChain);
|
|
509
|
+
result.current.setDstChain(dstChain);
|
|
510
|
+
result.current.setSrcToken(invalidToken);
|
|
511
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Should not allow setting a non-bridgeable token
|
|
515
|
+
expect(result.current.srcToken?.address.toLowerCase()).not.toBe(
|
|
516
|
+
invalidToken.address.toLowerCase(),
|
|
517
|
+
);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it("should prevent same chain selection in bridge mode", async () => {
|
|
521
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
522
|
+
wrapper: getWrapper(
|
|
523
|
+
{ ...integrationConfig, bridge: true },
|
|
524
|
+
supportedChains,
|
|
525
|
+
wagmiConfig,
|
|
526
|
+
),
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
await act(async () => {
|
|
530
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const chain = supportedChains.find((chain) => chain.bridgeSupported);
|
|
534
|
+
if (!chain) throw new Error("Bridge chain not found");
|
|
535
|
+
|
|
536
|
+
await act(async () => {
|
|
537
|
+
result.current.setSrcChain(chain);
|
|
538
|
+
result.current.setDstChain(chain);
|
|
539
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// Should not allow same chain selection in bridge mode
|
|
543
|
+
expect(result.current.srcChain?.chainId).not.toBe(
|
|
544
|
+
result.current.dstChain?.chainId,
|
|
545
|
+
);
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
describe("Swap mode validation", () => {
|
|
550
|
+
it("should enforce swap-supported chains", async () => {
|
|
551
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
552
|
+
wrapper: getWrapper(
|
|
553
|
+
{ ...integrationConfig, bridge: false },
|
|
554
|
+
supportedChains,
|
|
555
|
+
wagmiConfig,
|
|
556
|
+
),
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
await act(async () => {
|
|
560
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// Find a chain without swap support
|
|
564
|
+
const nonSwapChain = supportedChains.find(
|
|
565
|
+
(chain) => !chain.swapSupported,
|
|
566
|
+
);
|
|
567
|
+
if (!nonSwapChain) throw new Error("Non-swap chain not found");
|
|
568
|
+
|
|
569
|
+
await act(async () => {
|
|
570
|
+
result.current.setSrcChain(nonSwapChain);
|
|
571
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// Should not allow setting a non-swap chain
|
|
575
|
+
expect(result.current.srcChain?.chainId).not.toBe(nonSwapChain.chainId);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it("should handle disabled destination chains", async () => {
|
|
579
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
580
|
+
wrapper: getWrapper(
|
|
581
|
+
{ ...integrationConfig, bridge: false },
|
|
582
|
+
supportedChains,
|
|
583
|
+
wagmiConfig,
|
|
584
|
+
),
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
await act(async () => {
|
|
588
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// Find a chain with disabled destinations
|
|
592
|
+
const srcChain = supportedChains.find(
|
|
593
|
+
(chain) => chain.disabledForDestination?.length || 0 > 0,
|
|
594
|
+
);
|
|
595
|
+
if (!srcChain)
|
|
596
|
+
throw new Error("Chain with disabled destinations not found");
|
|
597
|
+
|
|
598
|
+
const disabledChain = supportedChains.find((chain) =>
|
|
599
|
+
srcChain.disabledForDestination?.includes(chain.chainId),
|
|
600
|
+
);
|
|
601
|
+
if (!disabledChain) throw new Error("Disabled chain not found");
|
|
602
|
+
|
|
603
|
+
await act(async () => {
|
|
604
|
+
result.current.setSrcChain(srcChain);
|
|
605
|
+
result.current.setDstChain(disabledChain);
|
|
606
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
// Should not allow setting a disabled destination chain
|
|
610
|
+
expect(result.current.dstChain?.chainId).not.toBe(disabledChain.chainId);
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
describe("Edge cases", () => {
|
|
615
|
+
it("should handle chain with no valid tokens", async () => {
|
|
616
|
+
const modifiedChain = {
|
|
617
|
+
...supportedChains[0],
|
|
618
|
+
tokens:
|
|
619
|
+
supportedChains[0]?.tokens?.map((token) => ({
|
|
620
|
+
...token,
|
|
621
|
+
supported: false,
|
|
622
|
+
})) || [],
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
626
|
+
wrapper: getWrapper(
|
|
627
|
+
{
|
|
628
|
+
...integrationConfig,
|
|
629
|
+
bridge: false,
|
|
630
|
+
srcChainId: modifiedChain.chainId,
|
|
631
|
+
},
|
|
632
|
+
[modifiedChain],
|
|
633
|
+
wagmiConfig,
|
|
634
|
+
),
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
await act(async () => {
|
|
638
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
expect(result.current.srcToken).toBeUndefined();
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it("should handle empty supported chains list", async () => {
|
|
645
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
646
|
+
wrapper: getWrapper(
|
|
647
|
+
{ ...integrationConfig, bridge: false },
|
|
648
|
+
[],
|
|
649
|
+
wagmiConfig,
|
|
650
|
+
),
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
await act(async () => {
|
|
654
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// Should handle the case gracefully
|
|
658
|
+
expect(result.current.srcChain).toBe(undefined);
|
|
659
|
+
expect(result.current.dstChain).toBe(undefined);
|
|
660
|
+
expect(result.current.srcToken).toBe(undefined);
|
|
661
|
+
expect(result.current.dstToken).toBe(undefined);
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
describe("Token validation", () => {
|
|
666
|
+
it("should handle undefined token addresses", async () => {
|
|
667
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
668
|
+
wrapper: getWrapper(
|
|
669
|
+
{ ...integrationConfig, bridge: false },
|
|
670
|
+
supportedChains,
|
|
671
|
+
wagmiConfig,
|
|
672
|
+
),
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
await act(async () => {
|
|
676
|
+
result.current.setSrcToken(undefined);
|
|
677
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
expect(result.current.srcToken).toBeDefined();
|
|
681
|
+
expect(result.current.srcToken?.address).toBe(DEFAULT_SOURCE_TOKEN_ADDR);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
it("should handle token decimals correctly", async () => {
|
|
685
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
686
|
+
wrapper: getWrapper(
|
|
687
|
+
{ ...integrationConfig, bridge: false },
|
|
688
|
+
supportedChains,
|
|
689
|
+
wagmiConfig,
|
|
690
|
+
),
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
const chain = supportedChains.find(
|
|
694
|
+
(chain) => chain.chainId === SRC_SWAP_CHAIN_ID,
|
|
695
|
+
);
|
|
696
|
+
if (!chain) throw new Error("Chain not found");
|
|
697
|
+
|
|
698
|
+
const token = chain.tokens.find(
|
|
699
|
+
(token) =>
|
|
700
|
+
token.address.toLowerCase() === SRC_SWAP_TOKEN_ADDR.toLowerCase(),
|
|
701
|
+
);
|
|
702
|
+
if (!token) throw new Error("Token not found");
|
|
703
|
+
|
|
704
|
+
await act(async () => {
|
|
705
|
+
result.current.setSrcChain(chain);
|
|
706
|
+
result.current.setSrcToken(token);
|
|
707
|
+
result.current.setSrcValue("1.0");
|
|
708
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
expect(result.current.srcValueWei).toBe(
|
|
712
|
+
ethers.utils.parseUnits("1.0", token.decimals).toString(),
|
|
713
|
+
);
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
describe("Invalid chain and token handling", () => {
|
|
718
|
+
it("should handle invalid source chain selection", async () => {
|
|
719
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
720
|
+
wrapper: getWrapper(
|
|
721
|
+
{
|
|
722
|
+
...integrationConfig,
|
|
723
|
+
bridge: false,
|
|
724
|
+
srcChainId: INVALID_SRC_CHAIN_ID,
|
|
725
|
+
},
|
|
726
|
+
supportedChains,
|
|
727
|
+
wagmiConfig,
|
|
728
|
+
),
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
await act(async () => {
|
|
732
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
expect(result.current.srcChain?.chainId).not.toBe(INVALID_SRC_CHAIN_ID);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
it("should handle invalid destination chain selection", async () => {
|
|
739
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
740
|
+
wrapper: getWrapper(
|
|
741
|
+
{
|
|
742
|
+
...integrationConfig,
|
|
743
|
+
bridge: false,
|
|
744
|
+
dstChainId: INVALID_DST_CHAIN_ID,
|
|
745
|
+
},
|
|
746
|
+
supportedChains,
|
|
747
|
+
wagmiConfig,
|
|
748
|
+
),
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
await act(async () => {
|
|
752
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
expect(result.current.dstChain?.chainId).not.toBe(INVALID_DST_CHAIN_ID);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
it("should handle invalid source token selection", async () => {
|
|
759
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
760
|
+
wrapper: getWrapper(
|
|
761
|
+
{
|
|
762
|
+
...integrationConfig,
|
|
763
|
+
bridge: false,
|
|
764
|
+
srcChainId: SRC_SWAP_CHAIN_ID,
|
|
765
|
+
srcTokenAddr: INVALID_SRC_TOKEN_ADDR,
|
|
766
|
+
},
|
|
767
|
+
supportedChains,
|
|
768
|
+
wagmiConfig,
|
|
769
|
+
),
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
await act(async () => {
|
|
773
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
expect(result.current.srcToken?.address.toLowerCase()).not.toBe(
|
|
777
|
+
INVALID_SRC_TOKEN_ADDR.toLowerCase(),
|
|
778
|
+
);
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
it("should handle invalid destination token selection", async () => {
|
|
782
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
783
|
+
wrapper: getWrapper(
|
|
784
|
+
{
|
|
785
|
+
...integrationConfig,
|
|
786
|
+
bridge: false,
|
|
787
|
+
dstChainId: DST_SWAP_CHAIN_ID,
|
|
788
|
+
dstTokenAddr: INVALID_DST_TOKEN_ADDR,
|
|
789
|
+
},
|
|
790
|
+
supportedChains,
|
|
791
|
+
wagmiConfig,
|
|
792
|
+
),
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
await act(async () => {
|
|
796
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
expect(result.current.dstToken?.address.toLowerCase()).not.toBe(
|
|
800
|
+
INVALID_DST_TOKEN_ADDR.toLowerCase(),
|
|
801
|
+
);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
describe("Bridge mode invalid cases", () => {
|
|
805
|
+
it("should handle invalid bridge destination chain", async () => {
|
|
806
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
807
|
+
wrapper: getWrapper(
|
|
808
|
+
{
|
|
809
|
+
...integrationConfig,
|
|
810
|
+
bridge: true,
|
|
811
|
+
dstChainId: INVALID_DST_BRIDGE_CHAIN_ID,
|
|
812
|
+
},
|
|
813
|
+
supportedChains,
|
|
814
|
+
wagmiConfig,
|
|
815
|
+
),
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
await act(async () => {
|
|
819
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
expect(result.current.dstChain?.chainId).not.toBe(
|
|
823
|
+
INVALID_DST_BRIDGE_CHAIN_ID,
|
|
824
|
+
);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it("should handle invalid bridge destination token", async () => {
|
|
828
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
829
|
+
wrapper: getWrapper(
|
|
830
|
+
{
|
|
831
|
+
...integrationConfig,
|
|
832
|
+
bridge: true,
|
|
833
|
+
dstChainId: DST_BRIDGE_CHAIN_ID,
|
|
834
|
+
dstTokenAddr: INVALID_DST_BRIDGE_TOKEN_ADDR,
|
|
835
|
+
},
|
|
836
|
+
supportedChains,
|
|
837
|
+
wagmiConfig,
|
|
838
|
+
),
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
await act(async () => {
|
|
842
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
expect(result.current.dstToken?.address.toLowerCase()).not.toBe(
|
|
846
|
+
INVALID_DST_BRIDGE_TOKEN_ADDR.toLowerCase(),
|
|
847
|
+
);
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
});
|
|
851
|
+
});
|