@xswap-link/sdk 0.9.7 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xswap-link/sdk",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
4
4
  "description": "JavaScript SDK for XSwap platform",
5
5
  "homepage": "https://github.com/xswap-link/xswap-sdk",
6
6
  "repository": {
@@ -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
  };
@@ -17,4 +17,3 @@ export * from "./TransactionHistory";
17
17
  export * from "./TxUIWrapper";
18
18
  export * from "./Web3Environment";
19
19
  export * from "./XSwapCallType";
20
- export * from "./XSwapConfig";
@@ -64,4 +64,7 @@ export type ModalIntegrationPayload = {
64
64
  onSrcChainChange?: (chain: string | undefined) => void;
65
65
  onConnectWallet?: () => void;
66
66
  bridge?: boolean;
67
+ override?: {
68
+ apiUrl?: string;
69
+ };
67
70
  };
@@ -27,4 +27,7 @@ export type WidgetIntegrationPayload = {
27
27
  onSrcChainChange?: (chain: string | undefined) => void;
28
28
  onConnectWallet?: () => void;
29
29
  bridge?: boolean;
30
+ override?: {
31
+ apiUrl?: string;
32
+ };
30
33
  };
@@ -13,13 +13,18 @@ import {
13
13
  TransactionHistory,
14
14
  TransactionStatus,
15
15
  } from "@src/models";
16
- import xSwapConfig from "../../xswap.config";
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(`${xSwapConfig.apiUrl}${urlPath}`, {
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
  /**
@@ -262,7 +262,14 @@ const mapToValidPathSwap = ({
262
262
  }
263
263
 
264
264
  // Setting tokens
265
- // Removed check for token chainId equality as tokens may not include a chainId property
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,
@@ -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 handle source token change", async () => {
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)
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
304
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 (!newChain) throw new Error("Source chain not set");
518
+ if (!activeSrcChain) throw new Error("Source chain not set");
308
519
 
309
- const newToken = newChain.tokens.find(
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
- { ...integrationConfig, bridge: false },
536
+ {
537
+ ...integrationConfig,
538
+ bridge: false,
539
+ dstChainId: DST_SWAP_CHAIN_ID,
540
+ },
326
541
  supportedChains,
327
542
  wagmiConfig,
328
543
  ),
@@ -1,4 +0,0 @@
1
- export type XSwapConfig = {
2
- apiUrl: string;
3
- localCss: boolean;
4
- };
package/xswap.config.ts DELETED
@@ -1,20 +0,0 @@
1
- import { XSwapConfig } from "@src/models";
2
-
3
- const xSwapConfig: XSwapConfig = {
4
- apiUrl: "https://xswap.link/api",
5
- // apiUrl: "http://127.0.0.1:5001/xswap-app/europe-west1/app/api/beta",
6
- localCss: false,
7
- };
8
-
9
- let xSwapConfigLocal = {};
10
- try {
11
- // eslint-disable-next-line
12
- xSwapConfigLocal = require("./xswap.config.local").default;
13
- } catch {
14
- // There's no local config, nothing to override.
15
- }
16
-
17
- export default {
18
- ...xSwapConfig,
19
- ...xSwapConfigLocal,
20
- };