@0xsquid/react-hooks 1.0.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 (108) hide show
  1. package/.babelrc +7 -0
  2. package/.eslintignore +1 -0
  3. package/.eslintrc.json +44 -0
  4. package/dist/index.js +6 -0
  5. package/jest.config.ts +28 -0
  6. package/package.json +124 -0
  7. package/scripts/copyAssets.js +27 -0
  8. package/scripts/removeBuildTestFolder.js +28 -0
  9. package/scripts/replacePaths.js +37 -0
  10. package/src/assets/images/icons/wallets/binance.svg +4 -0
  11. package/src/assets/images/icons/wallets/bitget.svg +43 -0
  12. package/src/assets/images/icons/wallets/bitkeep.svg +9 -0
  13. package/src/assets/images/icons/wallets/blockchaindotcom.svg +11 -0
  14. package/src/assets/images/icons/wallets/brave.svg +18 -0
  15. package/src/assets/images/icons/wallets/c98.svg +3 -0
  16. package/src/assets/images/icons/wallets/coinbase.svg +5 -0
  17. package/src/assets/images/icons/wallets/cosmostation.svg +9 -0
  18. package/src/assets/images/icons/wallets/defiConnect.svg +25 -0
  19. package/src/assets/images/icons/wallets/exodus.svg +33 -0
  20. package/src/assets/images/icons/wallets/fetchai.svg +1 -0
  21. package/src/assets/images/icons/wallets/index.ts +43 -0
  22. package/src/assets/images/icons/wallets/keplr.svg +9 -0
  23. package/src/assets/images/icons/wallets/leap.svg +35 -0
  24. package/src/assets/images/icons/wallets/metamask.svg +48 -0
  25. package/src/assets/images/icons/wallets/okx.svg +1 -0
  26. package/src/assets/images/icons/wallets/rabby.svg +1 -0
  27. package/src/assets/images/icons/wallets/trustwallet.svg +11 -0
  28. package/src/assets/images/icons/wallets/walletConnect.svg +11 -0
  29. package/src/assets/images/icons/wallets/wallet_icon.svg +1 -0
  30. package/src/assets/images/icons/wallets/xdefi.svg +9 -0
  31. package/src/assets/images/icons/wallets/zerion.svg +11 -0
  32. package/src/connectors/ExodusConnector.ts +6 -0
  33. package/src/connectors/LedgerLiveConnector.ts +226 -0
  34. package/src/contracts/typechain/ERC20.d.ts +441 -0
  35. package/src/contracts/typechain/factories/ERC20__factory.d.ts +56 -0
  36. package/src/contracts/typechain/factories/ERC20__factory.ts +340 -0
  37. package/src/core/constants.ts +549 -0
  38. package/src/core/externalLinks.ts +26 -0
  39. package/src/core/index.ts +1 -0
  40. package/src/core/multicall3.ts +22 -0
  41. package/src/core/numbers.ts +44 -0
  42. package/src/core/providers/CosmosProvider.tsx +55 -0
  43. package/src/core/queries/queries-keys.ts +156 -0
  44. package/src/core/queries/react-query-config.ts +13 -0
  45. package/src/core/types/components.ts +16 -0
  46. package/src/core/types/config.ts +52 -0
  47. package/src/core/types/dex.ts +31 -0
  48. package/src/core/types/error.ts +52 -0
  49. package/src/core/types/event.ts +16 -0
  50. package/src/core/types/route.ts +21 -0
  51. package/src/core/types/swap.ts +8 -0
  52. package/src/core/types/tokens.ts +30 -0
  53. package/src/core/types/transaction.ts +84 -0
  54. package/src/core/types/types.ts +37 -0
  55. package/src/core/types/wallet.ts +83 -0
  56. package/src/hooks/chains/useSquidChains.ts +66 -0
  57. package/src/hooks/cosmos/useCosmos.ts +279 -0
  58. package/src/hooks/cosmos/useCosmosForChain.ts +33 -0
  59. package/src/hooks/index.ts +32 -0
  60. package/src/hooks/navigation/useKeyboardNavigation.ts +88 -0
  61. package/src/hooks/store/useSquidStore.ts +80 -0
  62. package/src/hooks/swap/useSwap.ts +359 -0
  63. package/src/hooks/tokens/useAllTokensWithBalance.ts +157 -0
  64. package/src/hooks/tokens/useBalance.ts +180 -0
  65. package/src/hooks/tokens/useMultiChainBalance.ts +59 -0
  66. package/src/hooks/tokens/usePrices.ts +52 -0
  67. package/src/hooks/tokens/useSingleTokenPrice.ts +45 -0
  68. package/src/hooks/tokens/useSquidTokens.ts +48 -0
  69. package/src/hooks/tokens/useTokensWithBalance.ts +169 -0
  70. package/src/hooks/transaction/useEstimate.ts +397 -0
  71. package/src/hooks/transaction/useEstimatePriceImpact.ts +39 -0
  72. package/src/hooks/transaction/useExecuteTransaction.ts +440 -0
  73. package/src/hooks/transaction/useExpress.ts +46 -0
  74. package/src/hooks/transaction/useGetRoute.ts +145 -0
  75. package/src/hooks/transaction/useSingleTransaction.ts +155 -0
  76. package/src/hooks/transaction/useTransaction.ts +734 -0
  77. package/src/hooks/user/useUserParams.ts +63 -0
  78. package/src/hooks/wallet/useAddToken.ts +53 -0
  79. package/src/hooks/wallet/useAutoConnect.ts +56 -0
  80. package/src/hooks/wallet/useGnosisContext.ts +84 -0
  81. package/src/hooks/wallet/useIntegratorContext.ts +44 -0
  82. package/src/hooks/wallet/useMultiChainWallet.ts +146 -0
  83. package/src/hooks/wallet/useWallet.ts +86 -0
  84. package/src/hooks/webapp-router/useSquidRouter.ts +42 -0
  85. package/src/index.ts +5 -0
  86. package/src/provider/index.tsx +105 -0
  87. package/src/react-app-env.d.ts +2 -0
  88. package/src/services/external/analyticsService.ts +46 -0
  89. package/src/services/external/coingeckoService.ts +35 -0
  90. package/src/services/external/rpcService.ts +196 -0
  91. package/src/services/external/secretService.ts +178 -0
  92. package/src/services/index.ts +1 -0
  93. package/src/services/internal/apiService.ts +6 -0
  94. package/src/services/internal/assetsService.ts +84 -0
  95. package/src/services/internal/configService.ts +261 -0
  96. package/src/services/internal/errorService.ts +124 -0
  97. package/src/services/internal/eventService.ts +93 -0
  98. package/src/services/internal/priceService.ts +10 -0
  99. package/src/services/internal/transactionService.ts +255 -0
  100. package/src/services/internal/transactionStatusService.ts +293 -0
  101. package/src/services/internal/walletService.ts +158 -0
  102. package/src/tests/assetsService.test.ts +176 -0
  103. package/src/tests/configService.test.ts +486 -0
  104. package/src/tests/fetchSquidData.ts +43 -0
  105. package/src/tests/jest-svg-transform.ts +19 -0
  106. package/src/tests/sample.json +0 -0
  107. package/src/tests/walletService.test.ts +178 -0
  108. package/tsconfig.json +30 -0
@@ -0,0 +1,359 @@
1
+ import {
2
+ chainIdResetValue,
3
+ destinationAddressResetValue,
4
+ } from "@/core/constants";
5
+ import { useSquidChains } from "@/hooks/chains/useSquidChains";
6
+ import {
7
+ useSquidStore,
8
+ useSwapRoutePersistStore,
9
+ } from "@/hooks/store/useSquidStore";
10
+ import { usePrices } from "@/hooks/tokens/usePrices";
11
+ import { useSquidTokens } from "@/hooks/tokens/useSquidTokens";
12
+ import { useMultiChainWallet } from "@/hooks/wallet/useMultiChainWallet";
13
+ import {
14
+ filterTokensForDestination,
15
+ getDefaultTokenAddressForChain,
16
+ getFirstAvailableChainId,
17
+ getInitialOrDefaultTokenAddressForChain,
18
+ getTokensForChain,
19
+ } from "@/services/internal/configService";
20
+ import { WidgetEvents } from "@/services/internal/eventService";
21
+ import type { Token } from "@0xsquid/sdk/dist/types";
22
+ import { useCallback, useEffect, useMemo } from "react";
23
+
24
+ export const useSwap = () => {
25
+ const { fromPrice, toPrice, config } = useSquidStore();
26
+ const { swapRoute } = useSwapRoutePersistStore();
27
+ const { tokenPrices } = usePrices();
28
+ const { tokens } = useSquidTokens();
29
+ const { chains, supportedDestinationChains, supportedSourceChains } =
30
+ useSquidChains();
31
+
32
+ // chain ID will use the swapRoute one (the user choice)
33
+ // Or the config one (if defined)
34
+ const fromChainId: string | number | undefined =
35
+ swapRoute?.fromChainId ||
36
+ (chains.find((c) => c.chainId === config.initialAssets?.from?.chainId) &&
37
+ config.initialAssets?.from?.chainId);
38
+
39
+ const toChainId: string | number | undefined =
40
+ swapRoute?.toChainId ||
41
+ (chains.find((c) => c.chainId === config.initialAssets?.to?.chainId) &&
42
+ config.initialAssets?.to?.chainId);
43
+
44
+ const fromChain = chains.find((c) => c.chainId === swapRoute?.fromChainId);
45
+ const fromTokens = getTokensForChain(tokens, fromChainId);
46
+ const fromToken = fromTokens.find(
47
+ (t) => t.address === swapRoute?.fromTokenAddress
48
+ );
49
+ const toChain = chains.find((c) => c.chainId === swapRoute?.toChainId);
50
+ const toTokensForChain = getTokensForChain(tokens, toChainId);
51
+ const toToken = toTokensForChain.find(
52
+ (t) => t.address === swapRoute?.toTokenAddress
53
+ );
54
+ const isSameChain = fromChainId === toChainId;
55
+ const toTokens = filterTokensForDestination(
56
+ toTokensForChain,
57
+ toChain,
58
+ fromToken
59
+ );
60
+
61
+ const tokenItems: { from: Token[]; to: Token[] } = useMemo(
62
+ () => ({
63
+ from: fromTokens,
64
+ to: toTokens,
65
+ }),
66
+ [fromTokens, toTokens]
67
+ );
68
+
69
+ const updatedDestinationAddress = swapRoute?.destinationAddress;
70
+ const { connectedAddress: destinationUserAddress } =
71
+ useMultiChainWallet(toChain);
72
+
73
+ /**
74
+ * Important to have a defined address otherwise the "getRoute" cannot be called
75
+ * TODO: Improve this check readability
76
+ */
77
+ const destinationAddress = useMemo(() => {
78
+ if (updatedDestinationAddress) return updatedDestinationAddress;
79
+ if (destinationUserAddress) return destinationUserAddress;
80
+ return undefined;
81
+ }, [destinationUserAddress, updatedDestinationAddress]);
82
+
83
+ /**
84
+ * When user changes the price of the source token,
85
+ * This will update the destination token price
86
+ * @param price: string
87
+ */
88
+ const fromPriceChanged = useCallback(
89
+ (price: string) => {
90
+ useSquidStore.setState({
91
+ fromPrice: price || undefined,
92
+ });
93
+
94
+ if (
95
+ price !== "" &&
96
+ !!tokenPrices.data?.sourceTokenUsdPrice &&
97
+ !!tokenPrices.data?.destinationTokenUsdPrice
98
+ ) {
99
+ const ratio =
100
+ tokenPrices.data.sourceTokenUsdPrice /
101
+ tokenPrices.data.destinationTokenUsdPrice;
102
+ useSquidStore.setState({
103
+ toPrice: +price * ratio,
104
+ });
105
+ }
106
+ },
107
+ [
108
+ tokenPrices.data?.destinationTokenUsdPrice,
109
+ tokenPrices.data?.sourceTokenUsdPrice,
110
+ ]
111
+ );
112
+
113
+ /**
114
+ * Destination token address has different logic
115
+ * Some tokens are not available based on the source chain/token
116
+ * TODO: This could need a refactor
117
+ */
118
+ const handleDestinationAddressOnSwapChange = useCallback(
119
+ (
120
+ fromChainId: string | number | undefined,
121
+ fromTokenAddress: string | undefined,
122
+ toTokenAddress: string | undefined,
123
+ toChainId: number | string | undefined
124
+ ) => {
125
+ const fromToken = tokens.find(
126
+ (t) =>
127
+ t.address === (fromTokenAddress ?? swapRoute?.fromTokenAddress) &&
128
+ t.chainId === fromChainId
129
+ );
130
+
131
+ const toChain = chains.find(
132
+ (c) => c.chainId === (toChainId ?? swapRoute?.toChainId)
133
+ );
134
+
135
+ const destinationTokensFiltered = filterTokensForDestination(
136
+ getTokensForChain(tokens, toChainId ?? swapRoute?.toChainId),
137
+ toChain,
138
+ fromToken
139
+ );
140
+
141
+ const defaultToAddress = getDefaultTokenAddressForChain(
142
+ destinationTokensFiltered,
143
+ config,
144
+ toChainId ?? swapRoute?.toChainId
145
+ );
146
+
147
+ let destinationTokenAddress =
148
+ toChainId && !toTokenAddress
149
+ ? defaultToAddress
150
+ : toTokenAddress ?? swapRoute?.toTokenAddress;
151
+
152
+ // Re assign destination token address if it's not available
153
+ if (
154
+ destinationTokensFiltered.find(
155
+ (t) => t.address === destinationTokenAddress
156
+ ) === undefined
157
+ ) {
158
+ destinationTokenAddress = defaultToAddress;
159
+ }
160
+ return destinationTokenAddress;
161
+ },
162
+ [
163
+ config,
164
+ fromTokens,
165
+ tokens,
166
+ swapRoute?.fromTokenAddress,
167
+ swapRoute?.toChainId,
168
+ swapRoute?.fromChainId,
169
+ swapRoute?.toTokenAddress,
170
+ toChain,
171
+ ]
172
+ );
173
+
174
+ /**
175
+ * When user changes chains from dropdown
176
+ * @param
177
+ */
178
+ const onSwapChange = useCallback(
179
+ async ({
180
+ fromChainId,
181
+ toChainId,
182
+ fromTokenAddress,
183
+ toTokenAddress,
184
+ destinationAddress,
185
+ fallbackAddress,
186
+ }: {
187
+ fromChainId?: number | string;
188
+ toChainId?: number | string;
189
+ fromTokenAddress?: string;
190
+ toTokenAddress?: string;
191
+ destinationAddress?: string;
192
+ fallbackAddress?: string;
193
+ }) => {
194
+ const sourceChainId = fromChainId ?? swapRoute?.fromChainId;
195
+
196
+ // Get the default Source Address
197
+ // This can be either the first token or the one set by the user from config
198
+ const defaultFromAddress = getDefaultTokenAddressForChain(
199
+ tokens,
200
+ config,
201
+ sourceChainId
202
+ );
203
+
204
+ // Get the destination ChainId based on parameters of swap received
205
+ const destinationChainId =
206
+ toChainId === chainIdResetValue
207
+ ? undefined
208
+ : toChainId ?? swapRoute?.toChainId;
209
+
210
+ // Destination Token Address based on parameters of swap received
211
+ // If the user only changed the chain, we need to get the default token address
212
+ // Otherwise, we use the one received
213
+ const sourceTokenAddress =
214
+ fromChainId && !fromTokenAddress
215
+ ? defaultFromAddress
216
+ : fromTokenAddress ?? swapRoute?.fromTokenAddress;
217
+
218
+ // Destination Token Address based on parameters of swap received
219
+ const destinationWalletAddress =
220
+ destinationAddress === destinationAddressResetValue
221
+ ? undefined
222
+ : destinationAddress ?? swapRoute?.destinationAddress;
223
+
224
+ // If destination wallet address is changed and fallback is not, reset fallback address
225
+ const fbAddress =
226
+ destinationAddress === destinationAddressResetValue ||
227
+ (!!destinationAddress && !fallbackAddress)
228
+ ? undefined
229
+ : fallbackAddress ?? swapRoute?.fallbackAddress;
230
+
231
+ // The destination token address is a bit different
232
+ // Some specific filters apply based on source chain & token
233
+ const destinationTokenAddress = handleDestinationAddressOnSwapChange(
234
+ fromChainId ?? swapRoute?.fromChainId,
235
+ sourceTokenAddress,
236
+ toTokenAddress,
237
+ toChainId
238
+ );
239
+
240
+ // Fire event
241
+ WidgetEvents.getInstance().dispatchSwapParamsChanged({
242
+ fromChainId: String(sourceChainId),
243
+ toChainId: String(destinationChainId),
244
+ fromTokenAddress: sourceTokenAddress,
245
+ toTokenAddress: destinationTokenAddress,
246
+ });
247
+
248
+ useSwapRoutePersistStore.setState({
249
+ swapRoute: {
250
+ fromChainId: sourceChainId,
251
+ toChainId: destinationChainId,
252
+ fromTokenAddress: sourceTokenAddress,
253
+ toTokenAddress: destinationTokenAddress,
254
+ destinationAddress: destinationWalletAddress,
255
+ fallbackAddress: fbAddress,
256
+ },
257
+ });
258
+ },
259
+ [
260
+ tokens,
261
+ config,
262
+ swapRoute?.fromChainId,
263
+ swapRoute?.toChainId,
264
+ swapRoute?.fromTokenAddress,
265
+ swapRoute?.destinationAddress,
266
+ swapRoute?.fallbackAddress,
267
+ handleDestinationAddressOnSwapChange,
268
+ ]
269
+ );
270
+
271
+ /**
272
+ * Inverting selected prices, chains & tokens
273
+ */
274
+ const invertSwaps = () => {
275
+ const isToChainDisabledOnSource = config.disabledChains?.source?.includes(
276
+ String(toChainId)
277
+ );
278
+
279
+ const isFromChainDisabledOnDestination =
280
+ config.disabledChains?.destination?.includes(String(fromChainId));
281
+
282
+ // if any of the chains are disabled on the opposite direction, do not allow to invert swap
283
+ if (isToChainDisabledOnSource || isFromChainDisabledOnDestination) return;
284
+
285
+ useSwapRoutePersistStore.setState({
286
+ swapRoute: {
287
+ fromChainId: toChainId,
288
+ toChainId: fromChainId,
289
+ fromTokenAddress: swapRoute?.toTokenAddress || tokenItems.to[0].address,
290
+ toTokenAddress:
291
+ swapRoute?.fromTokenAddress || tokenItems.from[0].address,
292
+ },
293
+ });
294
+ };
295
+
296
+ /**
297
+ * Init default chain ids and default token addresses
298
+ * From the first item of sdk arrays
299
+ */
300
+ useEffect(() => {
301
+ if (chains.length > 0 && !useSwapRoutePersistStore.getState().swapRoute) {
302
+ const defaultFromChainId = getFirstAvailableChainId(
303
+ config,
304
+ "from",
305
+ supportedSourceChains
306
+ );
307
+ const defaultToChainId = getFirstAvailableChainId(
308
+ config,
309
+ "to",
310
+ supportedDestinationChains
311
+ );
312
+
313
+ const fromTokenAddress = getInitialOrDefaultTokenAddressForChain({
314
+ chainId: defaultFromChainId,
315
+ config,
316
+ direction: "from",
317
+ tokens,
318
+ });
319
+ const toTokenAddress = getInitialOrDefaultTokenAddressForChain({
320
+ chainId: defaultToChainId,
321
+ config,
322
+ direction: "to",
323
+ tokens,
324
+ unavailableTokenAddress: fromTokenAddress,
325
+ });
326
+
327
+ useSwapRoutePersistStore.setState({
328
+ swapRoute: {
329
+ fromChainId: defaultFromChainId,
330
+ toChainId: defaultToChainId,
331
+ fromTokenAddress,
332
+ toTokenAddress,
333
+ destinationAddress: undefined,
334
+ },
335
+ });
336
+ }
337
+ }, [
338
+ config,
339
+ fromTokens,
340
+ supportedDestinationChains,
341
+ supportedSourceChains,
342
+ toTokens,
343
+ ]);
344
+
345
+ return {
346
+ tokenItems,
347
+ onSwapChange,
348
+ invertSwaps,
349
+ fromPrice,
350
+ toPrice,
351
+ fromPriceChanged,
352
+ toToken,
353
+ fromToken,
354
+ fromChain,
355
+ toChain,
356
+ destinationAddress,
357
+ isSameChain,
358
+ };
359
+ };
@@ -0,0 +1,157 @@
1
+ import { keys } from "@/core/queries/queries-keys";
2
+ import { TokenWithBalance } from "@/core/types/tokens";
3
+ import { useSquidStore } from "@/hooks/store/useSquidStore";
4
+ import { useSquidTokens } from "@/hooks/tokens/useSquidTokens";
5
+ import { useSwap } from "@/hooks/swap/useSwap";
6
+ import type { Token } from "@0xsquid/sdk/dist/types";
7
+ import { useQuery } from "@tanstack/react-query";
8
+ import { useCallback, useMemo } from "react";
9
+ import { fetchPriceForToken } from "@/services/external/coingeckoService";
10
+ import { getAllEvmTokensBalance } from "@/services/external/rpcService";
11
+ import { useAccount } from "wagmi";
12
+
13
+ export const useAllTokensWithBalance = (direction: "from" | "to") => {
14
+ const { config } = useSquidStore();
15
+ const { address: connectedEVMAddress, isConnected } = useAccount();
16
+
17
+ const { destinationAddress, fromToken, toToken } = useSwap();
18
+ const { tokens: squidTokens } = useSquidTokens();
19
+
20
+ const desiredAddress = useMemo(
21
+ () => (direction === "to" ? destinationAddress : connectedEVMAddress),
22
+ [connectedEVMAddress, destinationAddress, direction]
23
+ );
24
+
25
+ // Checking if token is EVM by checking if chainId is a number
26
+ const evmTokens: TokenWithBalance[] = useMemo(() => {
27
+ const disabledTokensOnDirection =
28
+ config.disabledChains?.[
29
+ direction === "from" ? "source" : "destination"
30
+ ] ?? [];
31
+
32
+ return squidTokens
33
+ .filter(
34
+ (t) =>
35
+ !isNaN(Number(t.chainId)) &&
36
+ !disabledTokensOnDirection.includes(t.chainId)
37
+ )
38
+ .map((t) => ({ ...t, balance: "0" }));
39
+ }, [squidTokens]);
40
+
41
+ // Checking if token is EVM by checking if chainId is a suite of letters
42
+ const cosmosTokens: TokenWithBalance[] = useMemo(() => {
43
+ const disabledTokensOnDirection =
44
+ config.disabledChains?.[
45
+ direction === "from" ? "source" : "destination"
46
+ ] ?? [];
47
+
48
+ return squidTokens
49
+ .filter(
50
+ (t) =>
51
+ isNaN(Number(t.chainId)) &&
52
+ !disabledTokensOnDirection.includes(t.chainId)
53
+ )
54
+ .map((t) => ({ ...t, balance: "0" }));
55
+ }, [squidTokens]);
56
+
57
+ const tokensWithoutBalance = useMemo(() => {
58
+ return [...evmTokens, ...cosmosTokens];
59
+ }, [cosmosTokens, evmTokens]);
60
+
61
+ const tokensWithBalanceQuery = useQuery<TokenWithBalance[]>(
62
+ keys({
63
+ apiUrl: config.apiUrl,
64
+ address: desiredAddress,
65
+ }).allTokensBalance(direction),
66
+ async () => {
67
+ // Need to be connected to query balance for EVM tokens
68
+ if (!isConnected || !desiredAddress) {
69
+ return [...evmTokens, ...cosmosTokens];
70
+ }
71
+
72
+ // Get balances
73
+ const evmTokensBalances = await getAllEvmTokensBalance(
74
+ evmTokens,
75
+ desiredAddress
76
+ );
77
+
78
+ // Concatenate all tokens
79
+ return [...evmTokensBalances, ...cosmosTokens] as TokenWithBalance[];
80
+ }
81
+ );
82
+
83
+ const removeTokenSelectedInOtherDirection = useCallback(
84
+ (t: Token): boolean => {
85
+ const tokenSelectedInOtherDirection =
86
+ direction === "to" ? fromToken : toToken;
87
+
88
+ return !(
89
+ t.address === tokenSelectedInOtherDirection?.address &&
90
+ t.chainId === tokenSelectedInOtherDirection?.chainId
91
+ );
92
+ },
93
+ [direction, fromToken, toToken]
94
+ );
95
+
96
+ const tokens = useMemo(
97
+ () =>
98
+ (tokensWithBalanceQuery.data ?? tokensWithoutBalance).filter(
99
+ removeTokenSelectedInOtherDirection
100
+ ),
101
+ [
102
+ tokensWithBalanceQuery.data,
103
+ tokensWithoutBalance,
104
+ removeTokenSelectedInOtherDirection,
105
+ ]
106
+ );
107
+
108
+ // When we load this view,
109
+ // Fetch the prices for all tokens that have a balance
110
+ // We need to do this to sort the tokens by USD price after
111
+ // Only load for tokens having a balance
112
+ const getTokensWithPrices = useQuery(
113
+ keys({
114
+ apiUrl: config.apiUrl,
115
+ address: desiredAddress,
116
+ }).allTokensPriceUSDAndBalance(),
117
+
118
+ async () => {
119
+ const tokensWithUnitPrice = await Promise.all(
120
+ tokens.map(async (t) => {
121
+ let usdPrice = t.priceUSD;
122
+ // If token has a balance but no USD unit price, fetch it
123
+ if (+t.balance !== 0 && !t.priceUSD && t.chainId) {
124
+ try {
125
+ usdPrice = (
126
+ await fetchPriceForToken({
127
+ apiUrl: config.apiUrl,
128
+ chainId: t.chainId.toString(),
129
+ tokenAddress: t.address,
130
+ integratorId: config.integratorId,
131
+ })
132
+ ).toString();
133
+ } catch (error) {
134
+ // Silent catch
135
+ }
136
+ }
137
+
138
+ return {
139
+ ...t,
140
+ priceUSD: usdPrice?.toString(),
141
+ } as TokenWithBalance;
142
+ })
143
+ );
144
+
145
+ return tokensWithUnitPrice;
146
+ },
147
+ {
148
+ enabled: tokensWithBalanceQuery.isSuccess, // Only fetch price when balance is loaded
149
+ }
150
+ );
151
+
152
+ return {
153
+ tokensWithBalanceQuery,
154
+ getTokensWithPrices,
155
+ tokens,
156
+ };
157
+ };
@@ -0,0 +1,180 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import { getProvider } from "@wagmi/core";
3
+ import { ethers, utils } from "ethers";
4
+ import { useAccount } from "wagmi";
5
+
6
+ import { ERC20__factory } from "@/contracts/typechain/factories/ERC20__factory";
7
+ import { nativeEvmTokenAddress } from "@/core/constants";
8
+ import { useCosmosContext } from "@/core/providers/CosmosProvider";
9
+ import { keys } from "@/core/queries/queries-keys";
10
+ import { useCosmosForChain } from "@/hooks/cosmos/useCosmosForChain";
11
+ import { useSquidStore } from "@/hooks/store/useSquidStore";
12
+ import { useSwap } from "@/hooks/swap/useSwap";
13
+ import { useSquidTokens } from "@/hooks/tokens/useSquidTokens";
14
+ import { useAllTokensWithBalanceForChain } from "@/hooks/tokens/useTokensWithBalance";
15
+ import type { CosmosChain, EvmChain, Token } from "@0xsquid/squid-types";
16
+ import { ChainType } from "@0xsquid/squid-types";
17
+ import { useMemo } from "react";
18
+
19
+ const defaultRefreshIntervalMs = 15000;
20
+
21
+ /**
22
+ * Fetches the balance of a user on an EVM chain.
23
+ * The balance fetching only happens if the chain and token data are available, the user is connected, and the chain type is EVM.
24
+ *
25
+ * @param {boolean} enabled A flag indicating whether the balance fetching is enabled or not. Defaults to true.
26
+ * @param {boolean} refreshIntervalMs The interval in milliseconds at which the balance is fetched. Defaults to 15000 ms.
27
+ *
28
+ * @returns {Object} The react query object containing the balance data (defaults to "0").
29
+ *
30
+ */
31
+ export const useEvmBalance = ({
32
+ chain,
33
+ token,
34
+ userAddress,
35
+ enabled = true,
36
+ refreshIntervalMs = defaultRefreshIntervalMs,
37
+ }: {
38
+ chain?: EvmChain;
39
+ token?: Token;
40
+ userAddress: string;
41
+ enabled?: boolean;
42
+ refreshIntervalMs?: number;
43
+ }) => {
44
+ const { isConnected } = useAccount();
45
+ const { config } = useSquidStore();
46
+
47
+ const balance = useQuery(
48
+ keys({ address: userAddress, apiUrl: config.apiUrl }).balance(
49
+ chain?.chainId,
50
+ token?.address,
51
+ userAddress
52
+ ),
53
+ async () => {
54
+ if (
55
+ !!chain &&
56
+ isConnected &&
57
+ !!token?.address &&
58
+ chain.chainType === ChainType.EVM
59
+ ) {
60
+ const srcProvider = getProvider({ chainId: +(chain.chainId ?? 1) });
61
+ const srcTokenContract = new ethers.Contract(
62
+ token?.address,
63
+ ERC20__factory.abi,
64
+ srcProvider
65
+ );
66
+
67
+ if (token.address.toLowerCase() === nativeEvmTokenAddress) {
68
+ const nativeCurrencyBalance =
69
+ await srcProvider.getBalance(userAddress);
70
+ return utils.formatUnits(nativeCurrencyBalance, token?.decimals);
71
+ }
72
+ try {
73
+ const tokenBalance = await srcTokenContract.balanceOf(userAddress);
74
+ return utils.formatUnits(tokenBalance, token?.decimals);
75
+ } catch (error) {
76
+ return "0";
77
+ }
78
+ } else {
79
+ return "0";
80
+ }
81
+ },
82
+ {
83
+ enabled: isConnected && enabled,
84
+ refetchInterval: 0 ? false : refreshIntervalMs,
85
+ retry: 2,
86
+ }
87
+ );
88
+ return { balance };
89
+ };
90
+
91
+ export const useNativeTokenBalanceFromChain = () => {
92
+ const { isConnected, address } = useAccount();
93
+ const { config } = useSquidStore();
94
+ const { fromChain } = useSwap();
95
+ const chainId = fromChain?.chainId;
96
+
97
+ const balance = useQuery(
98
+ keys({ address, apiUrl: config.apiUrl }).nativeBalanceBigNumber(chainId),
99
+ async () => {
100
+ const srcProvider = getProvider({ chainId: +(chainId ?? 1) });
101
+ const nativeCurrencyBalance = await srcProvider.getBalance(
102
+ address as string
103
+ );
104
+ return nativeCurrencyBalance;
105
+ },
106
+ {
107
+ enabled: isConnected,
108
+ }
109
+ );
110
+ return { balance };
111
+ };
112
+
113
+ export const useNativeTokenBalanceDestinationChain = () => {
114
+ const { isConnected, address } = useAccount();
115
+ const { config } = useSquidStore();
116
+ const { toChain } = useSwap();
117
+ const { tokens } = useSquidTokens();
118
+ const chainId = toChain?.chainId;
119
+ const nativeToken = tokens.find(
120
+ (t) =>
121
+ t.symbol.toLowerCase() === toChain?.nativeCurrency.symbol.toLowerCase() &&
122
+ t.chainId === chainId
123
+ );
124
+ const balance = useQuery(
125
+ keys({ address, apiUrl: config.apiUrl }).nativeBalanceBigNumber(
126
+ chainId,
127
+ nativeToken?.address
128
+ ),
129
+ async () => {
130
+ const srcProvider = getProvider({ chainId: +(chainId ?? 1) });
131
+ const nativeCurrencyBalance = await srcProvider.getBalance(
132
+ address as string
133
+ );
134
+ return nativeCurrencyBalance;
135
+ },
136
+ {
137
+ enabled: isConnected && !!nativeToken,
138
+ }
139
+ );
140
+ return { balance };
141
+ };
142
+
143
+ export const useCosmosBalance = ({
144
+ chainData,
145
+ token,
146
+ userAddress,
147
+ }: {
148
+ chainData: CosmosChain | undefined;
149
+ token?: Token;
150
+ userAddress?: string;
151
+ }) => {
152
+ const { isConnected } = useCosmosContext();
153
+ const { cosmosAddress } = useCosmosForChain(chainData);
154
+ const { cosmosBalances } = useAllTokensWithBalanceForChain(chainData);
155
+
156
+ const walletAddressToFetch = userAddress ?? cosmosAddress;
157
+
158
+ const balance = useMemo(() => {
159
+ if (
160
+ walletAddressToFetch &&
161
+ chainData &&
162
+ chainData.chainType === ChainType.COSMOS &&
163
+ isConnected
164
+ ) {
165
+ return (
166
+ cosmosBalances?.data?.find(
167
+ (b) => b.address === token?.address && b.chainId === chainData.chainId
168
+ )?.balance ?? "0"
169
+ );
170
+ }
171
+ return "0";
172
+ }, [
173
+ cosmosBalances.data,
174
+ walletAddressToFetch,
175
+ chainData,
176
+ token,
177
+ isConnected,
178
+ ]);
179
+ return { balance };
180
+ };