@xswap-link/sdk 0.9.6 → 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/CHANGELOG.md +12 -0
- package/README.md +0 -18
- package/dist/index.d.mts +13 -8
- package/dist/index.d.ts +13 -8
- package/dist/index.global.js +63 -64
- package/dist/index.js +9 -10
- package/dist/index.mjs +9 -10
- package/localTheme.ts +3 -0
- package/package.json +1 -1
- package/src/assets/icons/ChainlinkIcon.tsx +21 -0
- package/src/assets/icons/index.ts +1 -1
- package/src/components/PoweredBy/index.tsx +3 -18
- package/src/components/Swap/SwapView/SwapPanel/ChainPanel/ChainPicker/index.tsx +13 -97
- package/src/components/Swap/SwapView/SwapPanel/ChainPanel/index.tsx +3 -82
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/TokenItem/index.tsx +8 -11
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +0 -1
- 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 +454 -300
- package/src/models/TokenData.ts +2 -0
- package/src/models/index.ts +0 -1
- package/src/models/payloads/ModalIntegrationPayload.ts +4 -0
- package/src/models/payloads/WidgetIntegrationPayload.ts +3 -0
- package/src/services/api.ts +8 -4
- package/src/utils/index.ts +30 -0
- package/src/utils/validation.ts +19 -25
- package/tailwind.config.js +2 -0
- package/test/context/SwapProvider.test.tsx +220 -4
- package/test/fixtures/supportedChains.mock.ts +7180 -22795
- package/src/assets/icons/ChainlinkCCIPIcon.tsx +0 -23
- package/src/models/XSwapConfig.ts +0 -4
- package/xswap.config.ts +0 -20
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
Token,
|
|
14
14
|
TokenOption,
|
|
15
15
|
TokenPrices,
|
|
16
|
-
Transaction,
|
|
17
16
|
Web3Environment,
|
|
18
17
|
} from "@src/models";
|
|
19
18
|
import {
|
|
@@ -27,11 +26,13 @@ import {
|
|
|
27
26
|
getBalanceOf,
|
|
28
27
|
humanReadableToWei,
|
|
29
28
|
isServer,
|
|
29
|
+
resolveTokenAddress,
|
|
30
30
|
safeBigNumberFrom,
|
|
31
|
+
sortChains,
|
|
31
32
|
weiToHumanReadable,
|
|
32
33
|
} from "@src/utils";
|
|
33
34
|
import BigNumberJS from "bignumber.js";
|
|
34
|
-
import { BigNumber, constants
|
|
35
|
+
import { BigNumber, constants } from "ethers";
|
|
35
36
|
import {
|
|
36
37
|
createContext,
|
|
37
38
|
Dispatch,
|
|
@@ -45,10 +46,14 @@ import {
|
|
|
45
46
|
useState,
|
|
46
47
|
} from "react";
|
|
47
48
|
import { Config, useAccount } from "wagmi";
|
|
48
|
-
import { useHistory } from "./HistoryProvider";
|
|
49
49
|
import { mapToValidPath } from "@src/utils/validation";
|
|
50
50
|
|
|
51
51
|
type Tab = "Swap" | "History" | "Settings";
|
|
52
|
+
|
|
53
|
+
type ChainOption = Chain & {
|
|
54
|
+
disabled: boolean;
|
|
55
|
+
};
|
|
56
|
+
|
|
52
57
|
type SwapContext = {
|
|
53
58
|
bridgeUI: boolean;
|
|
54
59
|
integrationConfig: Partial<TxConfigFormPayload>;
|
|
@@ -64,6 +69,8 @@ type SwapContext = {
|
|
|
64
69
|
setSrcChain: Dispatch<SetStateAction<Chain | undefined>>;
|
|
65
70
|
srcChainTokensOptions: TokenOption[];
|
|
66
71
|
srcChainOtherTokensOptions: TokenOption[];
|
|
72
|
+
srcChainOptions: ChainOption[];
|
|
73
|
+
dstChainOptions: ChainOption[];
|
|
67
74
|
tokenPrices: TokenPrices;
|
|
68
75
|
srcToken: Token | undefined;
|
|
69
76
|
setSrcToken: Dispatch<SetStateAction<Token | undefined>>;
|
|
@@ -85,8 +92,6 @@ type SwapContext = {
|
|
|
85
92
|
dstValueWei: string;
|
|
86
93
|
dstValueUsd: string | null;
|
|
87
94
|
dstValueMin: string;
|
|
88
|
-
lastTx: Transaction | undefined;
|
|
89
|
-
setLastTxHash: Dispatch<SetStateAction<string>>;
|
|
90
95
|
expressDelivery: boolean;
|
|
91
96
|
setExpressDelivery: Dispatch<SetStateAction<boolean>>;
|
|
92
97
|
infiniteApproval: boolean;
|
|
@@ -101,6 +106,7 @@ type SwapContext = {
|
|
|
101
106
|
srcTokenBalanceInfo: string;
|
|
102
107
|
isFetchingRoute: boolean;
|
|
103
108
|
isFetchingBalance: boolean;
|
|
109
|
+
isFetchingBalances: boolean;
|
|
104
110
|
isExpressDeliveryActive: boolean;
|
|
105
111
|
quoteRoute: () => Promise<void>;
|
|
106
112
|
refreshBalance: () => void;
|
|
@@ -146,7 +152,6 @@ const init: SwapContext = {
|
|
|
146
152
|
dstValueWei: "",
|
|
147
153
|
dstValueUsd: null,
|
|
148
154
|
dstValueMin: "",
|
|
149
|
-
lastTx: undefined,
|
|
150
155
|
expressDelivery: true,
|
|
151
156
|
infiniteApproval: true,
|
|
152
157
|
slippage: "1.5",
|
|
@@ -155,6 +160,7 @@ const init: SwapContext = {
|
|
|
155
160
|
srcTokenBalanceWei: "",
|
|
156
161
|
srcTokenBalanceInfo: "-",
|
|
157
162
|
isFetchingBalance: false,
|
|
163
|
+
isFetchingBalances: false,
|
|
158
164
|
isFetchingRoute: false,
|
|
159
165
|
isExpressDeliveryActive: true,
|
|
160
166
|
setTab: functionNotImplemented,
|
|
@@ -164,7 +170,6 @@ const init: SwapContext = {
|
|
|
164
170
|
setSrcToken: functionNotImplemented,
|
|
165
171
|
setDstToken: functionNotImplemented,
|
|
166
172
|
setSrcValue: functionNotImplemented,
|
|
167
|
-
setLastTxHash: functionNotImplemented,
|
|
168
173
|
setExpressDelivery: functionNotImplemented,
|
|
169
174
|
setInfiniteApproval: functionNotImplemented,
|
|
170
175
|
setSlippage: functionNotImplemented,
|
|
@@ -176,6 +181,8 @@ const init: SwapContext = {
|
|
|
176
181
|
bridgeTokensDictionary: null,
|
|
177
182
|
onConnectWallet: functionNotImplemented,
|
|
178
183
|
hasOnConnectWalletCallback: false,
|
|
184
|
+
srcChainOptions: [],
|
|
185
|
+
dstChainOptions: [],
|
|
179
186
|
};
|
|
180
187
|
|
|
181
188
|
const swapContext = createContext(init);
|
|
@@ -194,61 +201,47 @@ export const SwapProvider = ({
|
|
|
194
201
|
wagmiConfig,
|
|
195
202
|
overlay,
|
|
196
203
|
}: Props) => {
|
|
204
|
+
// =============================================================================
|
|
205
|
+
// Hooks
|
|
206
|
+
// =============================================================================
|
|
197
207
|
const { address, chainId: walletChainId } = useAccount();
|
|
208
|
+
|
|
209
|
+
// =============================================================================
|
|
210
|
+
// State
|
|
211
|
+
// =============================================================================
|
|
212
|
+
const [isInitialStateSet, setIsInitialStateSet] = useState(false);
|
|
213
|
+
// Settings
|
|
198
214
|
const [tab, setTab] = useState(init.tab);
|
|
199
215
|
const [error, setError] = useState(init.error);
|
|
200
216
|
const [expressDelivery, setExpressDelivery] = useState(init.expressDelivery);
|
|
217
|
+
const [slippage, setSlippage] = useState(init.slippage);
|
|
201
218
|
const [infiniteApproval, setInfiniteApproval] = useState(
|
|
202
219
|
init.infiniteApproval,
|
|
203
220
|
);
|
|
204
|
-
|
|
205
|
-
() => !!integrationConfig?.srcTokenLocked,
|
|
206
|
-
[integrationConfig?.srcTokenLocked],
|
|
207
|
-
);
|
|
208
|
-
const dstTokenLocked = useMemo(
|
|
209
|
-
() => !!integrationConfig?.dstTokenLocked,
|
|
210
|
-
[integrationConfig?.dstTokenLocked],
|
|
211
|
-
);
|
|
212
|
-
const srcChainLocked = useMemo(
|
|
213
|
-
() => !!integrationConfig?.srcChainLocked,
|
|
214
|
-
[integrationConfig?.srcChainLocked],
|
|
215
|
-
);
|
|
216
|
-
const dstChainLocked = useMemo(
|
|
217
|
-
() => !!integrationConfig?.dstChainLocked,
|
|
218
|
-
[integrationConfig?.dstChainLocked],
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
const bridgeUI = useMemo(
|
|
222
|
-
() => !!integrationConfig.bridge,
|
|
223
|
-
[integrationConfig.bridge],
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
const [slippage, setSlippage] = useState(init.slippage);
|
|
221
|
+
// Chains
|
|
227
222
|
const [srcChain, setSrcChain] = useState(init.srcChain);
|
|
228
223
|
const [dstChain, setDstChain] = useState(init.dstChain);
|
|
229
224
|
const [feeToken, setFeeToken] = useState(init.feeToken);
|
|
225
|
+
// Tokens
|
|
230
226
|
const [srcToken, setSrcToken] = useState(init.srcToken);
|
|
231
227
|
const [dstToken, setDstToken] = useState(init.dstToken);
|
|
228
|
+
const [tokenPrices, setTokenPrices] = useState(init.tokenPrices);
|
|
229
|
+
const [tokenBalances, setTokenBalances] = useState<
|
|
230
|
+
Record<string, Record<string, string>>
|
|
231
|
+
>({});
|
|
232
|
+
const [bridgeTokens, setBridgeTokens] = useState<BridgeToken[]>([]);
|
|
233
|
+
// Values
|
|
232
234
|
const [srcValue, setSrcValue] = useState(init.srcValue);
|
|
233
|
-
|
|
234
|
-
const [isFetchingBalance, setIsFetchingBalance] = useState(
|
|
235
|
-
init.isFetchingBalance,
|
|
236
|
-
);
|
|
237
235
|
const [srcTokenBalanceWei, setSrcTokenBalanceWei] = useState(
|
|
238
236
|
init.srcTokenBalanceWei,
|
|
239
237
|
);
|
|
240
|
-
|
|
241
|
-
const [route, setRoute] = useState(init.route);
|
|
242
|
-
|
|
243
|
-
const [lastTxHash, setLastTxHash] = useState("");
|
|
244
|
-
|
|
238
|
+
// Chains and Token options for pickers
|
|
245
239
|
const [srcChainTokensOptions, setSrcChainTokensOptions] = useState(
|
|
246
240
|
init.srcChainTokensOptions,
|
|
247
241
|
);
|
|
248
242
|
const [dstChainTokensOptions, setDstChainTokensOptions] = useState(
|
|
249
243
|
init.dstChainTokensOptions,
|
|
250
244
|
);
|
|
251
|
-
const [tokenPrices, setTokenPrices] = useState(init.tokenPrices);
|
|
252
245
|
const [srcChainOtherTokensOptions, setSrcChainOtherTokensOptions] = useState(
|
|
253
246
|
init.srcChainOtherTokensOptions,
|
|
254
247
|
);
|
|
@@ -256,24 +249,62 @@ export const SwapProvider = ({
|
|
|
256
249
|
init.dstChainOtherTokensOptions,
|
|
257
250
|
);
|
|
258
251
|
|
|
252
|
+
const [srcChainOptions, setSrcChainOptions] = useState<ChainOption[]>([]);
|
|
253
|
+
const [dstChainOptions, setDstChainOptions] = useState<ChainOption[]>([]);
|
|
254
|
+
// Loaders
|
|
255
|
+
const [isFetchingBalance, setIsFetchingBalance] = useState(
|
|
256
|
+
init.isFetchingBalance,
|
|
257
|
+
);
|
|
258
|
+
const [isFetchingBalances, setIsFetchingBalances] = useState(
|
|
259
|
+
init.isFetchingBalances,
|
|
260
|
+
);
|
|
261
|
+
const [isFetchingRoute, setFetchingRoute] = useState(init.isFetchingRoute);
|
|
262
|
+
// Route and tx data
|
|
263
|
+
const [route, setRoute] = useState(init.route);
|
|
264
|
+
|
|
265
|
+
// =============================================================================
|
|
266
|
+
// Refs
|
|
267
|
+
// =============================================================================
|
|
268
|
+
|
|
259
269
|
const routeAbortController = useRef<AbortController | null>(null);
|
|
260
270
|
const refreshSelectedTokenBalanceNonce = useRef(crypto.randomUUID());
|
|
261
271
|
|
|
262
|
-
|
|
272
|
+
// =============================================================================
|
|
273
|
+
// Memoized Values
|
|
274
|
+
// =============================================================================
|
|
263
275
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
276
|
+
// useMemo: determines if the source token is locked based on integration config.
|
|
277
|
+
const srcTokenLocked = useMemo(
|
|
278
|
+
() => !!integrationConfig?.srcTokenLocked,
|
|
279
|
+
[integrationConfig?.srcTokenLocked],
|
|
280
|
+
);
|
|
281
|
+
// useMemo: determines if the destination token is locked based on integration config.
|
|
282
|
+
const dstTokenLocked = useMemo(
|
|
283
|
+
() => !!integrationConfig?.dstTokenLocked,
|
|
284
|
+
[integrationConfig?.dstTokenLocked],
|
|
285
|
+
);
|
|
286
|
+
// useMemo: determines if the destination chain is locked from integration config.
|
|
287
|
+
const dstChainLocked = useMemo(
|
|
288
|
+
() => !!integrationConfig?.dstChainLocked,
|
|
289
|
+
[integrationConfig?.dstChainLocked],
|
|
290
|
+
);
|
|
291
|
+
// useMemo: determines if the source chain is locked from integration config.
|
|
292
|
+
const srcChainLocked = useMemo(
|
|
293
|
+
() => !!integrationConfig?.srcChainLocked,
|
|
294
|
+
[integrationConfig?.srcChainLocked],
|
|
295
|
+
);
|
|
296
|
+
// useMemo: sets a flag to indicate if the bridge UI is enabled based on integration config.
|
|
297
|
+
const bridgeUI = useMemo(
|
|
298
|
+
() => !!integrationConfig.bridge,
|
|
299
|
+
[integrationConfig.bridge],
|
|
300
|
+
);
|
|
267
301
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
setTokenPrices(prices);
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
}, [supportedChains]);
|
|
302
|
+
// useMemo: determines if a wallet connection callback exists.
|
|
303
|
+
const hasOnConnectWalletCallback = useMemo(() => {
|
|
304
|
+
return !!(integrationConfig.onConnectWallet || window.xPayOnConnectWallet);
|
|
305
|
+
}, [integrationConfig.onConnectWallet]);
|
|
276
306
|
|
|
307
|
+
// useMemo: merges supported chain tokens with custom tokens from localStorage.
|
|
277
308
|
const supportedTokens: Record<string, Record<string, Token>> = useMemo(() => {
|
|
278
309
|
const customTokens = !isServer
|
|
279
310
|
? JSON.parse(localStorage.getItem("custom-tokens") || "{}")
|
|
@@ -287,14 +318,14 @@ export const SwapProvider = ({
|
|
|
287
318
|
supportedChains.forEach((chain) => {
|
|
288
319
|
chainIdToTokenAddressToData[chain.chainId] = {};
|
|
289
320
|
chain.tokens.forEach((token) => {
|
|
290
|
-
|
|
291
|
-
chainIdToTokenAddressToData[chain.chainId][token.address] = token;
|
|
321
|
+
chainIdToTokenAddressToData[chain.chainId]![token.address] = token;
|
|
292
322
|
});
|
|
293
323
|
});
|
|
294
324
|
return deepMergeObjects(chainIdToTokenAddressToData, customTokens);
|
|
295
325
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
296
326
|
}, [supportedChains]);
|
|
297
327
|
|
|
328
|
+
// useMemo: creates a mapping from each chain ID to available token options sorted by token priority.
|
|
298
329
|
const chainToTokenOptionsMap = useMemo(() => {
|
|
299
330
|
const tempChainToTokenOptionsMap: Record<string, TokenOption[]> = {};
|
|
300
331
|
Object.entries(supportedTokens).forEach(([chainId, chainData]) => {
|
|
@@ -313,108 +344,43 @@ export const SwapProvider = ({
|
|
|
313
344
|
return tempChainToTokenOptionsMap;
|
|
314
345
|
}, [supportedTokens]);
|
|
315
346
|
|
|
316
|
-
|
|
317
|
-
useEffect(() => {
|
|
318
|
-
(async () => {
|
|
319
|
-
const data = await getBridgeTokens();
|
|
320
|
-
setBridgeTokens(data);
|
|
321
|
-
})();
|
|
322
|
-
}, []);
|
|
323
|
-
|
|
347
|
+
// useMemo: constructs a dictionary mapping tokens across chains for bridging based on bridgeTokens data.
|
|
324
348
|
const bridgeTokensDictionary = useMemo(() => {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
349
|
+
// If there are no bridge tokens, return null directly.
|
|
350
|
+
if (!bridgeTokens || bridgeTokens.length === 0) return null;
|
|
351
|
+
|
|
352
|
+
const dictionary: BridgeTokensDictionary = {};
|
|
353
|
+
// Helper to add a mapping from one token to its counterpart.
|
|
354
|
+
const addMapping = (
|
|
355
|
+
chainIdFrom: string,
|
|
356
|
+
tokenAddressFrom: string,
|
|
357
|
+
chainIdTo: string,
|
|
358
|
+
tokenAddressTo: string,
|
|
359
|
+
) => {
|
|
360
|
+
if (!dictionary[chainIdFrom]) {
|
|
361
|
+
dictionary[chainIdFrom] = {};
|
|
335
362
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
result[chainId1] = {};
|
|
363
|
+
if (!dictionary[chainIdFrom][tokenAddressFrom]) {
|
|
364
|
+
dictionary[chainIdFrom][tokenAddressFrom] = {};
|
|
339
365
|
}
|
|
340
|
-
|
|
341
|
-
|
|
366
|
+
dictionary[chainIdFrom][tokenAddressFrom][chainIdTo] = tokenAddressTo;
|
|
367
|
+
};
|
|
368
|
+
// Process each token pair from bridgeTokens.
|
|
369
|
+
for (const tokenPair of bridgeTokens) {
|
|
370
|
+
const entries = Object.entries(tokenPair);
|
|
371
|
+
if (entries.length !== 2) {
|
|
372
|
+
// Skip invalid token pairs.
|
|
373
|
+
continue;
|
|
342
374
|
}
|
|
343
|
-
|
|
344
|
-
|
|
375
|
+
const [chainId0, address0] = entries[0]!;
|
|
376
|
+
const [chainId1, address1] = entries[1]!;
|
|
345
377
|
|
|
346
|
-
|
|
347
|
-
|
|
378
|
+
addMapping(chainId0, address0, chainId1, address1);
|
|
379
|
+
addMapping(chainId1, address1, chainId0, address0);
|
|
348
380
|
}
|
|
349
|
-
|
|
350
|
-
return null;
|
|
381
|
+
return Object.keys(dictionary).length > 0 ? dictionary : null;
|
|
351
382
|
}, [bridgeTokens]);
|
|
352
|
-
|
|
353
|
-
const getTokenOptions = useCallback(
|
|
354
|
-
(chainId: string | undefined) => {
|
|
355
|
-
if (!chainId) {
|
|
356
|
-
return [];
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (bridgeTokensDictionary && bridgeUI) {
|
|
360
|
-
if (!chainToTokenOptionsMap[chainId]) {
|
|
361
|
-
return [];
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
return chainToTokenOptionsMap[chainId].filter((tokenData) => {
|
|
365
|
-
const supportedTokensAddresses = Object.keys(
|
|
366
|
-
bridgeTokensDictionary[chainId] || {},
|
|
367
|
-
);
|
|
368
|
-
|
|
369
|
-
return supportedTokensAddresses.includes(tokenData.address);
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return chainToTokenOptionsMap[chainId] || [];
|
|
374
|
-
},
|
|
375
|
-
[bridgeTokensDictionary, bridgeUI, chainToTokenOptionsMap],
|
|
376
|
-
);
|
|
377
|
-
|
|
378
|
-
const srcTokenBalanceInfo = useMemo(() => {
|
|
379
|
-
if (!srcToken || !srcTokenBalanceWei || !address) {
|
|
380
|
-
return "";
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (safeBigNumberFrom(srcTokenBalanceWei).eq(0)) {
|
|
384
|
-
return "0";
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const fullHumanReadableBalance = weiToHumanReadable({
|
|
388
|
-
amount: srcTokenBalanceWei,
|
|
389
|
-
decimals: srcToken.decimals,
|
|
390
|
-
precisionFractionalPlaces: srcToken.decimals,
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
if (new BigNumberJS(fullHumanReadableBalance).lt("0.00001")) {
|
|
394
|
-
return "<0.00001";
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Shortened to cut non-significant decimals
|
|
398
|
-
return weiToHumanReadable({
|
|
399
|
-
amount: srcTokenBalanceWei,
|
|
400
|
-
decimals: srcToken.decimals,
|
|
401
|
-
precisionFractionalPlaces: 5,
|
|
402
|
-
});
|
|
403
|
-
}, [srcToken, srcTokenBalanceWei]);
|
|
404
|
-
|
|
405
|
-
const lastTx = useMemo(() => {
|
|
406
|
-
return history?.find(({ hash }) => hash === lastTxHash);
|
|
407
|
-
}, [history, lastTxHash]);
|
|
408
|
-
|
|
409
|
-
const srcValueWei = useMemo(
|
|
410
|
-
() =>
|
|
411
|
-
humanReadableToWei({
|
|
412
|
-
amount: srcValue,
|
|
413
|
-
decimals: srcToken?.decimals || 18,
|
|
414
|
-
}),
|
|
415
|
-
[srcToken?.decimals, srcValue],
|
|
416
|
-
);
|
|
417
|
-
|
|
383
|
+
// useMemo: calculates the USD value for the source token amount using the current token prices.
|
|
418
384
|
const srcValueUsd = useMemo(() => {
|
|
419
385
|
if (
|
|
420
386
|
srcToken &&
|
|
@@ -430,6 +396,16 @@ export const SwapProvider = ({
|
|
|
430
396
|
}
|
|
431
397
|
return null;
|
|
432
398
|
}, [tokenPrices, srcToken, srcValue, srcChain]);
|
|
399
|
+
// useMemo: converts the source token value from human-readable format to Wei.
|
|
400
|
+
const srcValueWei = useMemo(
|
|
401
|
+
() =>
|
|
402
|
+
humanReadableToWei({
|
|
403
|
+
amount: srcValue,
|
|
404
|
+
decimals: srcToken?.decimals || 18,
|
|
405
|
+
}),
|
|
406
|
+
[srcToken?.decimals, srcValue],
|
|
407
|
+
);
|
|
408
|
+
// useMemo: converts the estimated output amount from the route to a human-readable destination token value.
|
|
433
409
|
const dstValue = useMemo(
|
|
434
410
|
() =>
|
|
435
411
|
weiToHumanReadable({
|
|
@@ -440,6 +416,7 @@ export const SwapProvider = ({
|
|
|
440
416
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
441
417
|
[route],
|
|
442
418
|
);
|
|
419
|
+
// useMemo: converts the human-readable destination value back to Wei using the destination token's decimals.
|
|
443
420
|
const dstValueWei = useMemo(
|
|
444
421
|
() =>
|
|
445
422
|
humanReadableToWei({
|
|
@@ -448,6 +425,7 @@ export const SwapProvider = ({
|
|
|
448
425
|
}),
|
|
449
426
|
[dstToken?.decimals, dstValue],
|
|
450
427
|
);
|
|
428
|
+
// useMemo: calculates the USD value for the destination token amount using the token prices.
|
|
451
429
|
const dstValueUsd = useMemo(() => {
|
|
452
430
|
if (
|
|
453
431
|
dstToken &&
|
|
@@ -463,6 +441,7 @@ export const SwapProvider = ({
|
|
|
463
441
|
}
|
|
464
442
|
return null;
|
|
465
443
|
}, [tokenPrices, dstToken, dstValue, dstChain]);
|
|
444
|
+
// useMemo: computes the minimum destination token value in human-readable format from route data.
|
|
466
445
|
const dstValueMin = useMemo(
|
|
467
446
|
() =>
|
|
468
447
|
weiToHumanReadable({
|
|
@@ -473,7 +452,7 @@ export const SwapProvider = ({
|
|
|
473
452
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
474
453
|
[route],
|
|
475
454
|
);
|
|
476
|
-
|
|
455
|
+
// useMemo: checks if express delivery is enabled and if the route includes a non-zero express delivery fee.
|
|
477
456
|
const isExpressDeliveryActive = useMemo(
|
|
478
457
|
() =>
|
|
479
458
|
expressDelivery &&
|
|
@@ -481,12 +460,72 @@ export const SwapProvider = ({
|
|
|
481
460
|
BigNumber.from(route.xSwapFees?.expressDeliveryFee || "0").gt("0"),
|
|
482
461
|
[expressDelivery, route],
|
|
483
462
|
);
|
|
484
|
-
|
|
463
|
+
// useMemo: indicates whether asynchronous data is loaded based on the presence of supported chains.
|
|
485
464
|
const isAsyncDataLoaded = useMemo(
|
|
486
|
-
() => supportedChains.length > 0,
|
|
487
|
-
[supportedChains],
|
|
465
|
+
() => supportedChains.length > 0 && bridgeTokens.length > 0,
|
|
466
|
+
[supportedChains, bridgeTokens],
|
|
488
467
|
);
|
|
468
|
+
// useMemo: computes a formatted balance string for the source token based on its balance in Wei.
|
|
469
|
+
const srcTokenBalanceInfo = useMemo(() => {
|
|
470
|
+
if (!srcToken || !srcTokenBalanceWei || !address) {
|
|
471
|
+
return "";
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (safeBigNumberFrom(srcTokenBalanceWei).eq(0)) {
|
|
475
|
+
return "0";
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const fullHumanReadableBalance = weiToHumanReadable({
|
|
479
|
+
amount: srcTokenBalanceWei,
|
|
480
|
+
decimals: srcToken.decimals,
|
|
481
|
+
precisionFractionalPlaces: srcToken.decimals,
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
if (new BigNumberJS(fullHumanReadableBalance).lt("0.00001")) {
|
|
485
|
+
return "<0.00001";
|
|
486
|
+
}
|
|
489
487
|
|
|
488
|
+
// Shortened to cut non-significant decimals
|
|
489
|
+
return weiToHumanReadable({
|
|
490
|
+
amount: srcTokenBalanceWei,
|
|
491
|
+
decimals: srcToken.decimals,
|
|
492
|
+
precisionFractionalPlaces: 5,
|
|
493
|
+
});
|
|
494
|
+
}, [srcToken, srcTokenBalanceWei]);
|
|
495
|
+
|
|
496
|
+
// =============================================================================
|
|
497
|
+
// Callbacks
|
|
498
|
+
// =============================================================================
|
|
499
|
+
|
|
500
|
+
// returns available token options for a given chain, filtering by bridge support if bridge UI is enabled.
|
|
501
|
+
// NOTE: This function does not handle any of the filtering
|
|
502
|
+
const getTokenOptions = useCallback(
|
|
503
|
+
(chainId: string | undefined) => {
|
|
504
|
+
if (!chainId) {
|
|
505
|
+
return [];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// swap
|
|
509
|
+
if (!bridgeUI) {
|
|
510
|
+
return chainToTokenOptionsMap[chainId] || [];
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// bridge
|
|
514
|
+
if (!chainToTokenOptionsMap[chainId] || !bridgeTokensDictionary) {
|
|
515
|
+
return [];
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const supportedTokensAddresses = Object.keys(
|
|
519
|
+
bridgeTokensDictionary[chainId] || {},
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
return chainToTokenOptionsMap[chainId].filter((tokenData) =>
|
|
523
|
+
supportedTokensAddresses.includes(tokenData.address),
|
|
524
|
+
);
|
|
525
|
+
},
|
|
526
|
+
[bridgeTokensDictionary, bridgeUI, chainToTokenOptionsMap],
|
|
527
|
+
);
|
|
528
|
+
// fetches and sets the route quote using the current input parameters, aborting any previous request.
|
|
490
529
|
const quoteRoute = useCallback(async () => {
|
|
491
530
|
if (
|
|
492
531
|
!srcChain ||
|
|
@@ -550,59 +589,139 @@ export const SwapProvider = ({
|
|
|
550
589
|
srcToken,
|
|
551
590
|
srcValueWei,
|
|
552
591
|
]);
|
|
592
|
+
// refreshes and updates the balance of the source token using its RPC URL.
|
|
593
|
+
const refreshBalance = useCallback(async () => {
|
|
594
|
+
if (
|
|
595
|
+
!address ||
|
|
596
|
+
!srcChain?.publicRpcUrls[0] ||
|
|
597
|
+
!srcToken ||
|
|
598
|
+
!srcToken?.address
|
|
599
|
+
) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const nonce = crypto.randomUUID();
|
|
604
|
+
refreshSelectedTokenBalanceNonce.current = nonce;
|
|
605
|
+
|
|
606
|
+
setIsFetchingBalance(true);
|
|
607
|
+
|
|
608
|
+
const balance = await getBalanceOf(
|
|
609
|
+
address,
|
|
610
|
+
srcToken.address,
|
|
611
|
+
srcChain?.publicRpcUrls[0],
|
|
612
|
+
);
|
|
553
613
|
|
|
614
|
+
if (nonce !== refreshSelectedTokenBalanceNonce.current) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
setSrcTokenBalanceWei(
|
|
619
|
+
humanReadableToWei({
|
|
620
|
+
amount: balance,
|
|
621
|
+
decimals: srcToken.decimals,
|
|
622
|
+
}),
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
setIsFetchingBalance(false);
|
|
626
|
+
}, [address, srcChain, srcToken]);
|
|
627
|
+
// retrieves token details for a given token address and chain from supported tokens mapping. (including imported tokens)
|
|
628
|
+
const findTokenDataByAddressAndChain = useCallback(
|
|
629
|
+
(tokenAddress: string, chainId: string): Token | undefined => {
|
|
630
|
+
return supportedTokens[chainId]?.[tokenAddress];
|
|
631
|
+
},
|
|
632
|
+
[supportedTokens],
|
|
633
|
+
);
|
|
634
|
+
// triggers the wallet connection process.
|
|
635
|
+
const onConnectWallet = useCallback(() => {
|
|
636
|
+
if (integrationConfig.onConnectWallet) {
|
|
637
|
+
integrationConfig.onConnectWallet();
|
|
638
|
+
} else if (window.xPayOnConnectWallet) {
|
|
639
|
+
window.xPayOnConnectWallet();
|
|
640
|
+
}
|
|
641
|
+
}, [integrationConfig]);
|
|
642
|
+
|
|
643
|
+
// =============================================================================
|
|
644
|
+
// Effects
|
|
645
|
+
// =============================================================================
|
|
646
|
+
|
|
647
|
+
// fetches token prices for supported chains once they are loaded.
|
|
648
|
+
useEffect(() => {
|
|
649
|
+
if (supportedChains.length > 0) {
|
|
650
|
+
const chainIds = supportedChains.map((chain) => chain.chainId);
|
|
651
|
+
getPrices({ chainId: chainIds }).then((prices) => {
|
|
652
|
+
setTokenPrices(prices);
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
}, [supportedChains]);
|
|
656
|
+
// fetches and sets bridge tokens on component mount.
|
|
657
|
+
useEffect(() => {
|
|
658
|
+
(async () => {
|
|
659
|
+
const data = await getBridgeTokens();
|
|
660
|
+
setBridgeTokens(data);
|
|
661
|
+
})();
|
|
662
|
+
}, []);
|
|
663
|
+
// recalculates the route quote when its dependency changes.
|
|
554
664
|
useEffect(() => {
|
|
555
665
|
quoteRoute();
|
|
556
666
|
}, [quoteRoute]);
|
|
557
|
-
|
|
667
|
+
// calls refreshBalance to update source token balance on mount or when refreshBalance changes.
|
|
668
|
+
useEffect(() => {
|
|
669
|
+
refreshBalance();
|
|
670
|
+
}, [refreshBalance]);
|
|
671
|
+
// fetches user all token balances when a wallet address is available and supported chains are loaded.
|
|
558
672
|
useEffect(() => {
|
|
559
673
|
if (address && supportedChains.length > 0) {
|
|
674
|
+
setIsFetchingBalances(true);
|
|
560
675
|
getBalances({ walletAddress: address }).then((balances) => {
|
|
561
676
|
setTokenBalances(balances);
|
|
677
|
+
setIsFetchingBalances(false);
|
|
562
678
|
});
|
|
563
679
|
}
|
|
564
680
|
}, [address, supportedChains.length]);
|
|
681
|
+
// sets the fee token from the source chain's tokens by selecting the native token (AddressZero).
|
|
682
|
+
useEffect(() => {
|
|
683
|
+
setFeeToken(
|
|
684
|
+
srcChain?.tokens.find(
|
|
685
|
+
({ address }) =>
|
|
686
|
+
address.toLowerCase() === constants.AddressZero.toLowerCase(),
|
|
687
|
+
),
|
|
688
|
+
);
|
|
689
|
+
}, [srcChain]);
|
|
565
690
|
|
|
691
|
+
// =============================================================================
|
|
692
|
+
// useEffects responsible for providing token and chain options
|
|
693
|
+
// =============================================================================
|
|
694
|
+
|
|
695
|
+
// updates token options for source and destination chains and other networks, incorporating balance info.
|
|
566
696
|
useEffect(() => {
|
|
567
697
|
(async () => {
|
|
568
|
-
|
|
698
|
+
const isSingleChainSelected = dstChain?.chainId === srcChain?.chainId;
|
|
699
|
+
// source chain options
|
|
569
700
|
const srcTokenOptions: TokenOption[] = getTokenOptions(
|
|
570
701
|
srcChain?.chainId,
|
|
571
702
|
).filter(({ symbol }) =>
|
|
572
|
-
|
|
573
|
-
? symbol !== dstToken?.symbol
|
|
574
|
-
: true,
|
|
703
|
+
isSingleChainSelected ? symbol !== dstToken?.symbol : true,
|
|
575
704
|
);
|
|
576
705
|
setSrcChainTokensOptions(srcTokenOptions);
|
|
577
|
-
|
|
578
|
-
// destination chain
|
|
706
|
+
// destination chain options
|
|
579
707
|
const dstTokenOptions: TokenOption[] = getTokenOptions(
|
|
580
708
|
dstChain?.chainId,
|
|
581
709
|
).filter(({ symbol }) =>
|
|
582
|
-
|
|
583
|
-
? symbol !== srcToken?.symbol
|
|
584
|
-
: true,
|
|
710
|
+
isSingleChainSelected ? symbol !== srcToken?.symbol : true,
|
|
585
711
|
);
|
|
586
712
|
setDstChainTokensOptions(dstTokenOptions);
|
|
587
|
-
|
|
588
|
-
// other source networks
|
|
713
|
+
// token options from other source networks
|
|
589
714
|
const srcOtherTokenOptions: TokenOption[] = [];
|
|
590
715
|
const srcOtherChains = supportedChains.filter(
|
|
591
|
-
({ chainId, web3Environment, swapSupported, bridgeSupported }) =>
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
if (bridgeUI) {
|
|
600
|
-
return bridgeSupported;
|
|
601
|
-
} else {
|
|
602
|
-
return swapSupported;
|
|
603
|
-
}
|
|
604
|
-
},
|
|
716
|
+
({ chainId, web3Environment, swapSupported, bridgeSupported }) =>
|
|
717
|
+
// same chain
|
|
718
|
+
chainId !== srcChain?.chainId &&
|
|
719
|
+
// not mainnet
|
|
720
|
+
web3Environment === Web3Environment.MAINNET &&
|
|
721
|
+
// bridgeUI and supported for bridge or !bridgeUI and supported for swap
|
|
722
|
+
((bridgeUI && bridgeSupported) || (!bridgeUI && swapSupported)),
|
|
605
723
|
);
|
|
724
|
+
// create token options from other source networks and filter out the destination token
|
|
606
725
|
for (const { chainId } of srcOtherChains) {
|
|
607
726
|
srcOtherTokenOptions.push(
|
|
608
727
|
...getTokenOptions(chainId).filter(({ symbol }) =>
|
|
@@ -611,15 +730,22 @@ export const SwapProvider = ({
|
|
|
611
730
|
);
|
|
612
731
|
}
|
|
613
732
|
setSrcChainOtherTokensOptions(srcOtherTokenOptions);
|
|
614
|
-
|
|
615
|
-
// other destination networks
|
|
733
|
+
// token options from other destination networks
|
|
616
734
|
const dstOtherTokenOptions: TokenOption[] = [];
|
|
617
735
|
const dstOtherChains = supportedChains.filter(
|
|
618
|
-
({ chainId, web3Environment, swapSupported }) =>
|
|
736
|
+
({ chainId, web3Environment, swapSupported, bridgeSupported }) =>
|
|
619
737
|
chainId !== dstChain?.chainId &&
|
|
738
|
+
// not mainnet
|
|
620
739
|
web3Environment === Web3Environment.MAINNET &&
|
|
621
|
-
|
|
740
|
+
// bridgeUI and supported for bridge as destination or !bridgeUI and supported for swap as destination
|
|
741
|
+
((bridgeUI &&
|
|
742
|
+
bridgeSupported &&
|
|
743
|
+
srcChain?.supportedDstForBridge?.includes(chainId)) ||
|
|
744
|
+
(!bridgeUI &&
|
|
745
|
+
swapSupported &&
|
|
746
|
+
srcChain?.supportedDstForSwap?.includes(chainId))),
|
|
622
747
|
);
|
|
748
|
+
// create token options from other destination networks and filter out the source token
|
|
623
749
|
for (const { chainId } of dstOtherChains) {
|
|
624
750
|
dstOtherTokenOptions.push(
|
|
625
751
|
...getTokenOptions(chainId).filter(({ symbol }) =>
|
|
@@ -628,7 +754,7 @@ export const SwapProvider = ({
|
|
|
628
754
|
);
|
|
629
755
|
}
|
|
630
756
|
setDstChainOtherTokensOptions(dstOtherTokenOptions);
|
|
631
|
-
|
|
757
|
+
// add balances to the token options
|
|
632
758
|
const addBalances = (tokenOptions: TokenOption[]) => {
|
|
633
759
|
return tokenOptions.map((token) => ({
|
|
634
760
|
...token,
|
|
@@ -637,7 +763,6 @@ export const SwapProvider = ({
|
|
|
637
763
|
),
|
|
638
764
|
}));
|
|
639
765
|
};
|
|
640
|
-
|
|
641
766
|
setSrcChainTokensOptions(addBalances(srcTokenOptions));
|
|
642
767
|
setDstChainTokensOptions(addBalances(dstTokenOptions));
|
|
643
768
|
setSrcChainOtherTokensOptions(addBalances(srcOtherTokenOptions));
|
|
@@ -654,68 +779,108 @@ export const SwapProvider = ({
|
|
|
654
779
|
tokenBalances,
|
|
655
780
|
]);
|
|
656
781
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
782
|
+
// updates the source and destination chain options based on network support and bridge configurations.
|
|
783
|
+
useEffect(() => {
|
|
784
|
+
const baseChains = supportedChains.filter(
|
|
785
|
+
({ web3Environment, swapSupported, bridgeSupported }) =>
|
|
786
|
+
web3Environment === Web3Environment.MAINNET &&
|
|
787
|
+
((bridgeUI && bridgeSupported) || (!bridgeUI && swapSupported)),
|
|
788
|
+
);
|
|
789
|
+
// Determine disabled source chains for bridge UI. For swap, the source chains are all the supported chains.
|
|
790
|
+
let sourceChainsDisabled: string[] = [];
|
|
791
|
+
// If we block any bridge source chain, we need to have dstChain and dstToken selected, otherwise we allow to pick any source chain.
|
|
792
|
+
if (bridgeUI && dstChain && dstToken && bridgeTokensDictionary) {
|
|
793
|
+
// Find chains that have tokens available for the selected destination chain
|
|
794
|
+
const chainsWithTokensForDestination = baseChains.filter(
|
|
795
|
+
({ chainId }) => {
|
|
796
|
+
const bridgeTokens = bridgeTokensDictionary[chainId] || {};
|
|
797
|
+
return Object.values(bridgeTokens).some(
|
|
798
|
+
(targetChains) => targetChains[dstChain.chainId],
|
|
799
|
+
);
|
|
800
|
+
},
|
|
801
|
+
);
|
|
664
802
|
|
|
665
|
-
|
|
803
|
+
// Disable chains that don't have tokens available for the destination
|
|
804
|
+
sourceChainsDisabled = baseChains
|
|
805
|
+
.filter(
|
|
806
|
+
(chain) =>
|
|
807
|
+
!chainsWithTokensForDestination.some(
|
|
808
|
+
(availableChain) => availableChain.chainId === chain.chainId,
|
|
809
|
+
),
|
|
810
|
+
)
|
|
811
|
+
.map((chain) => chain.chainId);
|
|
812
|
+
}
|
|
666
813
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
srcToken.address,
|
|
670
|
-
srcChain?.publicRpcUrls[0],
|
|
671
|
-
);
|
|
814
|
+
// Determine disabled destination chains.
|
|
815
|
+
const destinationChainsDisabled = new Set<string>();
|
|
672
816
|
|
|
673
|
-
|
|
674
|
-
|
|
817
|
+
// If there is a source chain, we need to disable the destination chains that are not supported for the source chain.
|
|
818
|
+
if (srcChain) {
|
|
819
|
+
baseChains
|
|
820
|
+
.filter((chain) =>
|
|
821
|
+
bridgeUI
|
|
822
|
+
? !srcChain.supportedDstForBridge?.includes(chain.chainId)
|
|
823
|
+
: !srcChain.supportedDstForSwap?.includes(chain.chainId),
|
|
824
|
+
)
|
|
825
|
+
.forEach((chain) => destinationChainsDisabled.add(chain.chainId));
|
|
675
826
|
}
|
|
676
827
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
);
|
|
828
|
+
// If there is no source token, we need to disable the chains without any supported tokens.
|
|
829
|
+
if (bridgeUI && srcChain && bridgeTokensDictionary && !srcToken) {
|
|
830
|
+
const srcChainTokens = bridgeTokensDictionary[srcChain.chainId];
|
|
831
|
+
if (!srcChainTokens) {
|
|
832
|
+
// If source chain has no bridge tokens, disable all destination chains
|
|
683
833
|
|
|
684
|
-
|
|
685
|
-
|
|
834
|
+
baseChains.forEach((chain) =>
|
|
835
|
+
destinationChainsDisabled.add(chain.chainId),
|
|
836
|
+
);
|
|
837
|
+
} else {
|
|
838
|
+
// Disable chains that have no tokens available from the source chain
|
|
839
|
+
baseChains
|
|
840
|
+
.filter((chain) => {
|
|
841
|
+
// Check if any token on source chain can bridge to this destination chain
|
|
842
|
+
return !Object.values(srcChainTokens).some(
|
|
843
|
+
(targetChains) => targetChains[chain.chainId],
|
|
844
|
+
);
|
|
845
|
+
})
|
|
846
|
+
.forEach((chain) => destinationChainsDisabled.add(chain.chainId));
|
|
847
|
+
}
|
|
848
|
+
}
|
|
686
849
|
|
|
687
|
-
|
|
688
|
-
(
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
);
|
|
850
|
+
// If there is a source token, we need to disable the chains that don't have tokens available for the source token.
|
|
851
|
+
if (bridgeUI && srcChain && bridgeTokensDictionary && srcToken) {
|
|
852
|
+
const availableChains =
|
|
853
|
+
bridgeTokensDictionary[srcChain.chainId]?.[srcToken.address];
|
|
854
|
+
baseChains
|
|
693
855
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
856
|
+
.filter((chain) => !availableChains?.[chain.chainId])
|
|
857
|
+
.forEach((chain) => destinationChainsDisabled.add(chain.chainId));
|
|
858
|
+
}
|
|
697
859
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
860
|
+
setSrcChainOptions(sortChains(baseChains, sourceChainsDisabled));
|
|
861
|
+
setDstChainOptions(sortChains(baseChains, [...destinationChainsDisabled]));
|
|
862
|
+
}, [
|
|
863
|
+
supportedChains,
|
|
864
|
+
bridgeUI,
|
|
865
|
+
srcChain,
|
|
866
|
+
srcToken,
|
|
867
|
+
dstChain,
|
|
868
|
+
dstToken,
|
|
869
|
+
bridgeTokensDictionary,
|
|
870
|
+
]);
|
|
706
871
|
|
|
707
|
-
//
|
|
708
|
-
|
|
872
|
+
// =============================================================================
|
|
873
|
+
// useEffects responsible for state updates and validations
|
|
874
|
+
// =============================================================================
|
|
709
875
|
|
|
876
|
+
// sets the initial source chain based on the wallet's chain ID if not already initialized.
|
|
710
877
|
useEffect(() => {
|
|
711
|
-
// apply wallet chain only after initial state is set
|
|
712
878
|
if (!walletChainId || !isInitialStateSet || integrationConfig?.srcChainId) {
|
|
713
879
|
return;
|
|
714
880
|
}
|
|
715
881
|
const initSrcChain = supportedChains.find(
|
|
716
882
|
({ chainId }) => chainId === `${walletChainId}`,
|
|
717
883
|
);
|
|
718
|
-
|
|
719
884
|
if (initSrcChain) {
|
|
720
885
|
setSrcChain(initSrcChain);
|
|
721
886
|
}
|
|
@@ -726,10 +891,12 @@ export const SwapProvider = ({
|
|
|
726
891
|
integrationConfig?.srcChainId,
|
|
727
892
|
]);
|
|
728
893
|
|
|
894
|
+
// resets source and destination tokens and chains when bridgeUI changes and initial state is not yet set.
|
|
729
895
|
useEffect(() => {
|
|
730
|
-
if (isInitialStateSet) {
|
|
896
|
+
if (!isInitialStateSet) {
|
|
731
897
|
return;
|
|
732
898
|
}
|
|
899
|
+
|
|
733
900
|
setSrcToken(undefined);
|
|
734
901
|
setDstToken(undefined);
|
|
735
902
|
setSrcChain(undefined);
|
|
@@ -738,9 +905,8 @@ export const SwapProvider = ({
|
|
|
738
905
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
739
906
|
}, [bridgeUI]);
|
|
740
907
|
|
|
908
|
+
// This useEffect runs just once in full. It initializes source and destination chains and tokens based on integration config, wallet, and default values, then marks initial state as set.
|
|
741
909
|
useEffect(() => {
|
|
742
|
-
// Skip if initial state was already set or wait if supported chains are not loaded yet
|
|
743
|
-
|
|
744
910
|
if (
|
|
745
911
|
isInitialStateSet ||
|
|
746
912
|
!supportedChains ||
|
|
@@ -750,70 +916,54 @@ export const SwapProvider = ({
|
|
|
750
916
|
return;
|
|
751
917
|
}
|
|
752
918
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
// is it a valid eth address? if not try to find it by symbol / tokenId
|
|
756
|
-
if (srcTokenAddress && !ethers.utils.isAddress(srcTokenAddress)) {
|
|
757
|
-
srcTokenAddress = supportedChains
|
|
758
|
-
.find((chain) => chain.chainId === integrationConfig?.srcChainId)
|
|
759
|
-
?.tokens.find(
|
|
760
|
-
(token) => token.tokenId === integrationConfig?.srcTokenAddr,
|
|
761
|
-
)
|
|
762
|
-
?.address?.toLowerCase();
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
if (dstTokenAddress && !ethers.utils.isAddress(dstTokenAddress)) {
|
|
766
|
-
dstTokenAddress = supportedChains
|
|
767
|
-
.find((chain) => chain.chainId === integrationConfig?.dstChainId)
|
|
768
|
-
?.tokens.find(
|
|
769
|
-
(token) => token.tokenId === integrationConfig?.dstTokenAddr,
|
|
770
|
-
)
|
|
771
|
-
?.address?.toLowerCase();
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
const searchedChainId =
|
|
775
|
-
// currently set chain
|
|
919
|
+
// find chains
|
|
920
|
+
const searchedSrcChainId =
|
|
776
921
|
srcChain ||
|
|
777
|
-
// chain set by the integrator
|
|
778
922
|
integrationConfig?.srcChainId ||
|
|
779
|
-
// wallet chain
|
|
780
923
|
(walletChainId ? `${walletChainId}` : undefined) ||
|
|
781
|
-
// default chain
|
|
782
924
|
(bridgeUI ? undefined : DEFAULT_SOURCE_CHAIN_ID);
|
|
783
925
|
|
|
784
926
|
const initSrcChain = supportedChains.find(
|
|
785
|
-
({ chainId }) => chainId ===
|
|
927
|
+
({ chainId }) => chainId === searchedSrcChainId,
|
|
786
928
|
);
|
|
787
929
|
|
|
930
|
+
const searchedDstChainId =
|
|
931
|
+
dstChain ||
|
|
932
|
+
integrationConfig?.dstChainId ||
|
|
933
|
+
(bridgeUI ? undefined : DEFAULT_DESTINATION_CHAIN_ID);
|
|
934
|
+
|
|
935
|
+
const initDstChain = supportedChains.find(
|
|
936
|
+
({ chainId }) => chainId === searchedDstChainId,
|
|
937
|
+
);
|
|
938
|
+
|
|
939
|
+
// find tokens
|
|
940
|
+
let initDstToken: Token | undefined;
|
|
788
941
|
let initSrcToken: Token | undefined;
|
|
942
|
+
|
|
943
|
+
const srcTokenAddress = resolveTokenAddress(
|
|
944
|
+
integrationConfig.srcTokenAddr,
|
|
945
|
+
integrationConfig?.srcChainId,
|
|
946
|
+
supportedChains,
|
|
947
|
+
);
|
|
948
|
+
const dstTokenAddress = resolveTokenAddress(
|
|
949
|
+
integrationConfig.dstTokenAddr,
|
|
950
|
+
integrationConfig?.dstChainId,
|
|
951
|
+
supportedChains,
|
|
952
|
+
);
|
|
953
|
+
|
|
789
954
|
if (initSrcChain) {
|
|
790
955
|
initSrcToken = initSrcChain.tokens.find(
|
|
791
956
|
({ address }) =>
|
|
792
957
|
address.toLowerCase() === srcTokenAddress?.toLowerCase(),
|
|
793
958
|
);
|
|
794
959
|
}
|
|
795
|
-
|
|
796
|
-
let initDstChain: Chain | undefined = bridgeUI
|
|
797
|
-
? undefined
|
|
798
|
-
: supportedChains.find(
|
|
799
|
-
({ chainId }) => chainId === DEFAULT_DESTINATION_CHAIN_ID,
|
|
800
|
-
);
|
|
801
|
-
|
|
802
|
-
// if integrator set it, use it
|
|
803
|
-
if (integrationConfig.dstChainId) {
|
|
804
|
-
initDstChain = supportedChains.find(
|
|
805
|
-
({ chainId }) => chainId === integrationConfig.dstChainId,
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
let initDstToken: Token | undefined;
|
|
810
960
|
if (initDstChain) {
|
|
811
961
|
initDstToken = initDstChain.tokens.find(
|
|
812
962
|
({ address }) => address.toLowerCase() === dstTokenAddress,
|
|
813
963
|
);
|
|
814
964
|
}
|
|
815
965
|
|
|
816
|
-
// the
|
|
966
|
+
// validate the initial state
|
|
817
967
|
const { source, target } = mapToValidPath({
|
|
818
968
|
source: { chain: initSrcChain, token: initSrcToken },
|
|
819
969
|
target: { chain: initDstChain, token: initDstToken },
|
|
@@ -821,7 +971,6 @@ export const SwapProvider = ({
|
|
|
821
971
|
supportedChains,
|
|
822
972
|
bridgeTokensDictionary,
|
|
823
973
|
});
|
|
824
|
-
|
|
825
974
|
setSrcChain(source.chain);
|
|
826
975
|
setDstChain(target.chain);
|
|
827
976
|
setSrcToken(source.token);
|
|
@@ -840,7 +989,7 @@ export const SwapProvider = ({
|
|
|
840
989
|
isInitialStateSet,
|
|
841
990
|
bridgeTokensDictionary,
|
|
842
991
|
]);
|
|
843
|
-
|
|
992
|
+
// syncs current chain and token selections with the valid path computed from current state.
|
|
844
993
|
useEffect(() => {
|
|
845
994
|
if (
|
|
846
995
|
!bridgeTokensDictionary ||
|
|
@@ -857,20 +1006,23 @@ export const SwapProvider = ({
|
|
|
857
1006
|
supportedChains,
|
|
858
1007
|
bridgeTokensDictionary,
|
|
859
1008
|
});
|
|
860
|
-
|
|
861
1009
|
if (source.chain?.chainId !== srcChain?.chainId) {
|
|
862
1010
|
setSrcChain(source.chain);
|
|
863
1011
|
}
|
|
864
|
-
|
|
865
1012
|
if (target.chain?.chainId !== dstChain?.chainId) {
|
|
866
1013
|
setDstChain(target.chain);
|
|
867
1014
|
}
|
|
868
|
-
|
|
869
|
-
|
|
1015
|
+
if (
|
|
1016
|
+
source.token?.address !== srcToken?.address ||
|
|
1017
|
+
source.token?.name !== srcToken?.name
|
|
1018
|
+
) {
|
|
870
1019
|
setSrcToken(source.token);
|
|
871
1020
|
}
|
|
872
1021
|
|
|
873
|
-
if (
|
|
1022
|
+
if (
|
|
1023
|
+
target.token?.address !== dstToken?.address ||
|
|
1024
|
+
target.token?.name !== dstToken?.name
|
|
1025
|
+
) {
|
|
874
1026
|
setDstToken(target.token);
|
|
875
1027
|
}
|
|
876
1028
|
}, [
|
|
@@ -881,9 +1033,13 @@ export const SwapProvider = ({
|
|
|
881
1033
|
bridgeUI,
|
|
882
1034
|
supportedChains,
|
|
883
1035
|
bridgeTokensDictionary,
|
|
1036
|
+
isInitialStateSet,
|
|
884
1037
|
]);
|
|
885
|
-
|
|
1038
|
+
// notifies integration or global callback when the destination chain changes.
|
|
886
1039
|
useEffect(() => {
|
|
1040
|
+
if (!isInitialStateSet) {
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
887
1043
|
if (integrationConfig.onDstChainChange) {
|
|
888
1044
|
integrationConfig.onDstChainChange(dstChain?.chainId);
|
|
889
1045
|
} else if (window.xPayOnDstChainChange) {
|
|
@@ -891,8 +1047,11 @@ export const SwapProvider = ({
|
|
|
891
1047
|
}
|
|
892
1048
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
893
1049
|
}, [integrationConfig.onDstChainChange, dstChain]);
|
|
894
|
-
|
|
1050
|
+
// notifies integration or global callback when the destination token changes.
|
|
895
1051
|
useEffect(() => {
|
|
1052
|
+
if (!isInitialStateSet) {
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
896
1055
|
const token = dstToken
|
|
897
1056
|
? { address: dstToken.address.toLowerCase(), symbol: dstToken.tokenId }
|
|
898
1057
|
: undefined;
|
|
@@ -904,8 +1063,11 @@ export const SwapProvider = ({
|
|
|
904
1063
|
}
|
|
905
1064
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
906
1065
|
}, [integrationConfig.onDstTokenChange, dstToken]);
|
|
907
|
-
|
|
1066
|
+
// notifies integration or global callback when the source chain changes.
|
|
908
1067
|
useEffect(() => {
|
|
1068
|
+
if (!isInitialStateSet) {
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
909
1071
|
if (integrationConfig.onSrcChainChange) {
|
|
910
1072
|
integrationConfig.onSrcChainChange(srcChain?.chainId);
|
|
911
1073
|
} else if (window.xPayOnSrcChainChange) {
|
|
@@ -913,8 +1075,11 @@ export const SwapProvider = ({
|
|
|
913
1075
|
}
|
|
914
1076
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
915
1077
|
}, [integrationConfig.onSrcChainChange, srcChain]);
|
|
916
|
-
|
|
1078
|
+
// notifies integration or global callback when the source token changes.
|
|
917
1079
|
useEffect(() => {
|
|
1080
|
+
if (!isInitialStateSet) {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
918
1083
|
const token = srcToken
|
|
919
1084
|
? { address: srcToken.address.toLowerCase(), symbol: srcToken.tokenId }
|
|
920
1085
|
: undefined;
|
|
@@ -927,18 +1092,6 @@ export const SwapProvider = ({
|
|
|
927
1092
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
928
1093
|
}, [integrationConfig.onSrcTokenChange, srcToken]);
|
|
929
1094
|
|
|
930
|
-
const onConnectWallet = useCallback(() => {
|
|
931
|
-
if (integrationConfig.onConnectWallet) {
|
|
932
|
-
integrationConfig.onConnectWallet();
|
|
933
|
-
} else if (window.xPayOnConnectWallet) {
|
|
934
|
-
window.xPayOnConnectWallet();
|
|
935
|
-
}
|
|
936
|
-
}, [integrationConfig]);
|
|
937
|
-
|
|
938
|
-
const hasOnConnectWalletCallback = useMemo(() => {
|
|
939
|
-
return !!(integrationConfig.onConnectWallet || window.xPayOnConnectWallet);
|
|
940
|
-
}, [integrationConfig.onConnectWallet]);
|
|
941
|
-
|
|
942
1095
|
return (
|
|
943
1096
|
<swapContext.Provider
|
|
944
1097
|
value={{
|
|
@@ -956,6 +1109,8 @@ export const SwapProvider = ({
|
|
|
956
1109
|
setSrcChain,
|
|
957
1110
|
srcChainTokensOptions,
|
|
958
1111
|
srcChainOtherTokensOptions,
|
|
1112
|
+
srcChainOptions,
|
|
1113
|
+
dstChainOptions,
|
|
959
1114
|
tokenPrices,
|
|
960
1115
|
dstChain,
|
|
961
1116
|
setDstChain,
|
|
@@ -973,8 +1128,6 @@ export const SwapProvider = ({
|
|
|
973
1128
|
dstValueWei,
|
|
974
1129
|
dstValueUsd,
|
|
975
1130
|
dstValueMin,
|
|
976
|
-
lastTx,
|
|
977
|
-
setLastTxHash,
|
|
978
1131
|
expressDelivery,
|
|
979
1132
|
setExpressDelivery,
|
|
980
1133
|
infiniteApproval,
|
|
@@ -989,6 +1142,7 @@ export const SwapProvider = ({
|
|
|
989
1142
|
srcTokenBalanceInfo,
|
|
990
1143
|
isFetchingRoute,
|
|
991
1144
|
isFetchingBalance,
|
|
1145
|
+
isFetchingBalances,
|
|
992
1146
|
isExpressDeliveryActive,
|
|
993
1147
|
quoteRoute,
|
|
994
1148
|
refreshBalance,
|