@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.
- package/.babelrc +7 -0
- package/.eslintignore +1 -0
- package/.eslintrc.json +44 -0
- package/dist/index.js +6 -0
- package/jest.config.ts +28 -0
- package/package.json +124 -0
- package/scripts/copyAssets.js +27 -0
- package/scripts/removeBuildTestFolder.js +28 -0
- package/scripts/replacePaths.js +37 -0
- package/src/assets/images/icons/wallets/binance.svg +4 -0
- package/src/assets/images/icons/wallets/bitget.svg +43 -0
- package/src/assets/images/icons/wallets/bitkeep.svg +9 -0
- package/src/assets/images/icons/wallets/blockchaindotcom.svg +11 -0
- package/src/assets/images/icons/wallets/brave.svg +18 -0
- package/src/assets/images/icons/wallets/c98.svg +3 -0
- package/src/assets/images/icons/wallets/coinbase.svg +5 -0
- package/src/assets/images/icons/wallets/cosmostation.svg +9 -0
- package/src/assets/images/icons/wallets/defiConnect.svg +25 -0
- package/src/assets/images/icons/wallets/exodus.svg +33 -0
- package/src/assets/images/icons/wallets/fetchai.svg +1 -0
- package/src/assets/images/icons/wallets/index.ts +43 -0
- package/src/assets/images/icons/wallets/keplr.svg +9 -0
- package/src/assets/images/icons/wallets/leap.svg +35 -0
- package/src/assets/images/icons/wallets/metamask.svg +48 -0
- package/src/assets/images/icons/wallets/okx.svg +1 -0
- package/src/assets/images/icons/wallets/rabby.svg +1 -0
- package/src/assets/images/icons/wallets/trustwallet.svg +11 -0
- package/src/assets/images/icons/wallets/walletConnect.svg +11 -0
- package/src/assets/images/icons/wallets/wallet_icon.svg +1 -0
- package/src/assets/images/icons/wallets/xdefi.svg +9 -0
- package/src/assets/images/icons/wallets/zerion.svg +11 -0
- package/src/connectors/ExodusConnector.ts +6 -0
- package/src/connectors/LedgerLiveConnector.ts +226 -0
- package/src/contracts/typechain/ERC20.d.ts +441 -0
- package/src/contracts/typechain/factories/ERC20__factory.d.ts +56 -0
- package/src/contracts/typechain/factories/ERC20__factory.ts +340 -0
- package/src/core/constants.ts +549 -0
- package/src/core/externalLinks.ts +26 -0
- package/src/core/index.ts +1 -0
- package/src/core/multicall3.ts +22 -0
- package/src/core/numbers.ts +44 -0
- package/src/core/providers/CosmosProvider.tsx +55 -0
- package/src/core/queries/queries-keys.ts +156 -0
- package/src/core/queries/react-query-config.ts +13 -0
- package/src/core/types/components.ts +16 -0
- package/src/core/types/config.ts +52 -0
- package/src/core/types/dex.ts +31 -0
- package/src/core/types/error.ts +52 -0
- package/src/core/types/event.ts +16 -0
- package/src/core/types/route.ts +21 -0
- package/src/core/types/swap.ts +8 -0
- package/src/core/types/tokens.ts +30 -0
- package/src/core/types/transaction.ts +84 -0
- package/src/core/types/types.ts +37 -0
- package/src/core/types/wallet.ts +83 -0
- package/src/hooks/chains/useSquidChains.ts +66 -0
- package/src/hooks/cosmos/useCosmos.ts +279 -0
- package/src/hooks/cosmos/useCosmosForChain.ts +33 -0
- package/src/hooks/index.ts +32 -0
- package/src/hooks/navigation/useKeyboardNavigation.ts +88 -0
- package/src/hooks/store/useSquidStore.ts +80 -0
- package/src/hooks/swap/useSwap.ts +359 -0
- package/src/hooks/tokens/useAllTokensWithBalance.ts +157 -0
- package/src/hooks/tokens/useBalance.ts +180 -0
- package/src/hooks/tokens/useMultiChainBalance.ts +59 -0
- package/src/hooks/tokens/usePrices.ts +52 -0
- package/src/hooks/tokens/useSingleTokenPrice.ts +45 -0
- package/src/hooks/tokens/useSquidTokens.ts +48 -0
- package/src/hooks/tokens/useTokensWithBalance.ts +169 -0
- package/src/hooks/transaction/useEstimate.ts +397 -0
- package/src/hooks/transaction/useEstimatePriceImpact.ts +39 -0
- package/src/hooks/transaction/useExecuteTransaction.ts +440 -0
- package/src/hooks/transaction/useExpress.ts +46 -0
- package/src/hooks/transaction/useGetRoute.ts +145 -0
- package/src/hooks/transaction/useSingleTransaction.ts +155 -0
- package/src/hooks/transaction/useTransaction.ts +734 -0
- package/src/hooks/user/useUserParams.ts +63 -0
- package/src/hooks/wallet/useAddToken.ts +53 -0
- package/src/hooks/wallet/useAutoConnect.ts +56 -0
- package/src/hooks/wallet/useGnosisContext.ts +84 -0
- package/src/hooks/wallet/useIntegratorContext.ts +44 -0
- package/src/hooks/wallet/useMultiChainWallet.ts +146 -0
- package/src/hooks/wallet/useWallet.ts +86 -0
- package/src/hooks/webapp-router/useSquidRouter.ts +42 -0
- package/src/index.ts +5 -0
- package/src/provider/index.tsx +105 -0
- package/src/react-app-env.d.ts +2 -0
- package/src/services/external/analyticsService.ts +46 -0
- package/src/services/external/coingeckoService.ts +35 -0
- package/src/services/external/rpcService.ts +196 -0
- package/src/services/external/secretService.ts +178 -0
- package/src/services/index.ts +1 -0
- package/src/services/internal/apiService.ts +6 -0
- package/src/services/internal/assetsService.ts +84 -0
- package/src/services/internal/configService.ts +261 -0
- package/src/services/internal/errorService.ts +124 -0
- package/src/services/internal/eventService.ts +93 -0
- package/src/services/internal/priceService.ts +10 -0
- package/src/services/internal/transactionService.ts +255 -0
- package/src/services/internal/transactionStatusService.ts +293 -0
- package/src/services/internal/walletService.ts +158 -0
- package/src/tests/assetsService.test.ts +176 -0
- package/src/tests/configService.test.ts +486 -0
- package/src/tests/fetchSquidData.ts +43 -0
- package/src/tests/jest-svg-transform.ts +19 -0
- package/src/tests/sample.json +0 -0
- package/src/tests/walletService.test.ts +178 -0
- 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
|
+
};
|