@xswap-link/sdk 0.8.6 → 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.
Files changed (41) hide show
  1. package/.github/workflows/main.yml +2 -1
  2. package/.github/workflows/pr_agent.yml +22 -0
  3. package/CHANGELOG.md +12 -0
  4. package/babel.config.cjs +8 -0
  5. package/dist/index.global.js +130 -133
  6. package/dist/index.js +10 -10
  7. package/dist/index.mjs +10 -10
  8. package/jest.config.ts +37 -0
  9. package/package.json +17 -5
  10. package/src/components/Modal/index.tsx +1 -1
  11. package/src/components/Swap/Header/index.tsx +1 -1
  12. package/src/components/Swap/ReorderButton/ReorderButton.tsx +12 -10
  13. package/src/components/Swap/SwapView/FeesPanel/index.tsx +1 -1
  14. package/src/components/Swap/SwapView/SwapButton/index.tsx +23 -21
  15. package/src/components/Swap/SwapView/SwapPanel/AmountPanel/index.tsx +4 -2
  16. package/src/components/Swap/SwapView/SwapPanel/ChainPanel/ChainPicker/ChainItem/index.tsx +5 -7
  17. package/src/components/Swap/SwapView/SwapPanel/ChainPanel/ChainPicker/index.tsx +18 -2
  18. package/src/components/Swap/SwapView/SwapPanel/ChainPanel/index.tsx +1 -24
  19. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/QuickPickTokenItem/index.tsx +2 -11
  20. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/TokenItem/index.tsx +12 -3
  21. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +124 -34
  22. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/index.tsx +1 -1
  23. package/src/components/Swap/SwapView/index.tsx +1 -1
  24. package/src/constants/index.ts +1 -1
  25. package/src/context/HistoryProvider.tsx +2 -2
  26. package/src/context/SwapProvider.tsx +163 -53
  27. package/src/context/TransactionProvider.tsx +1 -1
  28. package/src/utils/validation.ts +239 -186
  29. package/tailwind.config.js +1 -0
  30. package/test/context/SwapProvider.test.tsx +851 -0
  31. package/test/fileMock.ts +1 -0
  32. package/test/fixtures/bridgeTokens.mock.ts +1318 -0
  33. package/test/fixtures/bridgeTokensDictionary.mock.ts +1272 -0
  34. package/test/fixtures/integrationConfig.mock.ts +10 -0
  35. package/test/fixtures/supportedChains.mock.ts +32950 -0
  36. package/test/{api.test.ts → services/getChains.test.ts} +6 -5
  37. package/test/setup.ts +13 -0
  38. package/test/styleMock.ts +1 -0
  39. package/jest.config.json +0 -8
  40. package/test/api.mock.ts +0 -106
  41. package/test/setupTests.ts +0 -3
@@ -19,10 +19,11 @@ 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;
25
- chain: Chain;
26
+ chain?: Chain;
26
27
  tokens: TokenOption[];
27
28
  otherTokens: TokenOption[];
28
29
  selectedTokenAddress?: string;
@@ -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(() => {
@@ -59,7 +62,7 @@ export const TokenPicker = ({
59
62
  }, [isOpen]);
60
63
 
61
64
  const importCustomToken = useCallback(() => {
62
- if (customTokenData && isETHAddressValid(searchValue)) {
65
+ if (chain && customTokenData && isETHAddressValid(searchValue)) {
63
66
  let customTokens = {};
64
67
  if (!isServer) {
65
68
  customTokens = JSON.parse(
@@ -97,7 +100,7 @@ export const TokenPicker = ({
97
100
  currentRouteRequestNonce.current = nonce;
98
101
  const rpcUrl = supportedChains.find(
99
102
  ({ ecosystem, chainId }) =>
100
- ecosystem === Ecosystem.EVM && chainId === chain.chainId,
103
+ ecosystem === Ecosystem.EVM && chainId === chain?.chainId,
101
104
  )?.publicRpcUrls[0];
102
105
  const customTokenContract = new ethers.Contract(
103
106
  searchValue.toLowerCase(),
@@ -134,6 +137,7 @@ export const TokenPicker = ({
134
137
  if (
135
138
  !bridgeUI &&
136
139
  isETHAddressValid(searchValue) &&
140
+ chain &&
137
141
  !findTokenDataByAddressAndChain(searchValue.toLowerCase(), chain.chainId)
138
142
  ) {
139
143
  getCustomTokenData();
@@ -187,35 +191,63 @@ export const TokenPicker = ({
187
191
  }, [isOpen]);
188
192
 
189
193
  const allTokens = useMemo(() => {
190
- const filteredTokensSorted = [
191
- ...filteredTokens
192
- .filter((token) => BigNumber.from(token?.balance || "0").gt(0))
193
- .sort((tokenDataA, tokenDataB) => {
194
- return tokenDataB.priority - tokenDataA.priority;
195
- }),
196
- ...filteredTokens
197
- .filter((token) => BigNumber.from(token?.balance || "0").eq(0))
198
- .sort((tokenDataA, tokenDataB) => {
199
- return tokenDataB.priority - tokenDataA.priority;
200
- }),
201
- ];
202
- const otherFilteredTokensSorted = [
203
- ...otherFilteredTokens
204
- .filter((token) => BigNumber.from(token?.balance || "0").gt(0))
205
- .sort((tokenDataA, tokenDataB) => {
206
- return tokenDataB.priority - tokenDataA.priority;
207
- }),
208
- ...otherFilteredTokens
209
- .filter((token) => BigNumber.from(token?.balance || "0").eq(0))
210
- .sort((tokenDataA, tokenDataB) => {
211
- return tokenDataB.priority - tokenDataA.priority;
212
- }),
213
- ];
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
+ );
204
+
205
+ const currentChainTokens = filteredTokens
206
+ .filter((token) => BigNumber.from(token?.balance || "0").eq(0))
207
+ .sort(
208
+ (tokenDataA, tokenDataB) => tokenDataB.priority - tokenDataA.priority,
209
+ );
210
+
211
+ const otherChainTokens = otherFilteredTokens
212
+ .filter((token) => BigNumber.from(token?.balance || "0").eq(0))
213
+ .sort(
214
+ (tokenDataA, tokenDataB) => tokenDataB.priority - tokenDataA.priority,
215
+ );
214
216
 
215
- return [...filteredTokensSorted, ...otherFilteredTokensSorted];
216
- }, [filteredTokens, otherFilteredTokens]);
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
+ ];
217
232
 
218
- const tokenGroups = [filteredTokens, otherFilteredTokens];
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
+ ];
219
251
 
220
252
  return showImportWarning ? (
221
253
  <>
@@ -312,6 +344,7 @@ export const TokenPicker = ({
312
344
  <CloseIcon />
313
345
  </div>
314
346
  </div>
347
+
315
348
  <TextInput
316
349
  ref={tokenSearchRef}
317
350
  inputIcon={
@@ -323,7 +356,13 @@ export const TokenPicker = ({
323
356
  value={searchValue}
324
357
  handleChange={setSearchValue}
325
358
  />
326
- <div className="flex flex-wrap gap-1 my-4 mx-0">
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
+ >
327
366
  {tokens
328
367
  .filter((token) => token?.quickPick)
329
368
  .map((token) => (
@@ -382,12 +421,62 @@ export const TokenPicker = ({
382
421
  <GroupedVirtuoso
383
422
  style={{ maxHeight: "400px", height: "100%" }}
384
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
+ }}
385
432
  groupContent={(index) => (
386
433
  <div className="py-1 bg-t_bg_primary text-t_text_primary">
387
434
  <div className="px-4 text-left">
388
- {index === 0
389
- ? `${chain.displayName} network`
390
- : "Other networks"}
435
+ {(() => {
436
+ if (chain) {
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
+ }
478
+ }
479
+ })()}
391
480
  </div>
392
481
  <div className="horizontal-separator" />
393
482
  {!tokenGroups[index]?.length && (
@@ -400,6 +489,7 @@ export const TokenPicker = ({
400
489
  allTokens[index] && (
401
490
  <TokenItem
402
491
  token={allTokens[index]}
492
+ selectedChainId={chain?.chainId}
403
493
  selectedTokenAddress={selectedTokenAddress}
404
494
  onClick={() => {
405
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-5">
11
+ <div className="flex flex-col gap-[10px]">
12
12
  <SwapPanel type="source" />
13
13
  <ReorderButton />
14
14
  <SwapPanel type="destination" />
@@ -12,7 +12,7 @@ export const ROUTE_TIMEOUT_MS = 10000;
12
12
  export const MINIMUM_DISPLAYED_TOKEN_AMOUNT = 0.0001;
13
13
  export const DEFAULT_SOURCE_CHAIN_ID = "1";
14
14
  export const DEFAULT_SOURCE_TOKEN_ADDR = constants.AddressZero;
15
- export const DEFAULT_DESTINATION_CHAIN_ID = "8453";
15
+ export const DEFAULT_DESTINATION_CHAIN_ID = "1";
16
16
  export const DEFAULT_DESTINATION_TOKEN_ADDR =
17
17
  "0x8fe815417913a93ea99049fc0718ee1647a2a07c"; // XSWAP token
18
18
  export const CCIP_EXPLORER = "https://ccip.chain.link/";
@@ -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 { closeSnackbar, useSnackbar } from "notistack";
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
  });
@@ -1,10 +1,8 @@
1
1
  import { TxConfigFormPayload } from "@src/components";
2
2
  import {
3
3
  DEFAULT_DESTINATION_CHAIN_ID,
4
- DEFAULT_DESTINATION_TOKEN_ADDR,
5
4
  DEFAULT_REQUEST_ABORT_MSG,
6
5
  DEFAULT_SOURCE_CHAIN_ID,
7
- DEFAULT_SOURCE_TOKEN_ADDR,
8
6
  } from "@src/constants";
9
7
  import { ADDRESSES } from "@src/contracts";
10
8
  import {
@@ -32,9 +30,8 @@ import {
32
30
  safeBigNumberFrom,
33
31
  weiToHumanReadable,
34
32
  } from "@src/utils";
35
- import { mapToValidPath } from "@src/utils/validation";
36
33
  import BigNumberJS from "bignumber.js";
37
- import { BigNumber, constants } from "ethers";
34
+ import { BigNumber, constants, ethers } from "ethers";
38
35
  import {
39
36
  createContext,
40
37
  Dispatch,
@@ -49,6 +46,7 @@ import {
49
46
  } from "react";
50
47
  import { Config, useAccount } from "wagmi";
51
48
  import { useHistory } from "./HistoryProvider";
49
+ import { mapToValidPath } from "@src/utils/validation";
52
50
 
53
51
  type Tab = "Swap" | "History" | "Settings";
54
52
  type SwapContext = {
@@ -150,7 +148,7 @@ const init: SwapContext = {
150
148
  dstValueMin: "",
151
149
  lastTx: undefined,
152
150
  expressDelivery: true,
153
- infiniteApproval: false,
151
+ infiniteApproval: true,
154
152
  slippage: "1.5",
155
153
  route: undefined,
156
154
  isAsyncDataLoaded: false,
@@ -194,7 +192,7 @@ export const SwapProvider = ({
194
192
  wagmiConfig,
195
193
  overlay,
196
194
  }: Props) => {
197
- const { address } = useAccount();
195
+ const { address, chainId: walletChainId } = useAccount();
198
196
  const [tab, setTab] = useState(init.tab);
199
197
  const [error, setError] = useState(init.error);
200
198
  const [expressDelivery, setExpressDelivery] = useState(init.expressDelivery);
@@ -344,8 +342,8 @@ export const SwapProvider = ({
344
342
  }, [bridgeTokens]);
345
343
 
346
344
  const getTokenOptions = useCallback(
347
- (chainId: string | undefined, oppositeChainId: string | undefined) => {
348
- if (!chainId || !oppositeChainId) {
345
+ (chainId: string | undefined) => {
346
+ if (!chainId) {
349
347
  return [];
350
348
  }
351
349
 
@@ -560,16 +558,12 @@ export const SwapProvider = ({
560
558
  }, [quoteRoute]);
561
559
 
562
560
  useEffect(() => {
563
- if (!srcChain || !dstChain) {
564
- return;
565
- }
566
561
  (async () => {
567
562
  // source chain
568
563
  const srcTokenOptions: TokenOption[] = getTokenOptions(
569
- srcChain.chainId,
570
- dstChain.chainId,
564
+ srcChain?.chainId,
571
565
  ).filter(({ symbol }) =>
572
- dstChain.chainId === srcChain.chainId
566
+ dstChain?.chainId === srcChain?.chainId
573
567
  ? symbol !== dstToken?.symbol
574
568
  : true,
575
569
  );
@@ -577,10 +571,9 @@ export const SwapProvider = ({
577
571
 
578
572
  // destination chain
579
573
  const dstTokenOptions: TokenOption[] = getTokenOptions(
580
- dstChain.chainId,
581
- srcChain.chainId,
574
+ dstChain?.chainId,
582
575
  ).filter(({ symbol }) =>
583
- dstChain.chainId === srcChain.chainId
576
+ dstChain?.chainId === srcChain?.chainId
584
577
  ? symbol !== srcToken?.symbol
585
578
  : true,
586
579
  );
@@ -591,7 +584,7 @@ export const SwapProvider = ({
591
584
  const srcOtherChains = supportedChains.filter(
592
585
  ({ chainId, web3Environment, swapSupported, bridgeSupported }) => {
593
586
  if (
594
- chainId === srcChain.chainId ||
587
+ chainId === srcChain?.chainId ||
595
588
  web3Environment !== Web3Environment.MAINNET
596
589
  ) {
597
590
  return false;
@@ -606,8 +599,8 @@ export const SwapProvider = ({
606
599
  );
607
600
  for (const { chainId } of srcOtherChains) {
608
601
  srcOtherTokenOptions.push(
609
- ...getTokenOptions(chainId, dstChain.chainId).filter(({ symbol }) =>
610
- chainId === dstChain.chainId ? symbol !== dstToken?.symbol : true,
602
+ ...getTokenOptions(chainId).filter(({ symbol }) =>
603
+ chainId === dstChain?.chainId ? symbol !== dstToken?.symbol : true,
611
604
  ),
612
605
  );
613
606
  }
@@ -617,14 +610,14 @@ export const SwapProvider = ({
617
610
  const dstOtherTokenOptions: TokenOption[] = [];
618
611
  const dstOtherChains = supportedChains.filter(
619
612
  ({ chainId, web3Environment, swapSupported }) =>
620
- chainId !== dstChain.chainId &&
613
+ chainId !== dstChain?.chainId &&
621
614
  web3Environment === Web3Environment.MAINNET &&
622
615
  swapSupported,
623
616
  );
624
617
  for (const { chainId } of dstOtherChains) {
625
618
  dstOtherTokenOptions.push(
626
- ...getTokenOptions(chainId, srcChain.chainId).filter(({ symbol }) =>
627
- chainId === srcChain.chainId ? symbol !== srcToken?.symbol : true,
619
+ ...getTokenOptions(chainId).filter(({ symbol }) =>
620
+ chainId === srcChain?.chainId ? symbol !== srcToken?.symbol : true,
628
621
  ),
629
622
  );
630
623
  }
@@ -657,6 +650,7 @@ export const SwapProvider = ({
657
650
  }
658
651
  })();
659
652
  }, [
653
+ bridgeUI,
660
654
  srcChain,
661
655
  dstChain,
662
656
  address,
@@ -716,37 +710,149 @@ export const SwapProvider = ({
716
710
  );
717
711
  }, [srcChain]);
718
712
 
713
+ // Add new state to track initial setup
714
+ const [isInitialStateSet, setIsInitialStateSet] = useState(false);
715
+
719
716
  useEffect(() => {
717
+ // apply wallet chain only after initial state is set
718
+ if (!walletChainId || !isInitialStateSet || integrationConfig?.srcChainId) {
719
+ return;
720
+ }
720
721
  const initSrcChain = supportedChains.find(
721
- ({ chainId }) =>
722
- chainId === (integrationConfig?.srcChainId || DEFAULT_SOURCE_CHAIN_ID),
723
- );
724
- const initSrcToken = initSrcChain?.tokens.find(
725
- ({ address }) =>
726
- address.toLowerCase() ===
727
- (integrationConfig?.srcTokenAddr?.toLowerCase() ||
728
- DEFAULT_SOURCE_TOKEN_ADDR.toLocaleLowerCase()),
722
+ ({ chainId }) => chainId === `${walletChainId}`,
729
723
  );
730
- const initDstChain = supportedChains.find(
731
- ({ chainId }) =>
732
- chainId ===
733
- (integrationConfig.dstChainId || DEFAULT_DESTINATION_CHAIN_ID),
734
- );
735
- const initDstToken = initDstChain?.tokens.find(
736
- ({ address }) =>
737
- address.toLowerCase() ===
738
- (integrationConfig.dstTokenAddr?.toLowerCase() ||
739
- DEFAULT_DESTINATION_TOKEN_ADDR.toLowerCase()),
724
+
725
+ if (initSrcChain) {
726
+ setSrcChain(initSrcChain);
727
+ }
728
+ }, [
729
+ walletChainId,
730
+ isInitialStateSet,
731
+ supportedChains,
732
+ integrationConfig?.srcChainId,
733
+ ]);
734
+
735
+ useEffect(() => {
736
+ if (isInitialStateSet) {
737
+ return;
738
+ }
739
+ setSrcToken(undefined);
740
+ setDstToken(undefined);
741
+ setSrcChain(undefined);
742
+ setDstChain(undefined);
743
+
744
+ // eslint-disable-next-line react-hooks/exhaustive-deps
745
+ }, [bridgeUI]);
746
+
747
+ useEffect(() => {
748
+ // Skip if initial state was already set or wait if supported chains are not loaded yet
749
+
750
+ if (
751
+ isInitialStateSet ||
752
+ !supportedChains ||
753
+ supportedChains.length === 0 ||
754
+ !bridgeTokensDictionary
755
+ ) {
756
+ return;
757
+ }
758
+
759
+ let srcTokenAddress = integrationConfig.srcTokenAddr;
760
+ let dstTokenAddress = integrationConfig.dstTokenAddr;
761
+ // is it a valid eth address? if not try to find it by symbol / tokenId
762
+ if (srcTokenAddress && !ethers.utils.isAddress(srcTokenAddress)) {
763
+ srcTokenAddress = supportedChains
764
+ .find((chain) => chain.chainId === integrationConfig?.srcChainId)
765
+ ?.tokens.find(
766
+ (token) => token.tokenId === integrationConfig?.srcTokenAddr,
767
+ )
768
+ ?.address?.toLowerCase();
769
+ }
770
+
771
+ if (dstTokenAddress && !ethers.utils.isAddress(dstTokenAddress)) {
772
+ dstTokenAddress = supportedChains
773
+ .find((chain) => chain.chainId === integrationConfig?.dstChainId)
774
+ ?.tokens.find(
775
+ (token) => token.tokenId === integrationConfig?.dstTokenAddr,
776
+ )
777
+ ?.address?.toLowerCase();
778
+ }
779
+
780
+ const searchedChainId =
781
+ // currently set chain
782
+ srcChain ||
783
+ // chain set by the integrator
784
+ integrationConfig?.srcChainId ||
785
+ // wallet chain
786
+ (walletChainId ? `${walletChainId}` : undefined) ||
787
+ // default chain
788
+ (bridgeUI ? undefined : DEFAULT_SOURCE_CHAIN_ID);
789
+
790
+ const initSrcChain = supportedChains.find(
791
+ ({ chainId }) => chainId === searchedChainId,
740
792
  );
741
793
 
742
- setSrcChain(initSrcChain);
743
- setSrcToken(initSrcToken);
744
- setDstChain(initDstChain);
745
- setDstToken(initDstToken);
746
- }, [integrationConfig, supportedChains]);
794
+ let initSrcToken: Token | undefined;
795
+ if (initSrcChain) {
796
+ initSrcToken = initSrcChain.tokens.find(
797
+ ({ address }) =>
798
+ address.toLowerCase() === srcTokenAddress?.toLowerCase(),
799
+ );
800
+ }
801
+
802
+ let initDstChain: Chain | undefined = bridgeUI
803
+ ? undefined
804
+ : supportedChains.find(
805
+ ({ chainId }) => chainId === DEFAULT_DESTINATION_CHAIN_ID,
806
+ );
807
+
808
+ // if integrator set it, use it
809
+ if (integrationConfig.dstChainId) {
810
+ initDstChain = supportedChains.find(
811
+ ({ chainId }) => chainId === integrationConfig.dstChainId,
812
+ );
813
+ }
814
+
815
+ let initDstToken: Token | undefined;
816
+ if (initDstChain) {
817
+ initDstToken = initDstChain.tokens.find(
818
+ ({ address }) => address.toLowerCase() === dstTokenAddress,
819
+ );
820
+ }
821
+
822
+ // the values here should not be from state but from the code above
823
+ const { source, target } = mapToValidPath({
824
+ source: { chain: initSrcChain, token: initSrcToken },
825
+ target: { chain: initDstChain, token: initDstToken },
826
+ type: bridgeUI ? "BRIDGE" : "SWAP",
827
+ supportedChains,
828
+ bridgeTokensDictionary,
829
+ });
830
+
831
+ setSrcChain(source.chain);
832
+ setDstChain(target.chain);
833
+ setSrcToken(source.token);
834
+ setDstToken(target.token);
835
+ // Mark initial state as set
836
+ setIsInitialStateSet(true);
837
+ // eslint-disable-next-line react-hooks/exhaustive-deps
838
+ }, [
839
+ integrationConfig?.srcChainId,
840
+ integrationConfig?.srcTokenAddr,
841
+ integrationConfig?.dstChainId,
842
+ integrationConfig?.dstTokenAddr,
843
+ supportedChains,
844
+ bridgeUI,
845
+ walletChainId,
846
+ isInitialStateSet,
847
+ bridgeTokensDictionary,
848
+ ]);
747
849
 
748
850
  useEffect(() => {
749
- if (!bridgeTokensDictionary) {
851
+ if (
852
+ !bridgeTokensDictionary ||
853
+ supportedChains.length === 0 ||
854
+ !isInitialStateSet
855
+ ) {
750
856
  return;
751
857
  }
752
858
 
@@ -789,11 +895,12 @@ export const SwapProvider = ({
789
895
  } else if (window.xPayOnDstChainChange) {
790
896
  window.xPayOnDstChainChange(dstChain?.chainId);
791
897
  }
792
- }, [integrationConfig, dstChain]);
898
+ // eslint-disable-next-line react-hooks/exhaustive-deps
899
+ }, [integrationConfig.onDstChainChange, dstChain]);
793
900
 
794
901
  useEffect(() => {
795
902
  const token = dstToken
796
- ? { address: dstToken.address.toLowerCase(), symbol: dstToken.symbol }
903
+ ? { address: dstToken.address.toLowerCase(), symbol: dstToken.tokenId }
797
904
  : undefined;
798
905
 
799
906
  if (integrationConfig.onDstTokenChange) {
@@ -801,7 +908,8 @@ export const SwapProvider = ({
801
908
  } else if (window.xPayOnDstTokenChange) {
802
909
  window.xPayOnDstTokenChange(token);
803
910
  }
804
- }, [integrationConfig, dstToken]);
911
+ // eslint-disable-next-line react-hooks/exhaustive-deps
912
+ }, [integrationConfig.onDstTokenChange, dstToken]);
805
913
 
806
914
  useEffect(() => {
807
915
  if (integrationConfig.onSrcChainChange) {
@@ -809,11 +917,12 @@ export const SwapProvider = ({
809
917
  } else if (window.xPayOnSrcChainChange) {
810
918
  window.xPayOnSrcChainChange(srcChain?.chainId);
811
919
  }
812
- }, [integrationConfig, srcChain]);
920
+ // eslint-disable-next-line react-hooks/exhaustive-deps
921
+ }, [integrationConfig.onSrcChainChange, srcChain]);
813
922
 
814
923
  useEffect(() => {
815
924
  const token = srcToken
816
- ? { address: srcToken.address.toLowerCase(), symbol: srcToken.symbol }
925
+ ? { address: srcToken.address.toLowerCase(), symbol: srcToken.tokenId }
817
926
  : undefined;
818
927
 
819
928
  if (integrationConfig.onSrcTokenChange) {
@@ -821,7 +930,8 @@ export const SwapProvider = ({
821
930
  } else if (window.xPayOnSrcTokenChange) {
822
931
  window.xPayOnSrcTokenChange(token);
823
932
  }
824
- }, [integrationConfig, srcToken]);
933
+ // eslint-disable-next-line react-hooks/exhaustive-deps
934
+ }, [integrationConfig.onSrcTokenChange, srcToken]);
825
935
 
826
936
  return (
827
937
  <swapContext.Provider
@@ -1,4 +1,4 @@
1
- import { Ecosystem, EnqueueTxProps, TxStatus } from "@src/models";
1
+ import { Ecosystem, EnqueueTxProps } from "@src/models";
2
2
  import { ContractReceipt, ContractTransaction } from "ethers";
3
3
  import {
4
4
  Context,