@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,734 @@
1
+ // import type { TransactionResponse } from "@0xsquid/sdk/dist/types/ethers";
2
+ import type {
3
+ DexName,
4
+ FallbackAddress,
5
+ RouteRequest,
6
+ RouteResponse,
7
+ Token,
8
+ } from "@0xsquid/sdk/dist/types";
9
+ import { ChainType } from "@0xsquid/sdk/dist/types";
10
+
11
+ import { osmosisChainId } from "@/core/constants";
12
+ import { useCosmosContext } from "@/core/providers/CosmosProvider";
13
+ import { keys } from "@/core/queries/queries-keys";
14
+ import type { TransactionReplacedError } from "@/core/types/error";
15
+ import { TransactionErrorType } from "@/core/types/error";
16
+ import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
17
+ import type { TransactionReceipt } from "@ethersproject/abstract-provider";
18
+ import { TransactionStatus as GnosisTransactionStatus } from "@safe-global/safe-apps-sdk/dist/src/types";
19
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
20
+ import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
21
+ import type { BigNumber } from "ethers";
22
+ import { constants, ethers, utils } from "ethers";
23
+ import { Logger } from "ethers/lib/utils.js";
24
+ import { useCallback, useMemo } from "react";
25
+ import type { Connector } from "wagmi";
26
+ import { useAccount, useSigner } from "wagmi";
27
+
28
+ import type {
29
+ TransactionHistoryStore,
30
+ TransactionParams,
31
+ } from "@/core/types/transaction";
32
+ import { TransactionStatus } from "@/core/types/transaction";
33
+ import { getWorkingCosmosRpcUrl } from "@/services/external/rpcService";
34
+ import { getTransactionError } from "@/services/internal/errorService";
35
+ import {
36
+ getMainExplorerUrl,
37
+ getSourceExplorerTxUrl,
38
+ replaceTransactionAtNonce,
39
+ updateTransactionHistoryStatus,
40
+ } from "@/services/internal/transactionService";
41
+
42
+ import { useSquidChains } from "@/hooks//chains/useSquidChains";
43
+ import {
44
+ usePersistStore,
45
+ useSquidStore,
46
+ useSwapRoutePersistStore,
47
+ } from "@/hooks/store/useSquidStore";
48
+ import { useSwap } from "@/hooks/swap/useSwap";
49
+ import { useUserParams } from "@/hooks/user/useUserParams";
50
+ import { useGnosisContext } from "@/hooks/wallet/useGnosisContext";
51
+ import { useMultiChainWallet } from "@/hooks/wallet/useMultiChainWallet";
52
+ import { WidgetEvents } from "@/services/internal/eventService";
53
+
54
+ export const useTransaction = () => {
55
+ const signer = useSigner();
56
+ const {
57
+ cosmosSigner,
58
+ getCosmosAddressForChain,
59
+ isConnected: isCosmosConnected,
60
+ } = useCosmosContext();
61
+
62
+ const { chains } = useSquidChains();
63
+ const queryClient = useQueryClient();
64
+ const { fromPrice, toPrice, squid, currentTransaction, config } =
65
+ useSquidStore();
66
+ const { swapRoute } = useSwapRoutePersistStore();
67
+ const { connector: activeConnector } = useAccount();
68
+
69
+ const { fromChain, toChain, fromToken, toToken, destinationAddress } =
70
+ useSwap();
71
+
72
+ const { getGnosisSafeContext } = useGnosisContext();
73
+ const { expressEnabled, gasEnabled } = useUserParams();
74
+
75
+ const { connectedAddress: sourceUserAddress } =
76
+ useMultiChainWallet(fromChain);
77
+
78
+ const isSameChainSwap = useMemo(
79
+ () => fromChain?.chainId === toChain?.chainId,
80
+ [fromChain?.chainId, toChain?.chainId]
81
+ );
82
+
83
+ const mainExplorerUrl = useMemo(() => {
84
+ return getMainExplorerUrl(currentTransaction);
85
+ }, [currentTransaction]);
86
+
87
+ /**
88
+ * Get fallback addresses for cosmos chains
89
+ * This is needed by backend when a cosmos swap occurs between non coin118 chains
90
+ * The backend might need a coin118 address to send the funds to in case of failure for a swap happening between the two chains
91
+ */
92
+ const getCosmosFallbackAddresses = useCallback(async () => {
93
+ // If not connected, user might have filled it manually
94
+ if (!isCosmosConnected) {
95
+ return swapRoute?.fallbackAddress
96
+ ? ([
97
+ {
98
+ address: swapRoute?.fallbackAddress,
99
+ coinType: 118,
100
+ },
101
+ // TODO: Temp parsing here, should be included in v2 sdk
102
+ ] as FallbackAddress[])
103
+ : undefined;
104
+ }
105
+
106
+ // the user is connected to cosmos, we can get the fallback address from the wallet
107
+ // We only need coin118, so taking osmosis hub address by default
108
+ const osmosisAddress = await getCosmosAddressForChain(
109
+ osmosisChainId.mainnet
110
+ );
111
+ if (!osmosisAddress) {
112
+ return undefined;
113
+ }
114
+
115
+ // Always used the one set by user (if any), otherwise use the derived osmo address
116
+ // only return something if one of the fallback address is defined
117
+ if (!!swapRoute?.fallbackAddress || !!osmosisAddress) {
118
+ return [
119
+ {
120
+ address: swapRoute?.fallbackAddress ?? osmosisAddress,
121
+ coinType: 118,
122
+ },
123
+ ] as FallbackAddress[];
124
+ }
125
+
126
+ return undefined;
127
+ }, [getCosmosAddressForChain, isCosmosConnected, swapRoute?.fallbackAddress]);
128
+
129
+ /**
130
+ * Fetching route data from the API
131
+ * These data will be used to trigger the transaction
132
+ * @returns {Route} Route data
133
+ */
134
+ const squidRoute = useQuery(
135
+ keys({
136
+ address: destinationAddress,
137
+ apiUrl: config.apiUrl,
138
+ }).transaction(
139
+ swapRoute,
140
+ fromPrice,
141
+ config.slippage,
142
+ config.infiniteApproval,
143
+ config.enableGetGasOnDestination,
144
+ config.enableExpress,
145
+ sourceUserAddress
146
+ ),
147
+ async () => {
148
+ const quoteOnly =
149
+ sourceUserAddress === undefined || destinationAddress === undefined;
150
+
151
+ const isEvmSwap =
152
+ fromChain?.chainType === ChainType.EVM &&
153
+ toChain?.chainType === ChainType.EVM;
154
+
155
+ const cosmosFallbackAddresses =
156
+ quoteOnly || isEvmSwap ? undefined : await getCosmosFallbackAddresses();
157
+
158
+ if (!!swapRoute) {
159
+ // TODO: Put RouteRequest back when it supports fallbackAddress
160
+ const params: RouteRequest = {
161
+ fromChain: String(swapRoute.fromChainId),
162
+ fromToken: swapRoute.fromTokenAddress ?? "",
163
+ fromAddress: sourceUserAddress ?? constants.AddressZero,
164
+ fromAmount: utils
165
+ .parseUnits(fromPrice?.toString() ?? "0", fromToken?.decimals)
166
+ .toString(),
167
+ toChain: swapRoute.toChainId! as string,
168
+ toToken: swapRoute.toTokenAddress!,
169
+ toAddress: destinationAddress ?? "",
170
+ quoteOnly:
171
+ sourceUserAddress === undefined || destinationAddress === undefined,
172
+ slippageConfig: {
173
+ // We set slippage to undefined because automode will be picked if slippage is undefined
174
+ // Slippage to 1 = auto mode
175
+ slippage: config.slippage === 1 ? undefined : config.slippage,
176
+ autoMode: 1,
177
+ },
178
+ enableBoost: expressEnabled,
179
+ prefer: config.preferDex as DexName[] | undefined,
180
+ receiveGasOnDestination: gasEnabled,
181
+ onChainQuoting: !!config.onChainQuoting,
182
+ };
183
+
184
+ // If the swap is involving cosmos chains, we need to add the fallback addresses (if any)
185
+ if (
186
+ cosmosFallbackAddresses &&
187
+ cosmosFallbackAddresses.length > 0 &&
188
+ cosmosFallbackAddresses[0].address
189
+ ) {
190
+ params.fallbackAddresses = cosmosFallbackAddresses;
191
+ }
192
+
193
+ const { route, requestId } = await squid!.getRoute({
194
+ ...params,
195
+ });
196
+
197
+ useSquidStore.setState({
198
+ currentRequestId: requestId,
199
+ });
200
+
201
+ return route;
202
+ }
203
+ },
204
+ {
205
+ enabled:
206
+ squid !== undefined &&
207
+ fromPrice !== undefined &&
208
+ fromPrice !== "0" &&
209
+ swapRoute?.toChainId !== undefined &&
210
+ swapRoute.toTokenAddress !== undefined,
211
+ // TODO: Check this, might be outside library
212
+ // && currentRoute?.path !== routes.transaction.path,
213
+ cacheTime: 60000,
214
+ staleTime: 20000,
215
+ refetchOnWindowFocus: (query) =>
216
+ Date.now() - query.state.dataUpdatedAt > 30000, // Update if older than 30 seconds, when window is focused
217
+ refetchIntervalInBackground: false, // Don't refetch when window is not focused
218
+ refetchInterval: 30000, // Refetch every 30 seconds
219
+ }
220
+ );
221
+
222
+ /**
223
+ * Checking if spending tokens is allowed for this source address
224
+ * On Success: storing the transaction
225
+ * On Error: Showing the error message if any
226
+ * @returns {boolean} approved
227
+ */
228
+ const routeApproved = useQuery(
229
+ keys({
230
+ address: sourceUserAddress,
231
+ apiUrl: config.apiUrl,
232
+ }).routeApproved(
233
+ swapRoute,
234
+ sourceUserAddress,
235
+ squidRoute.data as RouteResponse["route"]
236
+ ),
237
+ async () => {
238
+ try {
239
+ const { isApproved } = await squid!.isRouteApproved({
240
+ route: squidRoute.data!,
241
+ sender: sourceUserAddress!,
242
+ });
243
+
244
+ return isApproved;
245
+ } catch (error) {
246
+ return false;
247
+ }
248
+ },
249
+ {
250
+ enabled: !!squidRoute.data && !!sourceUserAddress,
251
+ }
252
+ );
253
+
254
+ // USDT has a very specific way of handling approvals
255
+ // ```
256
+ /// To change the approve amount you first have to reduce the addresses`
257
+ // allowance to zero by calling `approve(_spender, 0)` if it is not
258
+ // already 0 to mitigate the race condition described here:
259
+ // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
260
+ // ```
261
+ // This is why we had an unpredictable gas error for USDT approvals
262
+ // So it needs a custom gas limit
263
+ const approveSpecificTokenToZero = async (token: Token) => {
264
+ if (
265
+ signer.data &&
266
+ token.symbol.toLowerCase() === "usdt" &&
267
+ token.chainId === "1" &&
268
+ fromToken
269
+ ) {
270
+ const squidRouter = "0xce16F69375520ab01377ce7B88f5BA8C48F8D666";
271
+ const usdtContract = new ethers.Contract(
272
+ fromToken.address,
273
+ [
274
+ {
275
+ constant: false,
276
+ inputs: [
277
+ { name: "_spender", type: "address" },
278
+ { name: "_value", type: "uint256" },
279
+ ],
280
+ name: "approve",
281
+ outputs: [],
282
+ payable: false,
283
+ stateMutability: "nonpayable",
284
+ type: "function",
285
+ },
286
+ {
287
+ constant: true,
288
+ inputs: [
289
+ { name: "_owner", type: "address" },
290
+ { name: "_spender", type: "address" },
291
+ ],
292
+ name: "allowance",
293
+ outputs: [{ name: "remaining", type: "uint256" }],
294
+ payable: false,
295
+ stateMutability: "view",
296
+ type: "function",
297
+ },
298
+ ],
299
+ signer.data
300
+ );
301
+
302
+ // Get approval amount from usdt contract
303
+ const allowance = (await usdtContract.allowance(
304
+ sourceUserAddress,
305
+ squidRouter
306
+ )) as BigNumber;
307
+
308
+ // If allowance is greater than 0, set it to 0
309
+ if (allowance.gt(constants.Zero)) {
310
+ const approveTx = await usdtContract.approve(
311
+ squidRouter,
312
+ constants.Zero
313
+ );
314
+ await approveTx.wait();
315
+ }
316
+ }
317
+ return true;
318
+ };
319
+
320
+ /**
321
+ * Manually approve route if necessary
322
+ */
323
+ const approveRoute = useMutation(
324
+ keys({
325
+ address: sourceUserAddress,
326
+ apiUrl: config.apiUrl,
327
+ }).aproveRoute(fromToken?.address, squidRoute?.data?.params.fromAmount),
328
+ async () => {
329
+ try {
330
+ if (squidRoute.data && signer.data && fromToken) {
331
+ await approveSpecificTokenToZero(fromToken);
332
+
333
+ const approved = await squid?.approveRoute({
334
+ route: squidRoute.data,
335
+ signer: signer.data,
336
+ executionSettings: { infiniteApproval: config.infiniteApproval },
337
+ });
338
+
339
+ return approved;
340
+ }
341
+ return false;
342
+ } catch (error) {
343
+ // Keep the error in the console to debug future issues
344
+ console.error(error);
345
+ return false;
346
+ }
347
+ },
348
+ {
349
+ onSuccess: () => {
350
+ queryClient.invalidateQueries(
351
+ keys({
352
+ address: sourceUserAddress,
353
+ apiUrl: config.apiUrl,
354
+ }).routeApproved(swapRoute, sourceUserAddress, squidRoute.data)
355
+ );
356
+ },
357
+ }
358
+ );
359
+
360
+ /**
361
+ * There's a specific way to get the transaction hash for the safe connector
362
+ * SO if the app is being used inside the safe container, we need to use the safe sdk to get the tx hash
363
+ * @param connector
364
+ * @param hashReceived
365
+ * @returns
366
+ */
367
+ const getTransactionHash = async (
368
+ connector: Connector<any, any, any> | undefined,
369
+ hashReceived: string
370
+ ): Promise<string> => {
371
+ if (connector?.id === "safe") {
372
+ const safeSdk = await getGnosisSafeContext();
373
+
374
+ const tx = await safeSdk?.txs.getBySafeTxHash(hashReceived);
375
+ const status: GnosisTransactionStatus | undefined = tx?.txStatus;
376
+ if (
377
+ status !== GnosisTransactionStatus.FAILED &&
378
+ status !== GnosisTransactionStatus.SUCCESS &&
379
+ status !== GnosisTransactionStatus.CANCELLED
380
+ ) {
381
+ // Wait 2 seconds before checking the gnosis status again
382
+ // eslint-disable-next-line no-promise-executor-return
383
+ await new Promise((res) => setTimeout(res, 2000));
384
+ return getTransactionHash(connector, hashReceived);
385
+ }
386
+ return tx?.txHash ?? hashReceived;
387
+ }
388
+ return hashReceived;
389
+ };
390
+
391
+ const persistTransaction = async (txToPersist: TransactionParams) => {
392
+ // Persisting the transaction in local storage
393
+ if (squidRoute.data) {
394
+ const previousHistoryList =
395
+ usePersistStore.getState().transactionsHistory ?? [];
396
+
397
+ const newHistoryElement: TransactionHistoryStore = {
398
+ ...txToPersist,
399
+ params: squidRoute.data.params,
400
+ estimate: squidRoute.data.estimate,
401
+ };
402
+
403
+ usePersistStore.setState({
404
+ transactionsHistory: [...previousHistoryList, newHistoryElement],
405
+ });
406
+ }
407
+ };
408
+
409
+ const setTransactionState = useCallback(
410
+ ({
411
+ route,
412
+ txHash,
413
+ nonce,
414
+ status,
415
+ sourceStatus,
416
+ userAddress,
417
+ axelarUrl,
418
+ }: {
419
+ route?: RouteResponse["route"];
420
+ txHash: string;
421
+ nonce?: number;
422
+ status: TransactionStatus;
423
+ sourceStatus: TransactionStatus;
424
+ userAddress?: string;
425
+ axelarUrl?: string;
426
+ }) => {
427
+ if (route && route.transactionRequest) {
428
+ const { routeType } = route.transactionRequest;
429
+
430
+ const tx: TransactionParams = {
431
+ fromChain,
432
+ toChain,
433
+ routeType,
434
+ nonce,
435
+ transactionId: txHash,
436
+ status,
437
+ sourceStatus,
438
+ timestamp: Date.now(),
439
+ fromAddress: userAddress,
440
+ sourceTxExplorerUrl: getSourceExplorerTxUrl(fromChain, txHash),
441
+ sourceExplorerImgUrl: fromChain?.chainIconURI,
442
+ axelarUrl,
443
+ };
444
+
445
+ useSquidStore.setState({
446
+ currentTransaction: tx,
447
+ });
448
+
449
+ return tx;
450
+ }
451
+ return undefined;
452
+ },
453
+ [fromChain, toChain]
454
+ );
455
+
456
+ const swapQueryCosmos = useMutation(
457
+ async (route?: RouteResponse["route"]) => {
458
+ const chainData = chains.find(
459
+ (c) => c.chainId === route?.params.fromChain
460
+ );
461
+
462
+ if (cosmosSigner && chainData) {
463
+ try {
464
+ const rpc = await getWorkingCosmosRpcUrl(chainData);
465
+ const signingClient = await SigningCosmWasmClient.connectWithSigner(
466
+ rpc,
467
+ cosmosSigner
468
+ );
469
+ const signerAddress = (await cosmosSigner.getAccounts())[0].address;
470
+
471
+ if (signerAddress && signingClient && route) {
472
+ const tx = (await squid?.executeRoute({
473
+ signer: signingClient as any,
474
+ signerAddress,
475
+ route,
476
+ executionSettings: {
477
+ infiniteApproval: config.infiniteApproval,
478
+ },
479
+ })) as TxRaw;
480
+
481
+ // set the tx state to loading, as soon as user signed the tx
482
+ setTransactionState({
483
+ txHash: "", // We don't have the hash yet
484
+ route,
485
+ status: TransactionStatus.ONGOING,
486
+ sourceStatus: TransactionStatus.ONGOING,
487
+ });
488
+
489
+ // broadcast the signed tx to get hash and listen to events
490
+ const response = await signingClient.broadcastTx(
491
+ TxRaw.encode(tx).finish()
492
+ );
493
+
494
+ const hash = response.transactionHash;
495
+
496
+ // Dispatch event so it can be listened from outside the widget
497
+ WidgetEvents.getInstance().dispatchSwapExecuteCall(route, hash);
498
+
499
+ const txParams: TransactionParams | undefined = setTransactionState(
500
+ {
501
+ route,
502
+ txHash: hash,
503
+ userAddress: sourceUserAddress,
504
+ status: TransactionStatus.ONGOING,
505
+ sourceStatus: TransactionStatus.ONGOING,
506
+ axelarUrl: undefined,
507
+ }
508
+ );
509
+ if (txParams) {
510
+ persistTransaction(txParams);
511
+ }
512
+
513
+ return response.code === 0;
514
+ }
515
+ } catch (error) {
516
+ console.log(error);
517
+
518
+ const castedError = (error as { message: string | undefined }) ?? {};
519
+ if (castedError.message?.includes("Request rejected")) {
520
+ throw new Error(castedError.message);
521
+ }
522
+ }
523
+ }
524
+ throw new Error("Need all parameters");
525
+ }
526
+ );
527
+
528
+ // If the transaction is replaced, we need to update the transaction hash
529
+ // Transaction replaced can mean that the user has speed up the transaction for example
530
+ // Could also be cancelled
531
+ const handleTransactionReplacementError = useCallback(
532
+ async ({
533
+ error,
534
+ route,
535
+ status,
536
+ sourceStatus,
537
+ userAddress,
538
+ axelarUrl,
539
+ }: {
540
+ error: any;
541
+ route?: RouteResponse["route"];
542
+ status: TransactionStatus;
543
+ sourceStatus: TransactionStatus;
544
+ userAddress?: string;
545
+ axelarUrl?: string;
546
+ }): Promise<TransactionReceipt> => {
547
+ if (route && error.code === Logger.errors.TRANSACTION_REPLACED) {
548
+ const txReplacementError = error as TransactionReplacedError;
549
+
550
+ const { hash: newHash, nonce: newNonce } =
551
+ txReplacementError.replacement;
552
+
553
+ if (route.transactionRequest && squidRoute.data) {
554
+ const txParams = setTransactionState({
555
+ route,
556
+ txHash: newHash,
557
+ nonce: newNonce,
558
+ userAddress: sourceUserAddress,
559
+ status,
560
+ sourceStatus,
561
+ axelarUrl: undefined,
562
+ });
563
+
564
+ if (txParams) {
565
+ const newHistoryElement: TransactionHistoryStore = {
566
+ ...txParams,
567
+ params: squidRoute.data.params,
568
+ estimate: squidRoute.data.estimate,
569
+ };
570
+
571
+ // Need to store the new transaction hash on the previous transaction
572
+ usePersistStore.setState({
573
+ transactionsHistory: replaceTransactionAtNonce(
574
+ newNonce,
575
+ sourceUserAddress!,
576
+ usePersistStore.getState().transactionsHistory,
577
+ newHistoryElement
578
+ ),
579
+ });
580
+ }
581
+ }
582
+
583
+ try {
584
+ const response = await txReplacementError.replacement.wait();
585
+ return response;
586
+ } catch (replacementError) {
587
+ // Maybe the transaction was replaced again
588
+ // recursive call
589
+ return handleTransactionReplacementError({
590
+ error,
591
+ route,
592
+ status,
593
+ sourceStatus,
594
+ userAddress,
595
+ axelarUrl,
596
+ });
597
+ }
598
+ } else {
599
+ throw error;
600
+ }
601
+ },
602
+ [
603
+ setTransactionState,
604
+ sourceUserAddress,
605
+ squidRoute.data,
606
+ squid?.axelarscanURL,
607
+ ]
608
+ );
609
+
610
+ const swapQueryEvm = useMutation(async (route?: RouteResponse["route"]) => {
611
+ if (route && !!squid && signer.isSuccess && signer.data) {
612
+ const txResponse = (await squid.executeRoute({
613
+ signer: signer.data,
614
+ route,
615
+ executionSettings: {
616
+ infiniteApproval: config.infiniteApproval,
617
+ },
618
+ })) as unknown as ethers.providers.TransactionResponse;
619
+
620
+ const hash = await getTransactionHash(activeConnector, txResponse.hash);
621
+
622
+ // Dispatch event so it can be listened from outside the widget
623
+ WidgetEvents.getInstance().dispatchSwapExecuteCall(route, hash);
624
+
625
+ if (route.transactionRequest) {
626
+ const txParams: TransactionParams | undefined = setTransactionState({
627
+ route,
628
+ txHash: hash,
629
+ nonce: txResponse.nonce,
630
+ userAddress: sourceUserAddress,
631
+ status: TransactionStatus.INITIAL_LOADING,
632
+ sourceStatus: TransactionStatus.ONGOING,
633
+ axelarUrl: undefined,
634
+ });
635
+ if (txParams) {
636
+ persistTransaction(txParams);
637
+ }
638
+ }
639
+
640
+ try {
641
+ const response = await txResponse.wait();
642
+ return response;
643
+ } catch (error: any) {
644
+ return handleTransactionReplacementError({
645
+ error,
646
+ route,
647
+ status: TransactionStatus.INITIAL_LOADING,
648
+ sourceStatus: TransactionStatus.ONGOING,
649
+ userAddress: sourceUserAddress,
650
+ axelarUrl: undefined,
651
+ });
652
+ }
653
+ }
654
+ throw new Error("Need all parameters");
655
+ });
656
+
657
+ /**
658
+ * Execute cross chain swap with selected tokens
659
+ * getRoute should be called before this mutation
660
+ */
661
+ const swapQuery = useMutation(
662
+ async (route?: RouteResponse["route"]) => {
663
+ const sourceChain = chains?.find(
664
+ (chain) => chain.chainId == route?.params?.fromChain
665
+ );
666
+
667
+ if (sourceChain?.chainType === ChainType.COSMOS) {
668
+ return swapQueryCosmos.mutateAsync(route);
669
+ }
670
+ if (sourceChain?.chainType === ChainType.EVM) {
671
+ return swapQueryEvm.mutateAsync(route);
672
+ }
673
+
674
+ throw new Error("Invalid parameters or chain not found");
675
+ },
676
+ {
677
+ onError: (error: any) => {
678
+ const { currentTransaction: currentTx } = useSquidStore.getState();
679
+ const errorObject = getTransactionError(error);
680
+ useSquidStore.setState({
681
+ currentTransaction: {
682
+ ...currentTx!,
683
+ status: TransactionStatus.ERROR,
684
+ sourceStatus: TransactionStatus.ERROR,
685
+ error: errorObject,
686
+ },
687
+ });
688
+ if (
689
+ currentTx?.transactionId &&
690
+ errorObject?.type === TransactionErrorType.CALL_EXCEPTION
691
+ ) {
692
+ usePersistStore.setState({
693
+ transactionsHistory: updateTransactionHistoryStatus(
694
+ currentTransaction?.transactionId,
695
+ TransactionStatus.ERROR,
696
+ usePersistStore.getState().transactionsHistory,
697
+ undefined
698
+ ),
699
+ });
700
+ }
701
+ },
702
+ onSuccess: () => {
703
+ const { currentTransaction: currentTx } = useSquidStore.getState();
704
+
705
+ queryClient.invalidateQueries(keys({}).balances());
706
+
707
+ useSquidStore.setState({
708
+ currentTransaction: {
709
+ ...currentTx!,
710
+ sourceStatus: TransactionStatus.SUCCESS,
711
+ status: isSameChainSwap
712
+ ? TransactionStatus.SUCCESS
713
+ : TransactionStatus.ONGOING,
714
+ },
715
+ });
716
+ },
717
+ }
718
+ );
719
+
720
+ return {
721
+ routeApproved,
722
+ approveRoute,
723
+ swapQuery,
724
+ currentTransaction,
725
+ fromToken,
726
+ toToken,
727
+ squidRoute,
728
+ fromPrice,
729
+ toPrice,
730
+ toChain,
731
+ fromChain,
732
+ mainExplorerUrl,
733
+ };
734
+ };