@xswap-link/sdk 0.9.7 → 0.9.9
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/CHANGELOG.md +12 -0
- package/README.md +0 -18
- package/dist/index.d.mts +10 -8
- package/dist/index.d.ts +10 -8
- package/dist/index.global.js +52 -53
- package/dist/index.js +9 -10
- package/dist/index.mjs +9 -10
- package/package.json +1 -1
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +21 -15
- package/src/components/TxConfigForm/index.tsx +10 -1
- package/src/components/TxWidgetWC/index.tsx +10 -1
- package/src/components/TxWidgetWCWrapped/index.tsx +2 -0
- package/src/context/SwapProvider.tsx +23 -1
- package/src/models/index.ts +0 -1
- package/src/models/payloads/ModalIntegrationPayload.ts +3 -0
- package/src/models/payloads/WidgetIntegrationPayload.ts +3 -0
- package/src/services/api.ts +8 -4
- package/src/utils/validation.ts +30 -9
- package/test/context/SwapProvider.test.tsx +274 -4
- package/src/models/XSwapConfig.ts +0 -4
- package/xswap.config.ts +0 -20
package/package.json
CHANGED
|
@@ -236,37 +236,43 @@ export const TokenPicker = ({
|
|
|
236
236
|
// Create groups based on chain and wallet connection status
|
|
237
237
|
const groups = chain
|
|
238
238
|
? [
|
|
239
|
-
...(!!address && ownedFilteredTokens.length
|
|
239
|
+
...(!!address && ownedFilteredTokens.length && type === "source"
|
|
240
240
|
? [ownedFilteredTokens]
|
|
241
241
|
: []),
|
|
242
242
|
currentChainTokens,
|
|
243
243
|
otherChainTokens,
|
|
244
244
|
]
|
|
245
245
|
: [
|
|
246
|
-
...(!!address && ownedFilteredTokens.length
|
|
246
|
+
...(!!address && ownedFilteredTokens.length && type === "source"
|
|
247
247
|
? [ownedFilteredTokens]
|
|
248
248
|
: []),
|
|
249
249
|
[...currentChainTokens, ...otherChainTokens],
|
|
250
250
|
];
|
|
251
251
|
|
|
252
252
|
return groups.flat();
|
|
253
|
-
}, [filteredTokens, otherFilteredTokens, chain, address, tokenPrices]);
|
|
253
|
+
}, [filteredTokens, otherFilteredTokens, chain, address, type, tokenPrices]);
|
|
254
254
|
|
|
255
255
|
const ownedTokens = useMemo(
|
|
256
256
|
() => allTokens.filter((token) => token.balance?.gt(0)),
|
|
257
257
|
[allTokens],
|
|
258
258
|
);
|
|
259
259
|
|
|
260
|
-
const tokenGroups =
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
260
|
+
const tokenGroups = useMemo(() => {
|
|
261
|
+
return chain
|
|
262
|
+
? [
|
|
263
|
+
...(type === "source" && !!address && ownedTokens.length
|
|
264
|
+
? [ownedTokens]
|
|
265
|
+
: []),
|
|
266
|
+
filteredTokens,
|
|
267
|
+
otherFilteredTokens,
|
|
268
|
+
]
|
|
269
|
+
: [
|
|
270
|
+
...(type === "source" && !!address && ownedTokens.length
|
|
271
|
+
? [ownedTokens]
|
|
272
|
+
: []),
|
|
273
|
+
otherFilteredTokens,
|
|
274
|
+
];
|
|
275
|
+
}, [chain, type, address, ownedTokens, filteredTokens, otherFilteredTokens]);
|
|
270
276
|
|
|
271
277
|
return showImportWarning ? (
|
|
272
278
|
<>
|
|
@@ -459,7 +465,7 @@ export const TokenPicker = ({
|
|
|
459
465
|
if (!address) {
|
|
460
466
|
return `${chain.displayName} network`;
|
|
461
467
|
}
|
|
462
|
-
return ownedTokens.length
|
|
468
|
+
return ownedTokens.length && type === "source"
|
|
463
469
|
? "My tokens"
|
|
464
470
|
: `${chain.displayName} network`;
|
|
465
471
|
}
|
|
@@ -483,7 +489,7 @@ export const TokenPicker = ({
|
|
|
483
489
|
if (!address) {
|
|
484
490
|
return "All networks";
|
|
485
491
|
}
|
|
486
|
-
return ownedTokens.length
|
|
492
|
+
return ownedTokens.length && type === "source"
|
|
487
493
|
? "My tokens"
|
|
488
494
|
: "All networks";
|
|
489
495
|
}
|
|
@@ -13,6 +13,7 @@ import { FC, MouseEvent, useEffect, useMemo } from "react";
|
|
|
13
13
|
import { ErrorBoundary } from "react-error-boundary";
|
|
14
14
|
import { useReconnect, WagmiProvider } from "wagmi";
|
|
15
15
|
import { changeHostVariableColor } from "../../../localTheme";
|
|
16
|
+
import { setApiUrl } from "@src/services/api";
|
|
16
17
|
|
|
17
18
|
export type TxConfigFormPayload = Omit<
|
|
18
19
|
ModalIntegrationPayload,
|
|
@@ -97,6 +98,7 @@ export const TxConfigForm = ({
|
|
|
97
98
|
onSrcChainChange,
|
|
98
99
|
bridge,
|
|
99
100
|
onConnectWallet,
|
|
101
|
+
override,
|
|
100
102
|
}: TxConfigFormPayload &
|
|
101
103
|
(
|
|
102
104
|
| {
|
|
@@ -105,7 +107,6 @@ export const TxConfigForm = ({
|
|
|
105
107
|
overlay: true;
|
|
106
108
|
}
|
|
107
109
|
| {
|
|
108
|
-
// Satisfy TypeScript, so it understands that this field is not needed when overlay=false
|
|
109
110
|
onSubmit?: undefined;
|
|
110
111
|
onClose?: undefined;
|
|
111
112
|
overlay: false;
|
|
@@ -129,6 +130,12 @@ export const TxConfigForm = ({
|
|
|
129
130
|
changeHostVariableColor(lightTheme, styles);
|
|
130
131
|
}, [lightTheme, styles]);
|
|
131
132
|
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
if (override?.apiUrl) {
|
|
135
|
+
setApiUrl(override?.apiUrl);
|
|
136
|
+
}
|
|
137
|
+
}, [override?.apiUrl]);
|
|
138
|
+
|
|
132
139
|
return (
|
|
133
140
|
<ErrorBoundary
|
|
134
141
|
FallbackComponent={() => (
|
|
@@ -214,6 +221,7 @@ export const openTxConfigForm = async ({
|
|
|
214
221
|
dstChainLocked,
|
|
215
222
|
srcTokenLocked,
|
|
216
223
|
srcChainLocked,
|
|
224
|
+
override,
|
|
217
225
|
}: TxConfigFormPayload): Promise<Route> => {
|
|
218
226
|
try {
|
|
219
227
|
if (xpayRoot === null) {
|
|
@@ -257,6 +265,7 @@ export const openTxConfigForm = async ({
|
|
|
257
265
|
dstChainLocked={dstChainLocked}
|
|
258
266
|
srcTokenLocked={srcTokenLocked}
|
|
259
267
|
srcChainLocked={srcChainLocked}
|
|
268
|
+
override={override}
|
|
260
269
|
/>,
|
|
261
270
|
);
|
|
262
271
|
});
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
Web3Environment,
|
|
5
5
|
WidgetIntegrationPayload,
|
|
6
6
|
} from "@src/models";
|
|
7
|
-
import { getChains } from "@src/services";
|
|
7
|
+
import { getChains, setApiUrl } from "@src/services";
|
|
8
8
|
import { isServer } from "@src/utils";
|
|
9
9
|
import { createWebComponent } from "@src/utils/webComponents";
|
|
10
10
|
import { useEffect, useState } from "react";
|
|
@@ -35,6 +35,7 @@ const TxWidget = ({
|
|
|
35
35
|
onSrcChainChange,
|
|
36
36
|
onConnectWallet,
|
|
37
37
|
bridge,
|
|
38
|
+
override,
|
|
38
39
|
}: WidgetIntegrationPayload) => {
|
|
39
40
|
const [loading, setLoading] = useState(true);
|
|
40
41
|
const [supportedChains, setSupportedChains] = useState<Chain[]>([]);
|
|
@@ -43,6 +44,10 @@ const TxWidget = ({
|
|
|
43
44
|
(async () => {
|
|
44
45
|
setLoading(true);
|
|
45
46
|
|
|
47
|
+
if (override?.apiUrl) {
|
|
48
|
+
setApiUrl(override.apiUrl);
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
const chains = (await getChains()).filter(
|
|
47
52
|
({ web3Environment, bridgeSupported, swapSupported }) =>
|
|
48
53
|
web3Environment === Web3Environment.MAINNET &&
|
|
@@ -52,6 +57,7 @@ const TxWidget = ({
|
|
|
52
57
|
setSupportedChains(chains);
|
|
53
58
|
setLoading(false);
|
|
54
59
|
})();
|
|
60
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
55
61
|
}, []);
|
|
56
62
|
|
|
57
63
|
const parsedStyles: ModalIntegrationStyles | undefined = (() => {
|
|
@@ -98,6 +104,7 @@ const TxWidget = ({
|
|
|
98
104
|
onSrcChainChange={onSrcChainChange}
|
|
99
105
|
onConnectWallet={onConnectWallet}
|
|
100
106
|
bridge={bridge}
|
|
107
|
+
override={override}
|
|
101
108
|
/>
|
|
102
109
|
)}
|
|
103
110
|
</>
|
|
@@ -141,6 +148,7 @@ export const createTxWidgetWC = () => {
|
|
|
141
148
|
onDstChainChange: "function",
|
|
142
149
|
onSrcTokenChange: "function",
|
|
143
150
|
onSrcChainChange: "function",
|
|
151
|
+
override: "json",
|
|
144
152
|
},
|
|
145
153
|
});
|
|
146
154
|
|
|
@@ -176,4 +184,5 @@ export type TxWidgetWCAttributes = {
|
|
|
176
184
|
"on-src-token-change"?: WidgetIntegrationPayload["onSrcTokenChange"];
|
|
177
185
|
"on-src-chain-change"?: WidgetIntegrationPayload["onSrcChainChange"];
|
|
178
186
|
bridge: WidgetIntegrationPayload["bridge"] | undefined;
|
|
187
|
+
override: string | undefined;
|
|
179
188
|
};
|
|
@@ -47,6 +47,7 @@ export const TxWidgetWCWrapped = ({
|
|
|
47
47
|
onSrcChainChange,
|
|
48
48
|
onConnectWallet,
|
|
49
49
|
bridge,
|
|
50
|
+
override,
|
|
50
51
|
}: WidgetIntegrationPayload) => {
|
|
51
52
|
useEffect(() => {
|
|
52
53
|
// We couldn't find a way to pass a function to the web component created by @r2wc/core.
|
|
@@ -84,6 +85,7 @@ export const TxWidgetWCWrapped = ({
|
|
|
84
85
|
src-token-locked={srcTokenLocked}
|
|
85
86
|
src-chain-locked={srcChainLocked}
|
|
86
87
|
bridge={bridge}
|
|
88
|
+
override={JSON.stringify(override)}
|
|
87
89
|
></xpay-widget>
|
|
88
90
|
);
|
|
89
91
|
};
|
|
@@ -533,11 +533,32 @@ export const SwapProvider = ({
|
|
|
533
533
|
!srcToken ||
|
|
534
534
|
!dstToken ||
|
|
535
535
|
!srcValueWei ||
|
|
536
|
-
safeBigNumberFrom(srcValueWei).eq(0)
|
|
536
|
+
safeBigNumberFrom(srcValueWei).eq(0) ||
|
|
537
|
+
(bridgeUI && !bridgeTokensDictionary)
|
|
537
538
|
) {
|
|
538
539
|
setRoute(undefined);
|
|
539
540
|
return;
|
|
540
541
|
}
|
|
542
|
+
|
|
543
|
+
// Validate the current path before requesting a route
|
|
544
|
+
const { source, target } = mapToValidPath({
|
|
545
|
+
source: { chain: srcChain, token: srcToken },
|
|
546
|
+
target: { chain: dstChain, token: dstToken },
|
|
547
|
+
type: bridgeUI ? "BRIDGE" : "SWAP",
|
|
548
|
+
supportedChains,
|
|
549
|
+
bridgeTokensDictionary: bridgeTokensDictionary || {},
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// If any of the chains or tokens would be changed by validation, don't request a route
|
|
553
|
+
if (
|
|
554
|
+
source.chain?.chainId !== srcChain?.chainId ||
|
|
555
|
+
target.chain?.chainId !== dstChain?.chainId ||
|
|
556
|
+
source.token?.address !== srcToken?.address ||
|
|
557
|
+
target.token?.address !== dstToken?.address
|
|
558
|
+
) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
541
562
|
try {
|
|
542
563
|
setError("");
|
|
543
564
|
setFetchingRoute(true);
|
|
@@ -580,6 +601,7 @@ export const SwapProvider = ({
|
|
|
580
601
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
581
602
|
}, [
|
|
582
603
|
address,
|
|
604
|
+
bridgeUI,
|
|
583
605
|
dstChain,
|
|
584
606
|
dstToken,
|
|
585
607
|
getRoute,
|
package/src/models/index.ts
CHANGED
package/src/services/api.ts
CHANGED
|
@@ -13,13 +13,18 @@ import {
|
|
|
13
13
|
TransactionHistory,
|
|
14
14
|
TransactionStatus,
|
|
15
15
|
} from "@src/models";
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
let XSWAP_API_URL = "https://xswap.link/api";
|
|
18
|
+
|
|
19
|
+
export const setApiUrl = (url: string) => {
|
|
20
|
+
XSWAP_API_URL = url;
|
|
21
|
+
};
|
|
17
22
|
|
|
18
23
|
async function _sendRequest<T>(
|
|
19
24
|
urlPath: string,
|
|
20
25
|
options?: RequestInit,
|
|
21
26
|
): Promise<T> {
|
|
22
|
-
const response = await fetch(`${
|
|
27
|
+
const response = await fetch(`${XSWAP_API_URL}${urlPath}`, {
|
|
23
28
|
method: "GET",
|
|
24
29
|
headers: {
|
|
25
30
|
"Content-Type": "application/json",
|
|
@@ -59,8 +64,7 @@ export async function getRoute(
|
|
|
59
64
|
* @param ecosystem optional
|
|
60
65
|
*/
|
|
61
66
|
export async function getChains(ecosystem?: Ecosystem): Promise<Chain[]> {
|
|
62
|
-
return _sendRequest<Chain[]>(`/chains?data=${JSON.stringify({ ecosystem })}
|
|
63
|
-
`);
|
|
67
|
+
return _sendRequest<Chain[]>(`/chains?data=${JSON.stringify({ ecosystem })}`);
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
/**
|
package/src/utils/validation.ts
CHANGED
|
@@ -262,7 +262,14 @@ const mapToValidPathSwap = ({
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
// Setting tokens
|
|
265
|
-
//
|
|
265
|
+
// validate if the token is supported in the new source chain
|
|
266
|
+
newSourceToken = newSourceChain.tokens.find(
|
|
267
|
+
(token) => token.tokenId === newSourceToken?.tokenId && token.supported,
|
|
268
|
+
);
|
|
269
|
+
newTargetToken = newTargetChain.tokens.find(
|
|
270
|
+
(token) => token.tokenId === newTargetToken?.tokenId && token.supported,
|
|
271
|
+
);
|
|
272
|
+
|
|
266
273
|
if (!newSourceToken) {
|
|
267
274
|
newSourceToken = getDefaultSwapTokenForChain(
|
|
268
275
|
newSourceChain,
|
|
@@ -339,18 +346,32 @@ const mapToValidPathBridge = ({
|
|
|
339
346
|
// Setting tokens
|
|
340
347
|
// check if the source token is bridgeable anywhere
|
|
341
348
|
if (newSourceToken && newSourceChain) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
] || {},
|
|
347
|
-
).length > 0;
|
|
349
|
+
// First validate if the token is supported in the source chain
|
|
350
|
+
newSourceToken = newSourceChain.tokens.find(
|
|
351
|
+
(token) => token.tokenId === newSourceToken?.tokenId && token.supported,
|
|
352
|
+
);
|
|
348
353
|
|
|
349
|
-
if (
|
|
350
|
-
|
|
354
|
+
if (newSourceToken) {
|
|
355
|
+
const isBridgeable =
|
|
356
|
+
Object.keys(
|
|
357
|
+
bridgeTokensDictionary[newSourceChain.chainId]?.[
|
|
358
|
+
newSourceToken.address.toLowerCase()
|
|
359
|
+
] || {},
|
|
360
|
+
).length > 0;
|
|
361
|
+
|
|
362
|
+
if (!isBridgeable) {
|
|
363
|
+
newSourceToken = undefined;
|
|
364
|
+
}
|
|
351
365
|
}
|
|
352
366
|
}
|
|
353
367
|
|
|
368
|
+
// Validate if target token is supported in the target chain
|
|
369
|
+
if (newTargetToken && newTargetChain) {
|
|
370
|
+
newTargetToken = newTargetChain.tokens.find(
|
|
371
|
+
(token) => token.tokenId === newTargetToken?.tokenId && token.supported,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
354
375
|
if (!newSourceToken) {
|
|
355
376
|
const defaultToken = getDefaultBridgeTokenForChain({
|
|
356
377
|
chain: newSourceChain,
|
|
@@ -260,6 +260,110 @@ describe("SwapProvider", () => {
|
|
|
260
260
|
// Should set a valid token for the new chain
|
|
261
261
|
expect(result.current.srcToken?.address).toBeDefined();
|
|
262
262
|
});
|
|
263
|
+
|
|
264
|
+
it("should preserve token when changing to chain with same token", async () => {
|
|
265
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
266
|
+
wrapper: getWrapper(
|
|
267
|
+
{ ...integrationConfig, bridge: false },
|
|
268
|
+
supportedChains,
|
|
269
|
+
wagmiConfig,
|
|
270
|
+
),
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
await act(async () => {
|
|
274
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// First set initial chain and token
|
|
278
|
+
const initialChain = supportedChains.find(
|
|
279
|
+
(chain) => chain.chainId === SRC_SWAP_CHAIN_ID,
|
|
280
|
+
);
|
|
281
|
+
if (!initialChain) throw new Error("Initial chain not found");
|
|
282
|
+
|
|
283
|
+
const initialToken = initialChain.tokens.find(
|
|
284
|
+
(token) =>
|
|
285
|
+
token.address.toLowerCase() === SRC_SWAP_TOKEN_ADDR.toLowerCase(),
|
|
286
|
+
);
|
|
287
|
+
if (!initialToken) throw new Error("Initial token not found");
|
|
288
|
+
|
|
289
|
+
await act(async () => {
|
|
290
|
+
result.current.setSrcChain(initialChain);
|
|
291
|
+
result.current.setSrcToken(initialToken);
|
|
292
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Now change to a different chain that has the same token (by tokenId)
|
|
296
|
+
const newChain = supportedChains.find(
|
|
297
|
+
(chain) =>
|
|
298
|
+
chain.chainId !== initialChain.chainId &&
|
|
299
|
+
chain.tokens.some(
|
|
300
|
+
(token) =>
|
|
301
|
+
token.tokenId === initialToken.tokenId && token.supported,
|
|
302
|
+
),
|
|
303
|
+
);
|
|
304
|
+
if (!newChain) throw new Error("New chain with same token not found");
|
|
305
|
+
|
|
306
|
+
await act(async () => {
|
|
307
|
+
result.current.setSrcChain(newChain);
|
|
308
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Should preserve the same tokenId but with new chain's token address
|
|
312
|
+
expect(result.current.srcToken?.tokenId).toBe(initialToken.tokenId);
|
|
313
|
+
expect(result.current.srcToken?.address).not.toBe(initialToken.address);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("should reset token when changing to chain without same token", async () => {
|
|
317
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
318
|
+
wrapper: getWrapper(
|
|
319
|
+
{ ...integrationConfig, bridge: false },
|
|
320
|
+
supportedChains,
|
|
321
|
+
wagmiConfig,
|
|
322
|
+
),
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
await act(async () => {
|
|
326
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// First set initial chain and token
|
|
330
|
+
const initialChain = supportedChains.find(
|
|
331
|
+
(chain) => chain.chainId === SRC_SWAP_CHAIN_ID,
|
|
332
|
+
);
|
|
333
|
+
if (!initialChain) throw new Error("Initial chain not found");
|
|
334
|
+
|
|
335
|
+
const initialToken = initialChain.tokens.find(
|
|
336
|
+
(token) =>
|
|
337
|
+
token.address.toLowerCase() === SRC_SWAP_TOKEN_ADDR.toLowerCase(),
|
|
338
|
+
);
|
|
339
|
+
if (!initialToken) throw new Error("Initial token not found");
|
|
340
|
+
|
|
341
|
+
await act(async () => {
|
|
342
|
+
result.current.setSrcChain(initialChain);
|
|
343
|
+
result.current.setSrcToken(initialToken);
|
|
344
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Now change to a chain that doesn't have the same token
|
|
348
|
+
const newChain = supportedChains.find(
|
|
349
|
+
(chain) =>
|
|
350
|
+
chain.chainId !== initialChain.chainId &&
|
|
351
|
+
!chain.tokens.some((token) => token.tokenId === initialToken.tokenId),
|
|
352
|
+
);
|
|
353
|
+
if (!newChain) throw new Error("New chain without same token not found");
|
|
354
|
+
|
|
355
|
+
await act(async () => {
|
|
356
|
+
result.current.setSrcChain(newChain);
|
|
357
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Should have a different token now
|
|
361
|
+
expect(result.current.srcToken?.tokenId).not.toBe(initialToken.tokenId);
|
|
362
|
+
expect(result.current.srcToken?.address).not.toBe(initialToken.address);
|
|
363
|
+
// But should still have a valid token
|
|
364
|
+
expect(result.current.srcToken).toBeDefined();
|
|
365
|
+
});
|
|
366
|
+
|
|
263
367
|
it("should handle destination chain change", async () => {
|
|
264
368
|
const { result } = renderHook(() => useSwapContext(), {
|
|
265
369
|
wrapper: getWrapper(
|
|
@@ -288,7 +392,7 @@ describe("SwapProvider", () => {
|
|
|
288
392
|
expect(result.current.dstToken?.address).toBeDefined();
|
|
289
393
|
});
|
|
290
394
|
|
|
291
|
-
it("should
|
|
395
|
+
it("should preserve token when changing destination chain with same token", async () => {
|
|
292
396
|
const { result } = renderHook(() => useSwapContext(), {
|
|
293
397
|
wrapper: getWrapper(
|
|
294
398
|
{ ...integrationConfig, bridge: false },
|
|
@@ -301,12 +405,119 @@ describe("SwapProvider", () => {
|
|
|
301
405
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
302
406
|
});
|
|
303
407
|
|
|
408
|
+
// First set initial chain and token
|
|
409
|
+
const initialChain = supportedChains.find(
|
|
410
|
+
(chain) => chain.chainId === DST_SWAP_CHAIN_ID,
|
|
411
|
+
);
|
|
412
|
+
if (!initialChain) throw new Error("Initial chain not found");
|
|
413
|
+
|
|
414
|
+
const initialToken = initialChain.tokens.find(
|
|
415
|
+
(token) =>
|
|
416
|
+
token.address.toLowerCase() === DST_SWAP_TOKEN_ADDR.toLowerCase(),
|
|
417
|
+
);
|
|
418
|
+
if (!initialToken) throw new Error("Initial token not found");
|
|
419
|
+
|
|
420
|
+
await act(async () => {
|
|
421
|
+
result.current.setDstChain(initialChain);
|
|
422
|
+
result.current.setDstToken(initialToken);
|
|
423
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Now change to a different chain that has the same token (by tokenId)
|
|
304
427
|
const newChain = supportedChains.find(
|
|
428
|
+
(chain) =>
|
|
429
|
+
chain.chainId !== initialChain.chainId &&
|
|
430
|
+
chain.tokens.some(
|
|
431
|
+
(token) =>
|
|
432
|
+
token.tokenId === initialToken.tokenId && token.supported,
|
|
433
|
+
),
|
|
434
|
+
);
|
|
435
|
+
if (!newChain) throw new Error("New chain with same token not found");
|
|
436
|
+
|
|
437
|
+
await act(async () => {
|
|
438
|
+
result.current.setDstChain(newChain);
|
|
439
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Should preserve the same tokenId but with new chain's token address
|
|
443
|
+
expect(result.current.dstToken?.tokenId).toBe(initialToken.tokenId);
|
|
444
|
+
expect(result.current.dstToken?.address).not.toBe(initialToken.address);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("should reset token when changing destination chain without same token", async () => {
|
|
448
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
449
|
+
wrapper: getWrapper(
|
|
450
|
+
{ ...integrationConfig, bridge: false },
|
|
451
|
+
supportedChains,
|
|
452
|
+
wagmiConfig,
|
|
453
|
+
),
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
await act(async () => {
|
|
457
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// First set initial chain and token
|
|
461
|
+
const initialChain = supportedChains.find(
|
|
462
|
+
(chain) => chain.chainId === DST_SWAP_CHAIN_ID,
|
|
463
|
+
);
|
|
464
|
+
if (!initialChain) throw new Error("Initial chain not found");
|
|
465
|
+
|
|
466
|
+
const initialToken = initialChain.tokens.find(
|
|
467
|
+
(token) =>
|
|
468
|
+
token.address.toLowerCase() === DST_SWAP_TOKEN_ADDR.toLowerCase(),
|
|
469
|
+
);
|
|
470
|
+
if (!initialToken) throw new Error("Initial token not found");
|
|
471
|
+
|
|
472
|
+
await act(async () => {
|
|
473
|
+
result.current.setDstChain(initialChain);
|
|
474
|
+
result.current.setDstToken(initialToken);
|
|
475
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// Now change to a chain that doesn't have the same token
|
|
479
|
+
const newChain = supportedChains.find(
|
|
480
|
+
(chain) =>
|
|
481
|
+
chain.chainId !== initialChain.chainId &&
|
|
482
|
+
!chain.tokens.some((token) => token.tokenId === initialToken.tokenId),
|
|
483
|
+
);
|
|
484
|
+
if (!newChain) throw new Error("New chain without same token not found");
|
|
485
|
+
|
|
486
|
+
await act(async () => {
|
|
487
|
+
result.current.setDstChain(newChain);
|
|
488
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// Should have a different token now
|
|
492
|
+
expect(result.current.dstToken?.tokenId).not.toBe(initialToken.tokenId);
|
|
493
|
+
expect(result.current.dstToken?.address).not.toBe(initialToken.address);
|
|
494
|
+
// But should still have a valid token
|
|
495
|
+
expect(result.current.dstToken).toBeDefined();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it("should handle source token change", async () => {
|
|
499
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
500
|
+
wrapper: getWrapper(
|
|
501
|
+
{
|
|
502
|
+
...integrationConfig,
|
|
503
|
+
bridge: false,
|
|
504
|
+
srcChainId: SRC_SWAP_CHAIN_ID,
|
|
505
|
+
},
|
|
506
|
+
supportedChains,
|
|
507
|
+
wagmiConfig,
|
|
508
|
+
),
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
await act(async () => {
|
|
512
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
const activeSrcChain = supportedChains.find(
|
|
305
516
|
(chain) => chain.chainId === SRC_SWAP_CHAIN_ID,
|
|
306
517
|
);
|
|
307
|
-
if (!
|
|
518
|
+
if (!activeSrcChain) throw new Error("Source chain not set");
|
|
308
519
|
|
|
309
|
-
const newToken =
|
|
520
|
+
const newToken = activeSrcChain.tokens.find(
|
|
310
521
|
(token) =>
|
|
311
522
|
token.address.toLowerCase() === SRC_SWAP_TOKEN_ADDR.toLowerCase(),
|
|
312
523
|
);
|
|
@@ -322,7 +533,11 @@ describe("SwapProvider", () => {
|
|
|
322
533
|
it("should handle destination token change", async () => {
|
|
323
534
|
const { result } = renderHook(() => useSwapContext(), {
|
|
324
535
|
wrapper: getWrapper(
|
|
325
|
-
{
|
|
536
|
+
{
|
|
537
|
+
...integrationConfig,
|
|
538
|
+
bridge: false,
|
|
539
|
+
dstChainId: DST_SWAP_CHAIN_ID,
|
|
540
|
+
},
|
|
326
541
|
supportedChains,
|
|
327
542
|
wagmiConfig,
|
|
328
543
|
),
|
|
@@ -544,6 +759,61 @@ describe("SwapProvider", () => {
|
|
|
544
759
|
result.current.dstChain?.chainId,
|
|
545
760
|
);
|
|
546
761
|
});
|
|
762
|
+
|
|
763
|
+
it("should preserve token when changing destination chain if token exists on both chains", async () => {
|
|
764
|
+
// Set up initial state with Arbitrum as source chain
|
|
765
|
+
const arbitrumChain = supportedChains.find(
|
|
766
|
+
(chain) => chain.chainId === "42161", // Arbitrum One
|
|
767
|
+
);
|
|
768
|
+
if (!arbitrumChain) throw new Error("Arbitrum chain not found");
|
|
769
|
+
|
|
770
|
+
// Find WETH token on Base
|
|
771
|
+
const arbitrumWethToken = arbitrumChain.tokens.find(
|
|
772
|
+
(token) => token.tokenId === "WETH",
|
|
773
|
+
);
|
|
774
|
+
if (!arbitrumWethToken)
|
|
775
|
+
throw new Error("WETH token not found on arbitrum");
|
|
776
|
+
|
|
777
|
+
// Set Base as initial destination chain
|
|
778
|
+
const baseChain = supportedChains.find(
|
|
779
|
+
(chain) => chain.chainId === "8453", // Base
|
|
780
|
+
);
|
|
781
|
+
if (!baseChain) throw new Error("Base chain not found");
|
|
782
|
+
|
|
783
|
+
const { result } = renderHook(() => useSwapContext(), {
|
|
784
|
+
wrapper: getWrapper(
|
|
785
|
+
{
|
|
786
|
+
...integrationConfig,
|
|
787
|
+
bridge: true,
|
|
788
|
+
srcChainId: arbitrumChain.chainId,
|
|
789
|
+
dstChainId: baseChain.chainId,
|
|
790
|
+
srcTokenAddr: arbitrumWethToken.address,
|
|
791
|
+
},
|
|
792
|
+
supportedChains,
|
|
793
|
+
wagmiConfig,
|
|
794
|
+
),
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
await act(async () => {
|
|
798
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// Find Ethereum chain
|
|
802
|
+
const ethereumChain = supportedChains.find(
|
|
803
|
+
(chain) => chain.chainId === "1", // Ethereum
|
|
804
|
+
);
|
|
805
|
+
if (!ethereumChain) throw new Error("Ethereum chain not found");
|
|
806
|
+
|
|
807
|
+
// Change destination chain to Ethereum
|
|
808
|
+
await act(async () => {
|
|
809
|
+
result.current.setDstChain(ethereumChain);
|
|
810
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
// Verify that WETH is still selected but with Ethereum's address
|
|
814
|
+
expect(result.current.dstToken?.tokenId).toBe("WETH");
|
|
815
|
+
expect(result.current.dstChain?.chainId).toBe("1");
|
|
816
|
+
});
|
|
547
817
|
});
|
|
548
818
|
|
|
549
819
|
describe("Swap mode validation", () => {
|