@xswap-link/sdk 0.7.0 → 0.8.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/index.d.mts +122 -70
  3. package/dist/index.d.ts +122 -70
  4. package/dist/index.global.js +686 -281
  5. package/dist/index.js +10 -10
  6. package/dist/index.mjs +10 -10
  7. package/package.json +1 -1
  8. package/src/assets/icons/ArrowsExchange.tsx +18 -0
  9. package/src/assets/icons/index.ts +1 -0
  10. package/src/components/Swap/Header/index.tsx +4 -2
  11. package/src/components/Swap/HistoryView/index.tsx +2 -2
  12. package/src/components/Swap/ReorderButton/ReorderButton.tsx +37 -0
  13. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/ArrowIcon.tsx +1 -1
  14. package/src/components/Swap/SwapView/SwapButton/index.tsx +3 -1
  15. package/src/components/Swap/SwapView/SwapPanel/ChainPanel/ChainPicker/ChainItem/index.tsx +55 -22
  16. package/src/components/Swap/SwapView/SwapPanel/ChainPanel/ChainPicker/index.tsx +71 -6
  17. package/src/components/Swap/SwapView/SwapPanel/ChainPanel/index.tsx +68 -15
  18. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +10 -2
  19. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/index.tsx +31 -11
  20. package/src/components/Swap/SwapView/SwapPanel/index.tsx +8 -3
  21. package/src/components/Swap/SwapView/index.tsx +3 -3
  22. package/src/components/Swap/index.tsx +1 -1
  23. package/src/components/TxConfigForm/index.tsx +57 -4
  24. package/src/components/TxWidgetWC/index.tsx +47 -2
  25. package/src/components/TxWidgetWCWrapped/index.tsx +33 -1
  26. package/src/config/index.ts +1 -1
  27. package/src/context/SwapProvider.tsx +177 -27
  28. package/src/models/BridgeTokensDictionary.ts +7 -0
  29. package/src/models/TokenData.ts +3 -1
  30. package/src/models/index.ts +6 -5
  31. package/src/models/payloads/ModalIntegrationPayload.ts +15 -1
  32. package/src/models/payloads/WidgetIntegrationPayload.ts +15 -1
  33. package/src/services/api.ts +6 -1
  34. package/src/services/integrations/transactions.ts +20 -0
  35. package/src/types/global.d.ts +15 -0
  36. package/src/utils/validation.ts +383 -0
  37. package/tsconfig.json +3 -2
@@ -1,16 +1,16 @@
1
+ import { ReorderButton } from "../ReorderButton/ReorderButton";
1
2
  import { FeesPanel } from "./FeesPanel";
2
3
  import { SwapButton } from "./SwapButton";
3
4
  import { SwapPanel } from "./SwapPanel";
4
- import { ArrowIcon } from "@src/components/Swap/SwapView/ConfirmationView/TxOverview/ArrowIcon";
5
5
 
6
6
  export type SwapPanelType = "source" | "destination";
7
7
 
8
8
  export const SwapView = () => {
9
9
  return (
10
10
  <div className="flex flex-col gap-2 fill-t_text_primary">
11
- <div className="relative flex flex-col gap-5">
11
+ <div className="flex flex-col gap-5">
12
12
  <SwapPanel type="source" />
13
- <ArrowIcon />
13
+ <ReorderButton />
14
14
  <SwapPanel type="destination" />
15
15
  </div>
16
16
  <FeesPanel />
@@ -25,7 +25,7 @@ export const Swap = ({ onClose }: Props) => {
25
25
 
26
26
  return (
27
27
  <div
28
- className={`flex flex-col gap-5 p-4 mx-auto text-t_text_primary bg-t_bg_primary rounded-3xl`}
28
+ className={`relative flex flex-col gap-5 p-4 text-t_text_primary bg-t_bg_primary rounded-3xl flex-1`}
29
29
  >
30
30
  <Header onClose={onClose} />
31
31
  {view}
@@ -5,6 +5,7 @@ import {
5
5
  SwapProvider,
6
6
  TransactionProvider,
7
7
  TxUIWrapper,
8
+ useHistory,
8
9
  } from "@src/context";
9
10
  import { Chain, ModalIntegrationPayload, Route } from "@src/models";
10
11
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
@@ -23,7 +24,6 @@ export type TxConfigFormPayload = Omit<
23
24
  srcTokenAddr?: string;
24
25
  dstDisplayTokenAddr?: string;
25
26
  supportedChains: Chain[];
26
- defaultWalletPicker?: boolean;
27
27
  };
28
28
 
29
29
  const queryClient = new QueryClient();
@@ -44,6 +44,27 @@ const WagmiReload: FC = () => {
44
44
  return <></>;
45
45
  };
46
46
 
47
+ const PendingTransactionsEmitter: FC<{
48
+ onPendingTransactionsChange: ModalIntegrationPayload["onPendingTransactionsChange"];
49
+ }> = ({ onPendingTransactionsChange }) => {
50
+ const { history } = useHistory();
51
+
52
+ const pendingTransactions = useMemo(
53
+ () => history.filter((entry) => entry.status === "IN_PROGRESS"),
54
+ [history],
55
+ );
56
+
57
+ useEffect(() => {
58
+ if (onPendingTransactionsChange) {
59
+ onPendingTransactionsChange(pendingTransactions);
60
+ } else if (window.xPayOnPendingTransactionsChange) {
61
+ window.xPayOnPendingTransactionsChange(pendingTransactions);
62
+ }
63
+ }, [onPendingTransactionsChange, pendingTransactions]);
64
+
65
+ return <></>;
66
+ };
67
+
47
68
  export const TxConfigForm = ({
48
69
  integratorId,
49
70
  dstChainId,
@@ -60,6 +81,16 @@ export const TxConfigForm = ({
60
81
  lightTheme,
61
82
  defaultWalletPicker,
62
83
  styles,
84
+ onPendingTransactionsChange,
85
+ dstTokenLocked,
86
+ dstChainLocked,
87
+ srcTokenLocked,
88
+ srcChainLocked,
89
+ onDstTokenChange,
90
+ onDstChainChange,
91
+ onSrcTokenChange,
92
+ onSrcChainChange,
93
+ bridge,
63
94
  }: TxConfigFormPayload &
64
95
  (
65
96
  | {
@@ -102,6 +133,9 @@ export const TxConfigForm = ({
102
133
  <QueryClientProvider client={queryClient}>
103
134
  <WagmiReload />
104
135
  <HistoryProvider>
136
+ <PendingTransactionsEmitter
137
+ onPendingTransactionsChange={onPendingTransactionsChange}
138
+ />
105
139
  <SwapProvider
106
140
  integrationConfig={{
107
141
  integratorId,
@@ -113,6 +147,15 @@ export const TxConfigForm = ({
113
147
  desc,
114
148
  dstDisplayTokenAddr,
115
149
  defaultWalletPicker,
150
+ dstTokenLocked,
151
+ dstChainLocked,
152
+ srcTokenLocked,
153
+ srcChainLocked,
154
+ onDstTokenChange,
155
+ onDstChainChange,
156
+ onSrcTokenChange,
157
+ onSrcChainChange,
158
+ bridge,
116
159
  }}
117
160
  supportedChains={supportedChains}
118
161
  wagmiConfig={wagmiConfig}
@@ -122,16 +165,16 @@ export const TxConfigForm = ({
122
165
  <TxUIWrapper>
123
166
  {overlay ? (
124
167
  <div
125
- className="top-0 left-0 right-0 bottom-0 bg-[rgba(0,0,0,0.8)] fixed flex items-center justify-center z-10"
168
+ className="top-0 left-0 right-0 bottom-0 bg-[rgba(0,0,0,0.8)] fixed flex flex-col items-center justify-center z-10"
126
169
  onMouseDown={onBackdropClick}
127
170
  >
128
- <div className="relative bg-t_bg_primary rounded-3xl overflow-auto text-t_text_primary border-2 border-solid border-[rgba(255,255,255,0.1)] min-w-x_modal max-w-x_modal min-h-[432px] w-full">
171
+ <div className="relative bg-t_bg_primary rounded-3xl overflow-auto text-t_text_primary border-2 border-solid border-[rgba(255,255,255,0.1)] min-w-x_modal max-w-x_modal min-h-[532px] w-full">
129
172
  <Swap onSubmit={onSubmit} onClose={onClose} />
130
173
  <PoweredBy />
131
174
  </div>
132
175
  </div>
133
176
  ) : (
134
- <div className="bg-t_bg_primary rounded-3xl overflow-auto text-t_text_primary border-2 border-solid border-[rgba(255,255,255,0.1)] min-h-[432px] w-full">
177
+ <div className="bg-t_bg_primary rounded-3xl overflow-auto text-t_text_primary border-2 border-solid border-[rgba(255,255,255,0.1)] min-h-[532px] w-full flex flex-col">
135
178
  <Swap onSubmit={() => {}} onClose={() => {}} />
136
179
  <PoweredBy />
137
180
  </div>
@@ -159,6 +202,11 @@ export const openTxConfigForm = async ({
159
202
  lightTheme,
160
203
  defaultWalletPicker,
161
204
  styles,
205
+ onPendingTransactionsChange,
206
+ dstTokenLocked,
207
+ dstChainLocked,
208
+ srcTokenLocked,
209
+ srcChainLocked,
162
210
  }: TxConfigFormPayload): Promise<Route> => {
163
211
  try {
164
212
  if (xpayRoot === null) {
@@ -197,6 +245,11 @@ export const openTxConfigForm = async ({
197
245
  }}
198
246
  overlay={true}
199
247
  defaultWalletPicker={defaultWalletPicker}
248
+ onPendingTransactionsChange={onPendingTransactionsChange}
249
+ dstTokenLocked={dstTokenLocked}
250
+ dstChainLocked={dstChainLocked}
251
+ srcTokenLocked={srcTokenLocked}
252
+ srcChainLocked={srcChainLocked}
200
253
  />,
201
254
  );
202
255
  });
@@ -5,12 +5,12 @@ import {
5
5
  WidgetIntegrationPayload,
6
6
  } from "@src/models";
7
7
  import { getChains } from "@src/services";
8
+ import { isServer } from "@src/utils";
8
9
  import { createWebComponent } from "@src/utils/webComponents";
9
10
  import { useEffect, useState } from "react";
10
11
  import { Spinner } from "../Spinner";
11
12
  import { TxConfigForm } from "../TxConfigForm";
12
13
  import CSS from "../global.css";
13
- import { isServer } from "@src/utils";
14
14
 
15
15
  const TxWidget = ({
16
16
  integratorId,
@@ -24,6 +24,16 @@ const TxWidget = ({
24
24
  lightTheme,
25
25
  defaultWalletPicker,
26
26
  styles,
27
+ onPendingTransactionsChange,
28
+ dstTokenLocked,
29
+ dstChainLocked,
30
+ srcTokenLocked,
31
+ srcChainLocked,
32
+ onDstTokenChange,
33
+ onDstChainChange,
34
+ onSrcTokenChange,
35
+ onSrcChainChange,
36
+ bridge,
27
37
  }: WidgetIntegrationPayload) => {
28
38
  const [loading, setLoading] = useState(true);
29
39
  const [supportedChains, setSupportedChains] = useState<Chain[]>([]);
@@ -57,7 +67,9 @@ const TxWidget = ({
57
67
  <style>{CSS}</style>
58
68
 
59
69
  {loading ? (
60
- <Spinner className="w-10 h-10" />
70
+ <div className="min-h-[532px] w-full flex items-center justify-center">
71
+ <Spinner className="w-10 h-10" />
72
+ </div>
61
73
  ) : (
62
74
  <TxConfigForm
63
75
  integratorId={integratorId}
@@ -73,6 +85,16 @@ const TxWidget = ({
73
85
  lightTheme={lightTheme}
74
86
  defaultWalletPicker={defaultWalletPicker}
75
87
  styles={parsedStyles}
88
+ onPendingTransactionsChange={onPendingTransactionsChange}
89
+ dstTokenLocked={dstTokenLocked}
90
+ dstChainLocked={dstChainLocked}
91
+ srcTokenLocked={srcTokenLocked}
92
+ srcChainLocked={srcChainLocked}
93
+ onDstTokenChange={onDstTokenChange}
94
+ onDstChainChange={onDstChainChange}
95
+ onSrcTokenChange={onSrcTokenChange}
96
+ onSrcChainChange={onSrcChainChange}
97
+ bridge={bridge}
76
98
  />
77
99
  )}
78
100
  </>
@@ -106,6 +128,16 @@ export const createTxWidgetWC = () => {
106
128
  lightTheme: "boolean",
107
129
  defaultWalletPicker: "boolean",
108
130
  styles: "json",
131
+ onPendingTransactionsChange: "function",
132
+ dstTokenLocked: "boolean",
133
+ dstChainLocked: "boolean",
134
+ srcTokenLocked: "boolean",
135
+ srcChainLocked: "boolean",
136
+ bridge: "boolean",
137
+ onDstTokenChange: "function",
138
+ onDstChainChange: "function",
139
+ onSrcTokenChange: "function",
140
+ onSrcChainChange: "function",
109
141
  },
110
142
  });
111
143
 
@@ -116,6 +148,8 @@ export const createTxWidgetWC = () => {
116
148
  return TxWidgetWC;
117
149
  };
118
150
 
151
+ // Most fields are required here to enforce adding them in TxWidgetWCWrapped.
152
+ // callbacks are optional, because it will not be passed in TxWidgetWCWrapped.
119
153
  export type TxWidgetWCAttributes = {
120
154
  "integrator-id": WidgetIntegrationPayload["integratorId"];
121
155
  "dst-chain": WidgetIntegrationPayload["dstChain"];
@@ -128,4 +162,15 @@ export type TxWidgetWCAttributes = {
128
162
  "light-theme": WidgetIntegrationPayload["lightTheme"];
129
163
  "default-wallet-picker": WidgetIntegrationPayload["defaultWalletPicker"];
130
164
  styles: string | undefined;
165
+ "on-pending-transactions-change"?:
166
+ | WidgetIntegrationPayload["onPendingTransactionsChange"];
167
+ "dst-token-locked": WidgetIntegrationPayload["dstTokenLocked"] | undefined;
168
+ "dst-chain-locked": WidgetIntegrationPayload["dstChainLocked"] | undefined;
169
+ "src-token-locked": WidgetIntegrationPayload["srcTokenLocked"] | undefined;
170
+ "src-chain-locked": WidgetIntegrationPayload["srcChainLocked"] | undefined;
171
+ "on-dst-token-change"?: WidgetIntegrationPayload["onDstTokenChange"];
172
+ "on-dst-chain-change"?: WidgetIntegrationPayload["onDstChainChange"];
173
+ "on-src-token-change"?: WidgetIntegrationPayload["onSrcTokenChange"];
174
+ "on-src-chain-change"?: WidgetIntegrationPayload["onSrcChainChange"];
175
+ bridge: WidgetIntegrationPayload["bridge"] | undefined;
131
176
  };
@@ -1,6 +1,7 @@
1
1
  import { WidgetIntegrationPayload } from "@src/models";
2
- import { createTxWidgetWC, TxWidgetWCAttributes } from "../TxWidgetWC";
3
2
  import { isServer } from "@tanstack/react-query";
3
+ import { useEffect } from "react";
4
+ import { createTxWidgetWC, TxWidgetWCAttributes } from "../TxWidgetWC";
4
5
 
5
6
  const TxWidgetWC = createTxWidgetWC();
6
7
 
@@ -35,7 +36,33 @@ export const TxWidgetWCWrapped = ({
35
36
  lightTheme,
36
37
  defaultWalletPicker,
37
38
  styles,
39
+ onPendingTransactionsChange,
40
+ dstTokenLocked,
41
+ dstChainLocked,
42
+ srcTokenLocked,
43
+ srcChainLocked,
44
+ onDstTokenChange,
45
+ onDstChainChange,
46
+ onSrcTokenChange,
47
+ onSrcChainChange,
48
+ bridge,
38
49
  }: WidgetIntegrationPayload) => {
50
+ useEffect(() => {
51
+ // We couldn't find a way to pass a function to the web component created by @r2wc/core.
52
+ // It is assigned to window to be accessed later from within the web component.
53
+ window.xPayOnPendingTransactionsChange = onPendingTransactionsChange;
54
+ window.xPayOnDstTokenChange = onDstTokenChange;
55
+ window.xPayOnDstChainChange = onDstChainChange;
56
+ window.xPayOnSrcTokenChange = onSrcTokenChange;
57
+ window.xPayOnSrcChainChange = onSrcChainChange;
58
+ }, [
59
+ onPendingTransactionsChange,
60
+ onDstTokenChange,
61
+ onDstChainChange,
62
+ onSrcTokenChange,
63
+ onSrcChainChange,
64
+ ]);
65
+
39
66
  return (
40
67
  <xpay-widget
41
68
  integrator-id={integratorId}
@@ -49,6 +76,11 @@ export const TxWidgetWCWrapped = ({
49
76
  light-theme={lightTheme}
50
77
  default-wallet-picker={defaultWalletPicker}
51
78
  styles={typeof styles === "string" ? styles : JSON.stringify(styles)}
79
+ dst-token-locked={dstTokenLocked}
80
+ dst-chain-locked={dstChainLocked}
81
+ src-token-locked={srcTokenLocked}
82
+ src-chain-locked={srcChainLocked}
83
+ bridge={bridge}
52
84
  ></xpay-widget>
53
85
  );
54
86
  };
@@ -1,2 +1,2 @@
1
- export * from "./wagmiConfig";
2
1
  export * from "./init";
2
+ export * from "./wagmiConfig";
@@ -8,6 +8,8 @@ import {
8
8
  } from "@src/constants";
9
9
  import { ADDRESSES } from "@src/contracts";
10
10
  import {
11
+ BridgeToken,
12
+ BridgeTokensDictionary,
11
13
  Chain,
12
14
  Route,
13
15
  Token,
@@ -16,7 +18,12 @@ import {
16
18
  Transaction,
17
19
  Web3Environment,
18
20
  } from "@src/models";
19
- import { getBalances, getPrices, getRoute } from "@src/services";
21
+ import {
22
+ getBalances,
23
+ getBridgeTokens,
24
+ getPrices,
25
+ getRoute,
26
+ } from "@src/services";
20
27
  import {
21
28
  deepMergeObjects,
22
29
  getBalanceOf,
@@ -25,6 +32,7 @@ import {
25
32
  safeBigNumberFrom,
26
33
  weiToHumanReadable,
27
34
  } from "@src/utils";
35
+ import { mapToValidPath } from "@src/utils/validation";
28
36
  import BigNumberJS from "bignumber.js";
29
37
  import { BigNumber, constants } from "ethers";
30
38
  import {
@@ -44,6 +52,7 @@ import { useHistory } from "./HistoryProvider";
44
52
 
45
53
  type Tab = "Swap" | "History" | "Settings";
46
54
  type SwapContext = {
55
+ bridgeUI: boolean;
47
56
  integrationConfig: Partial<TxConfigFormPayload>;
48
57
  wagmiConfig: Config;
49
58
  supportedChains: Chain[];
@@ -53,22 +62,22 @@ type SwapContext = {
53
62
  error: string;
54
63
  setError: Dispatch<SetStateAction<string>>;
55
64
  feeToken: Token | undefined;
56
- srcChainProvided: boolean;
57
65
  srcChain: Chain | undefined;
58
66
  setSrcChain: Dispatch<SetStateAction<Chain | undefined>>;
59
67
  srcChainTokensOptions: TokenOption[];
60
68
  srcChainOtherTokensOptions: TokenOption[];
61
69
  srcChainTokensPrices: TokenPrices;
62
70
  dstChainTokensPrices: TokenPrices;
63
- srcTokenProvided: boolean;
64
71
  srcToken: Token | undefined;
65
72
  setSrcToken: Dispatch<SetStateAction<Token | undefined>>;
66
- dstChainProvided: boolean;
67
73
  dstChain: Chain | undefined;
68
74
  setDstChain: Dispatch<SetStateAction<Chain | undefined>>;
69
75
  dstChainTokensOptions: TokenOption[];
70
76
  dstChainOtherTokensOptions: TokenOption[];
71
- dstTokenProvided: boolean;
77
+ dstTokenLocked: boolean;
78
+ dstChainLocked?: boolean;
79
+ srcTokenLocked?: boolean;
80
+ srcChainLocked?: boolean;
72
81
  dstToken: Token | undefined;
73
82
  setDstToken: Dispatch<SetStateAction<Token | undefined>>;
74
83
  srcValue: string;
@@ -102,6 +111,7 @@ type SwapContext = {
102
111
  tokenAddress: string,
103
112
  chainId: string,
104
113
  ) => Token | undefined;
114
+ bridgeTokensDictionary: BridgeTokensDictionary | null;
105
115
  };
106
116
 
107
117
  const functionNotImplemented = () => {
@@ -109,6 +119,7 @@ const functionNotImplemented = () => {
109
119
  };
110
120
 
111
121
  const init: SwapContext = {
122
+ bridgeUI: false,
112
123
  integrationConfig: {},
113
124
  wagmiConfig: {} as Config,
114
125
  supportedChains: [],
@@ -116,19 +127,19 @@ const init: SwapContext = {
116
127
  overlay: false,
117
128
  error: "",
118
129
  feeToken: undefined,
119
- srcChainProvided: false,
120
130
  srcChain: undefined,
121
131
  srcChainTokensOptions: [],
122
132
  srcChainOtherTokensOptions: [],
123
133
  srcChainTokensPrices: {},
124
134
  dstChainTokensPrices: {},
125
- dstChainProvided: false,
126
135
  dstChain: undefined,
127
136
  dstChainTokensOptions: [],
128
137
  dstChainOtherTokensOptions: [],
129
- srcTokenProvided: false,
130
138
  srcToken: undefined,
131
- dstTokenProvided: false,
139
+ dstTokenLocked: false,
140
+ dstChainLocked: false,
141
+ srcTokenLocked: false,
142
+ srcChainLocked: false,
132
143
  dstToken: undefined,
133
144
  srcValue: "",
134
145
  srcValueWei: "",
@@ -164,6 +175,7 @@ const init: SwapContext = {
164
175
  quoteRoute: functionNotImplemented,
165
176
  refreshBalance: functionNotImplemented,
166
177
  findTokenDataByAddressAndChain: functionNotImplemented,
178
+ bridgeTokensDictionary: null,
167
179
  };
168
180
 
169
181
  const swapContext = createContext(init);
@@ -189,22 +201,28 @@ export const SwapProvider = ({
189
201
  const [infiniteApproval, setInfiniteApproval] = useState(
190
202
  init.infiniteApproval,
191
203
  );
192
- const srcTokenProvided = useMemo(
193
- () => !!integrationConfig?.srcTokenAddr,
194
- [integrationConfig?.srcTokenAddr],
204
+ const srcTokenLocked = useMemo(
205
+ () => !!integrationConfig?.srcTokenLocked,
206
+ [integrationConfig?.srcTokenLocked],
207
+ );
208
+ const dstTokenLocked = useMemo(
209
+ () => !!integrationConfig?.dstTokenLocked,
210
+ [integrationConfig?.dstTokenLocked],
195
211
  );
196
- const dstTokenProvided = useMemo(
197
- () => !!integrationConfig?.dstTokenAddr,
198
- [integrationConfig?.dstTokenAddr],
212
+ const srcChainLocked = useMemo(
213
+ () => !!integrationConfig?.srcChainLocked,
214
+ [integrationConfig?.srcChainLocked],
199
215
  );
200
- const srcChainProvided = useMemo(
201
- () => !!integrationConfig?.srcChainId,
202
- [integrationConfig?.srcChainId],
216
+ const dstChainLocked = useMemo(
217
+ () => !!integrationConfig?.dstChainLocked,
218
+ [integrationConfig?.dstChainLocked],
203
219
  );
204
- const dstChainProvided = useMemo(
205
- () => !!integrationConfig?.dstChainId,
206
- [integrationConfig?.dstChainId],
220
+
221
+ const bridgeUI = useMemo(
222
+ () => !!integrationConfig.bridge,
223
+ [integrationConfig.bridge],
207
224
  );
225
+
208
226
  const [slippage, setSlippage] = useState(init.slippage);
209
227
  const [srcChain, setSrcChain] = useState(init.srcChain);
210
228
  const [dstChain, setDstChain] = useState(init.dstChain);
@@ -288,14 +306,66 @@ export const SwapProvider = ({
288
306
  return tempChainToTokenOptionsMap;
289
307
  }, [supportedTokens]);
290
308
 
309
+ const [bridgeTokens, setBridgeTokens] = useState<BridgeToken[]>([]);
310
+ useEffect(() => {
311
+ (async () => {
312
+ const data = await getBridgeTokens();
313
+ setBridgeTokens(data);
314
+ })();
315
+ }, []);
316
+
317
+ const bridgeTokensDictionary = useMemo(() => {
318
+ const result: BridgeTokensDictionary = {};
319
+
320
+ for (const dict of bridgeTokens) {
321
+ const [chainId0, address0] = Object.entries(dict)[0]!;
322
+ const [chainId1, address1] = Object.entries(dict)[1]!;
323
+ if (!result[chainId0]) {
324
+ result[chainId0] = {};
325
+ }
326
+ if (!result[chainId0][address0]) {
327
+ result[chainId0][address0] = {};
328
+ }
329
+ result[chainId0][address0][chainId1] = address1;
330
+ if (!result[chainId1]) {
331
+ result[chainId1] = {};
332
+ }
333
+ if (!result[chainId1][address1]) {
334
+ result[chainId1][address1] = {};
335
+ }
336
+ result[chainId1][address1][chainId0] = address0;
337
+ }
338
+
339
+ if (Object.keys(result).length) {
340
+ return result;
341
+ }
342
+
343
+ return null;
344
+ }, [bridgeTokens]);
345
+
291
346
  const getTokenOptions = useCallback(
292
- (chainId: string, oppositeChainId: string) => {
347
+ (chainId: string | undefined, oppositeChainId: string | undefined) => {
293
348
  if (!chainId || !oppositeChainId) {
294
349
  return [];
295
350
  }
351
+
352
+ if (bridgeTokensDictionary && bridgeUI) {
353
+ if (!chainToTokenOptionsMap[chainId]) {
354
+ return [];
355
+ }
356
+
357
+ return chainToTokenOptionsMap[chainId].filter((tokenData) => {
358
+ const supportedTokensAddresses = Object.keys(
359
+ bridgeTokensDictionary[chainId] || {},
360
+ );
361
+
362
+ return supportedTokensAddresses.includes(tokenData.address);
363
+ });
364
+ }
365
+
296
366
  return chainToTokenOptionsMap[chainId] || [];
297
367
  },
298
- [chainToTokenOptionsMap],
368
+ [bridgeTokensDictionary, bridgeUI, chainToTokenOptionsMap],
299
369
  );
300
370
 
301
371
  const srcTokenBalanceInfo = useMemo(() => {
@@ -665,9 +735,88 @@ export const SwapProvider = ({
665
735
  setDstToken(initDstToken);
666
736
  }, [integrationConfig, supportedChains]);
667
737
 
738
+ useEffect(() => {
739
+ if (!bridgeTokensDictionary) {
740
+ return;
741
+ }
742
+
743
+ const { source, target } = mapToValidPath({
744
+ source: { chain: srcChain, token: srcToken },
745
+ target: { chain: dstChain, token: dstToken },
746
+ type: bridgeUI ? "BRIDGE" : "SWAP",
747
+ supportedChains,
748
+ bridgeTokensDictionary,
749
+ });
750
+
751
+ if (source.chain?.chainId !== srcChain?.chainId) {
752
+ setSrcChain(source.chain);
753
+ }
754
+
755
+ if (target.chain?.chainId !== dstChain?.chainId) {
756
+ setDstChain(target.chain);
757
+ }
758
+
759
+ if (source.token?.address !== srcToken?.address) {
760
+ setSrcToken(source.token);
761
+ }
762
+
763
+ if (target.token?.address !== dstToken?.address) {
764
+ setDstToken(target.token);
765
+ }
766
+ }, [
767
+ srcChain,
768
+ srcToken,
769
+ dstChain,
770
+ dstToken,
771
+ bridgeUI,
772
+ supportedChains,
773
+ bridgeTokensDictionary,
774
+ ]);
775
+
776
+ useEffect(() => {
777
+ if (integrationConfig.onDstChainChange) {
778
+ integrationConfig.onDstChainChange(dstChain?.chainId);
779
+ } else if (window.xPayOnDstChainChange) {
780
+ window.xPayOnDstChainChange(dstChain?.chainId);
781
+ }
782
+ }, [integrationConfig, dstChain]);
783
+
784
+ useEffect(() => {
785
+ const token = dstToken
786
+ ? { address: dstToken.address.toLowerCase(), symbol: dstToken.symbol }
787
+ : undefined;
788
+
789
+ if (integrationConfig.onDstTokenChange) {
790
+ integrationConfig.onDstTokenChange(token);
791
+ } else if (window.xPayOnDstTokenChange) {
792
+ window.xPayOnDstTokenChange(token);
793
+ }
794
+ }, [integrationConfig, dstToken]);
795
+
796
+ useEffect(() => {
797
+ if (integrationConfig.onSrcChainChange) {
798
+ integrationConfig.onSrcChainChange(srcChain?.chainId);
799
+ } else if (window.xPayOnSrcChainChange) {
800
+ window.xPayOnSrcChainChange(srcChain?.chainId);
801
+ }
802
+ }, [integrationConfig, srcChain]);
803
+
804
+ useEffect(() => {
805
+ const token = srcToken
806
+ ? { address: srcToken.address.toLowerCase(), symbol: srcToken.symbol }
807
+ : undefined;
808
+
809
+ if (integrationConfig.onSrcTokenChange) {
810
+ integrationConfig.onSrcTokenChange(token);
811
+ } else if (window.xPayOnSrcTokenChange) {
812
+ window.xPayOnSrcTokenChange(token);
813
+ }
814
+ }, [integrationConfig, srcToken]);
815
+
668
816
  return (
669
817
  <swapContext.Provider
670
818
  value={{
819
+ bridgeUI,
671
820
  integrationConfig,
672
821
  wagmiConfig,
673
822
  supportedChains,
@@ -677,22 +826,18 @@ export const SwapProvider = ({
677
826
  error,
678
827
  setError,
679
828
  feeToken,
680
- srcChainProvided,
681
829
  srcChain,
682
830
  setSrcChain,
683
831
  srcChainTokensOptions,
684
832
  srcChainOtherTokensOptions,
685
833
  dstChainTokensPrices,
686
834
  srcChainTokensPrices,
687
- dstChainProvided,
688
835
  dstChain,
689
836
  setDstChain,
690
837
  dstChainTokensOptions,
691
838
  dstChainOtherTokensOptions,
692
- srcTokenProvided,
693
839
  srcToken,
694
840
  setSrcToken,
695
- dstTokenProvided,
696
841
  dstToken,
697
842
  setDstToken,
698
843
  srcValue,
@@ -723,6 +868,11 @@ export const SwapProvider = ({
723
868
  quoteRoute,
724
869
  refreshBalance,
725
870
  findTokenDataByAddressAndChain,
871
+ srcTokenLocked,
872
+ srcChainLocked,
873
+ dstTokenLocked,
874
+ dstChainLocked,
875
+ bridgeTokensDictionary,
726
876
  }}
727
877
  >
728
878
  {children}
@@ -0,0 +1,7 @@
1
+ export type BridgeTokensDictionary = {
2
+ [chainId0: string]: {
3
+ [tokenAddress0: string]: {
4
+ [chainId1: string]: string; // string is tokenAddress1
5
+ };
6
+ };
7
+ };
@@ -1,5 +1,5 @@
1
- import { Web3Environment } from "./Web3Environment";
2
1
  import { BigNumber } from "ethers";
2
+ import { Web3Environment } from "./Web3Environment";
3
3
 
4
4
  export type Chain = {
5
5
  ecosystem: string;
@@ -17,6 +17,8 @@ export type Chain = {
17
17
  publicRpcUrls: string[];
18
18
  privateRpcUrls?: string[];
19
19
  scanApiURL?: string;
20
+ defaultDstChain?: string;
21
+ disabledForDestination?: string[];
20
22
  };
21
23
 
22
24
  export type Token = {