@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,440 @@
|
|
|
1
|
+
// import type { TransactionResponse } from "@0xsquid/sdk/dist/types/ethers";
|
|
2
|
+
import { useCosmosContext } from "@/core/providers/CosmosProvider";
|
|
3
|
+
import { keys } from "@/core/queries/queries-keys";
|
|
4
|
+
import type { TransactionReplacedError } from "@/core/types/error";
|
|
5
|
+
import { TransactionErrorType } from "@/core/types/error";
|
|
6
|
+
import type { RouteResponse } from "@0xsquid/sdk/dist/types";
|
|
7
|
+
import { ChainType } from "@0xsquid/sdk/dist/types";
|
|
8
|
+
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
|
|
9
|
+
import type { TransactionReceipt } from "@ethersproject/abstract-provider";
|
|
10
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
11
|
+
import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
|
|
12
|
+
import { ethers } from "ethers";
|
|
13
|
+
import { Logger } from "ethers/lib/utils.js";
|
|
14
|
+
import { useCallback, useMemo } from "react";
|
|
15
|
+
import { useAccount, useSigner } from "wagmi";
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
TransactionHistoryStore,
|
|
19
|
+
TransactionParams,
|
|
20
|
+
} from "@/core/types/transaction";
|
|
21
|
+
import { TransactionStatus } from "@/core/types/transaction";
|
|
22
|
+
import { getWorkingCosmosRpcUrl } from "@/services/external/rpcService";
|
|
23
|
+
import { getTransactionError } from "@/services/internal/errorService";
|
|
24
|
+
import {
|
|
25
|
+
getSourceExplorerTxUrl,
|
|
26
|
+
replaceTransactionAtNonce,
|
|
27
|
+
updateTransactionHistoryStatus,
|
|
28
|
+
} from "@/services/internal/transactionService";
|
|
29
|
+
|
|
30
|
+
import { useSquidChains } from "@/hooks//chains/useSquidChains";
|
|
31
|
+
import { usePersistStore, useSquidStore } from "@/hooks/store/useSquidStore";
|
|
32
|
+
import { useSwap } from "@/hooks/swap/useSwap";
|
|
33
|
+
import { useEstimate } from "@/hooks/transaction/useEstimate";
|
|
34
|
+
import { useGetRoute } from "@/hooks/transaction/useGetRoute";
|
|
35
|
+
import { useGnosisContext } from "@/hooks/wallet/useGnosisContext";
|
|
36
|
+
import { useMultiChainWallet } from "@/hooks/wallet/useMultiChainWallet";
|
|
37
|
+
import { WidgetEvents } from "@/services/internal/eventService";
|
|
38
|
+
|
|
39
|
+
enum ExecutionState {
|
|
40
|
+
NOT_ENOUGH_BALANCE = "NOT_ENOUGH_BALANCE",
|
|
41
|
+
NOT_CONNECTED = "NOT_CONNECTED",
|
|
42
|
+
READY = "READY",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const useExecuteTransaction = (
|
|
46
|
+
route: ReturnType<typeof useGetRoute>
|
|
47
|
+
) => {
|
|
48
|
+
const signer = useSigner();
|
|
49
|
+
const { cosmosSigner } = useCosmosContext();
|
|
50
|
+
|
|
51
|
+
const { chains } = useSquidChains();
|
|
52
|
+
const queryClient = useQueryClient();
|
|
53
|
+
const { fromPrice, toPrice, squid, currentTransaction, config } =
|
|
54
|
+
useSquidStore();
|
|
55
|
+
|
|
56
|
+
const { enoughBalanceToSwap } = useEstimate({
|
|
57
|
+
squidRoute: route,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const { connector: activeConnector } = useAccount();
|
|
61
|
+
|
|
62
|
+
const { fromChain, toChain, fromToken, toToken } = useSwap();
|
|
63
|
+
const { getGnosisTransactionHash } = useGnosisContext();
|
|
64
|
+
|
|
65
|
+
const { connectedAddress: sourceUserAddress } =
|
|
66
|
+
useMultiChainWallet(fromChain);
|
|
67
|
+
|
|
68
|
+
const isSameChainSwap = useMemo(
|
|
69
|
+
() => fromChain?.chainId === toChain?.chainId,
|
|
70
|
+
[fromChain?.chainId, toChain?.chainId]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* States to see if the swap execution is possible or not based on swap & user params
|
|
75
|
+
* And if not possible, gives the reason
|
|
76
|
+
* TODO: The NOT_ENOUGH_BALANCE state could be fetch independently from the route response, only based on user fromAmount
|
|
77
|
+
*/
|
|
78
|
+
const executionState = useMemo(() => {
|
|
79
|
+
if (!enoughBalanceToSwap) {
|
|
80
|
+
return ExecutionState.NOT_ENOUGH_BALANCE;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// TODO:; Add more states here
|
|
84
|
+
|
|
85
|
+
return ExecutionState.READY;
|
|
86
|
+
}, [enoughBalanceToSwap]);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* This method is used to persist the transaction in local storage
|
|
90
|
+
* This way we can access user's transaction history
|
|
91
|
+
* TODO: This part will be fetched from the backend, using user address in the future
|
|
92
|
+
* @param txToPersist
|
|
93
|
+
* @param route
|
|
94
|
+
*/
|
|
95
|
+
const persistTransaction = async (
|
|
96
|
+
txToPersist: TransactionParams,
|
|
97
|
+
route?: RouteResponse["route"]
|
|
98
|
+
) => {
|
|
99
|
+
// Persisting the transaction in local storage
|
|
100
|
+
if (route) {
|
|
101
|
+
const previousHistoryList =
|
|
102
|
+
usePersistStore.getState().transactionsHistory ?? [];
|
|
103
|
+
|
|
104
|
+
const newHistoryElement: TransactionHistoryStore = {
|
|
105
|
+
...txToPersist,
|
|
106
|
+
params: route.params,
|
|
107
|
+
estimate: route.estimate,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
usePersistStore.setState({
|
|
111
|
+
transactionsHistory: [...previousHistoryList, newHistoryElement],
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Set the transaction state in the store
|
|
118
|
+
* This is useful to access the latest transaction from any hook
|
|
119
|
+
*/
|
|
120
|
+
const setTransactionState = useCallback(
|
|
121
|
+
({
|
|
122
|
+
route,
|
|
123
|
+
txHash,
|
|
124
|
+
nonce,
|
|
125
|
+
status,
|
|
126
|
+
sourceStatus,
|
|
127
|
+
userAddress,
|
|
128
|
+
axelarUrl,
|
|
129
|
+
}: {
|
|
130
|
+
route?: RouteResponse["route"];
|
|
131
|
+
txHash: string;
|
|
132
|
+
nonce?: number;
|
|
133
|
+
status: TransactionStatus;
|
|
134
|
+
sourceStatus: TransactionStatus;
|
|
135
|
+
userAddress?: string;
|
|
136
|
+
axelarUrl?: string;
|
|
137
|
+
}) => {
|
|
138
|
+
if (route && route.transactionRequest) {
|
|
139
|
+
const { routeType } = route.transactionRequest;
|
|
140
|
+
|
|
141
|
+
const tx: TransactionParams = {
|
|
142
|
+
fromChain,
|
|
143
|
+
toChain,
|
|
144
|
+
routeType,
|
|
145
|
+
nonce,
|
|
146
|
+
transactionId: txHash,
|
|
147
|
+
status,
|
|
148
|
+
sourceStatus,
|
|
149
|
+
timestamp: Date.now(),
|
|
150
|
+
fromAddress: userAddress,
|
|
151
|
+
sourceTxExplorerUrl: getSourceExplorerTxUrl(fromChain, txHash),
|
|
152
|
+
sourceExplorerImgUrl: fromChain?.chainIconURI,
|
|
153
|
+
axelarUrl,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
useSquidStore.setState({
|
|
157
|
+
currentTransaction: tx,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return tx;
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
},
|
|
164
|
+
[fromChain, toChain]
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const swapQueryCosmos = useMutation(
|
|
168
|
+
async (route?: RouteResponse["route"]) => {
|
|
169
|
+
const chainData = chains.find(
|
|
170
|
+
(c) => c.chainId === route?.params.fromChain
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
if (cosmosSigner && chainData) {
|
|
174
|
+
try {
|
|
175
|
+
const rpc = await getWorkingCosmosRpcUrl(chainData);
|
|
176
|
+
const signingClient = await SigningCosmWasmClient.connectWithSigner(
|
|
177
|
+
rpc,
|
|
178
|
+
cosmosSigner
|
|
179
|
+
);
|
|
180
|
+
const signerAddress = (await cosmosSigner.getAccounts())[0].address;
|
|
181
|
+
|
|
182
|
+
if (signerAddress && signingClient && route) {
|
|
183
|
+
const tx = (await squid?.executeRoute({
|
|
184
|
+
signer: signingClient as any,
|
|
185
|
+
signerAddress,
|
|
186
|
+
route,
|
|
187
|
+
executionSettings: {
|
|
188
|
+
infiniteApproval: config.infiniteApproval,
|
|
189
|
+
},
|
|
190
|
+
})) as TxRaw;
|
|
191
|
+
|
|
192
|
+
// set the tx state to loading, as soon as user signed the tx
|
|
193
|
+
setTransactionState({
|
|
194
|
+
txHash: "", // We don't have the hash yet
|
|
195
|
+
route,
|
|
196
|
+
status: TransactionStatus.ONGOING,
|
|
197
|
+
sourceStatus: TransactionStatus.ONGOING,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// broadcast the signed tx to get hash and listen to events
|
|
201
|
+
const response = await signingClient.broadcastTx(
|
|
202
|
+
TxRaw.encode(tx).finish()
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const hash = response.transactionHash;
|
|
206
|
+
|
|
207
|
+
// Dispatch event so it can be listened from outside the widget
|
|
208
|
+
WidgetEvents.getInstance().dispatchSwapExecuteCall(route, hash);
|
|
209
|
+
|
|
210
|
+
const txParams: TransactionParams | undefined = setTransactionState(
|
|
211
|
+
{
|
|
212
|
+
route,
|
|
213
|
+
txHash: hash,
|
|
214
|
+
userAddress: sourceUserAddress,
|
|
215
|
+
status: TransactionStatus.ONGOING,
|
|
216
|
+
sourceStatus: TransactionStatus.ONGOING,
|
|
217
|
+
axelarUrl: undefined,
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
if (txParams) {
|
|
221
|
+
persistTransaction(txParams);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return response.code === 0;
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.log(error);
|
|
228
|
+
|
|
229
|
+
const castedError = (error as { message: string | undefined }) ?? {};
|
|
230
|
+
if (castedError.message?.includes("Request rejected")) {
|
|
231
|
+
throw new Error(castedError.message);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
throw new Error("Need all parameters");
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// If the transaction is replaced, we need to update the transaction hash
|
|
240
|
+
// Transaction replaced can mean that the user has speed up the transaction for example
|
|
241
|
+
// Could also be cancelled
|
|
242
|
+
const handleTransactionReplacementError = useCallback(
|
|
243
|
+
async ({
|
|
244
|
+
error,
|
|
245
|
+
route,
|
|
246
|
+
status,
|
|
247
|
+
sourceStatus,
|
|
248
|
+
userAddress,
|
|
249
|
+
axelarUrl,
|
|
250
|
+
}: {
|
|
251
|
+
error: any;
|
|
252
|
+
route?: RouteResponse["route"];
|
|
253
|
+
status: TransactionStatus;
|
|
254
|
+
sourceStatus: TransactionStatus;
|
|
255
|
+
userAddress?: string;
|
|
256
|
+
axelarUrl?: string;
|
|
257
|
+
}): Promise<TransactionReceipt> => {
|
|
258
|
+
if (route && error.code === Logger.errors.TRANSACTION_REPLACED) {
|
|
259
|
+
const txReplacementError = error as TransactionReplacedError;
|
|
260
|
+
|
|
261
|
+
const { hash: newHash, nonce: newNonce } =
|
|
262
|
+
txReplacementError.replacement;
|
|
263
|
+
|
|
264
|
+
if (route.transactionRequest) {
|
|
265
|
+
const txParams = setTransactionState({
|
|
266
|
+
route,
|
|
267
|
+
txHash: newHash,
|
|
268
|
+
nonce: newNonce,
|
|
269
|
+
userAddress: sourceUserAddress,
|
|
270
|
+
status,
|
|
271
|
+
sourceStatus,
|
|
272
|
+
axelarUrl: undefined,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (txParams) {
|
|
276
|
+
const newHistoryElement: TransactionHistoryStore = {
|
|
277
|
+
...txParams,
|
|
278
|
+
params: route.params,
|
|
279
|
+
estimate: route.estimate,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Need to store the new transaction hash on the previous transaction
|
|
283
|
+
usePersistStore.setState({
|
|
284
|
+
transactionsHistory: replaceTransactionAtNonce(
|
|
285
|
+
newNonce,
|
|
286
|
+
sourceUserAddress!,
|
|
287
|
+
usePersistStore.getState().transactionsHistory,
|
|
288
|
+
newHistoryElement
|
|
289
|
+
),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const response = await txReplacementError.replacement.wait();
|
|
296
|
+
return response;
|
|
297
|
+
} catch (replacementError) {
|
|
298
|
+
// Maybe the transaction was replaced again
|
|
299
|
+
// recursive call
|
|
300
|
+
return handleTransactionReplacementError({
|
|
301
|
+
error,
|
|
302
|
+
route,
|
|
303
|
+
status,
|
|
304
|
+
sourceStatus,
|
|
305
|
+
userAddress,
|
|
306
|
+
axelarUrl,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
[setTransactionState, sourceUserAddress, squid?.axelarscanURL]
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const swapQueryEvm = useMutation(async (route?: RouteResponse["route"]) => {
|
|
317
|
+
if (route && !!squid && signer.isSuccess && signer.data) {
|
|
318
|
+
const txResponse = (await squid.executeRoute({
|
|
319
|
+
signer: signer.data,
|
|
320
|
+
route,
|
|
321
|
+
executionSettings: {
|
|
322
|
+
infiniteApproval: config.infiniteApproval,
|
|
323
|
+
},
|
|
324
|
+
})) as unknown as ethers.providers.TransactionResponse;
|
|
325
|
+
|
|
326
|
+
let hash = txResponse.hash;
|
|
327
|
+
if (activeConnector?.id === "safe") {
|
|
328
|
+
hash = await getGnosisTransactionHash(txResponse.hash);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Dispatch event so it can be listened from outside the widget
|
|
332
|
+
WidgetEvents.getInstance().dispatchSwapExecuteCall(route, hash);
|
|
333
|
+
|
|
334
|
+
if (route.transactionRequest) {
|
|
335
|
+
const txParams: TransactionParams | undefined = setTransactionState({
|
|
336
|
+
route,
|
|
337
|
+
txHash: hash,
|
|
338
|
+
nonce: txResponse.nonce,
|
|
339
|
+
userAddress: sourceUserAddress,
|
|
340
|
+
status: TransactionStatus.INITIAL_LOADING,
|
|
341
|
+
sourceStatus: TransactionStatus.ONGOING,
|
|
342
|
+
axelarUrl: undefined,
|
|
343
|
+
});
|
|
344
|
+
if (txParams) {
|
|
345
|
+
persistTransaction(txParams);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const response = await txResponse.wait();
|
|
351
|
+
return response;
|
|
352
|
+
} catch (error: any) {
|
|
353
|
+
return handleTransactionReplacementError({
|
|
354
|
+
error,
|
|
355
|
+
route,
|
|
356
|
+
status: TransactionStatus.INITIAL_LOADING,
|
|
357
|
+
sourceStatus: TransactionStatus.ONGOING,
|
|
358
|
+
userAddress: sourceUserAddress,
|
|
359
|
+
axelarUrl: undefined,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
throw new Error("Need all parameters");
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Execute cross chain swap with selected tokens
|
|
368
|
+
* getRoute should be called before this mutation
|
|
369
|
+
*/
|
|
370
|
+
const swapQuery = useMutation(
|
|
371
|
+
async () => {
|
|
372
|
+
const sourceChain = chains?.find(
|
|
373
|
+
(chain) => chain.chainId == route.data?.params?.fromChain
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
if (sourceChain?.chainType === ChainType.COSMOS) {
|
|
377
|
+
return swapQueryCosmos.mutateAsync(route.data);
|
|
378
|
+
}
|
|
379
|
+
if (sourceChain?.chainType === ChainType.EVM) {
|
|
380
|
+
return swapQueryEvm.mutateAsync(route.data);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
throw new Error("Invalid parameters or chain not found");
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
onError: (error: any) => {
|
|
387
|
+
const { currentTransaction: currentTx } = useSquidStore.getState();
|
|
388
|
+
const errorObject = getTransactionError(error);
|
|
389
|
+
useSquidStore.setState({
|
|
390
|
+
currentTransaction: {
|
|
391
|
+
...currentTx!,
|
|
392
|
+
status: TransactionStatus.ERROR,
|
|
393
|
+
sourceStatus: TransactionStatus.ERROR,
|
|
394
|
+
error: errorObject,
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
if (
|
|
398
|
+
currentTx?.transactionId &&
|
|
399
|
+
errorObject?.type === TransactionErrorType.CALL_EXCEPTION
|
|
400
|
+
) {
|
|
401
|
+
usePersistStore.setState({
|
|
402
|
+
transactionsHistory: updateTransactionHistoryStatus(
|
|
403
|
+
currentTransaction?.transactionId,
|
|
404
|
+
TransactionStatus.ERROR,
|
|
405
|
+
usePersistStore.getState().transactionsHistory,
|
|
406
|
+
undefined
|
|
407
|
+
),
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
onSuccess: () => {
|
|
412
|
+
const { currentTransaction: currentTx } = useSquidStore.getState();
|
|
413
|
+
|
|
414
|
+
queryClient.invalidateQueries(keys({}).balances());
|
|
415
|
+
|
|
416
|
+
useSquidStore.setState({
|
|
417
|
+
currentTransaction: {
|
|
418
|
+
...currentTx!,
|
|
419
|
+
sourceStatus: TransactionStatus.SUCCESS,
|
|
420
|
+
status: isSameChainSwap
|
|
421
|
+
? TransactionStatus.SUCCESS
|
|
422
|
+
: TransactionStatus.ONGOING,
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
},
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
swapQuery,
|
|
431
|
+
currentTransaction,
|
|
432
|
+
fromToken,
|
|
433
|
+
toToken,
|
|
434
|
+
fromPrice,
|
|
435
|
+
toPrice,
|
|
436
|
+
toChain,
|
|
437
|
+
fromChain,
|
|
438
|
+
executionState,
|
|
439
|
+
};
|
|
440
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useSquidStore } from "@/hooks/store/useSquidStore";
|
|
2
|
+
import { useSwap } from "@/hooks/swap/useSwap";
|
|
3
|
+
import { useUserParams } from "@/hooks/user/useUserParams";
|
|
4
|
+
import { formatSeconds } from "@/services/internal/transactionService";
|
|
5
|
+
import { useMemo } from "react";
|
|
6
|
+
|
|
7
|
+
export const useEstimateExpress = () => {
|
|
8
|
+
const { expressSupportedForThisRoute } = useUserParams();
|
|
9
|
+
const { fromChain } = useSwap();
|
|
10
|
+
const { config } = useSquidStore();
|
|
11
|
+
|
|
12
|
+
const backendSupportingExpress = useMemo(
|
|
13
|
+
() => fromChain?.enableBoostByDefault,
|
|
14
|
+
[fromChain]
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const expressActivatedUI = useMemo(
|
|
18
|
+
() =>
|
|
19
|
+
expressSupportedForThisRoute &&
|
|
20
|
+
config.enableExpress &&
|
|
21
|
+
backendSupportingExpress,
|
|
22
|
+
[
|
|
23
|
+
config.enableExpress,
|
|
24
|
+
backendSupportingExpress,
|
|
25
|
+
expressSupportedForThisRoute,
|
|
26
|
+
]
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const transactionTimeEstimate = useMemo(
|
|
30
|
+
() =>
|
|
31
|
+
expressSupportedForThisRoute && config.enableExpress
|
|
32
|
+
? formatSeconds(20, "s", "min")
|
|
33
|
+
: formatSeconds(fromChain?.estimatedRouteDuration || 0, "s", "min"),
|
|
34
|
+
[
|
|
35
|
+
config.enableExpress,
|
|
36
|
+
fromChain?.estimatedRouteDuration,
|
|
37
|
+
expressSupportedForThisRoute,
|
|
38
|
+
]
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
expressSupportedForThisRoute,
|
|
43
|
+
transactionTimeEstimate,
|
|
44
|
+
expressActivatedUI,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DexName,
|
|
3
|
+
FallbackAddress,
|
|
4
|
+
RouteRequest,
|
|
5
|
+
Token,
|
|
6
|
+
} from "@0xsquid/sdk/dist/types";
|
|
7
|
+
|
|
8
|
+
import { osmosisChainId } from "@/core/constants";
|
|
9
|
+
import { useCosmosContext } from "@/core/providers/CosmosProvider";
|
|
10
|
+
import { useMutation } from "@tanstack/react-query";
|
|
11
|
+
import { constants, utils } from "ethers";
|
|
12
|
+
import { useCallback } from "react";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
useSquidStore,
|
|
16
|
+
useSwapRoutePersistStore,
|
|
17
|
+
} from "@/hooks/store/useSquidStore";
|
|
18
|
+
import { useUserParams } from "@/hooks/user/useUserParams";
|
|
19
|
+
|
|
20
|
+
export const useGetRoute = () => {
|
|
21
|
+
const { getCosmosAddressForChain, isConnected: isCosmosConnected } =
|
|
22
|
+
useCosmosContext();
|
|
23
|
+
const { squid, config } = useSquidStore();
|
|
24
|
+
const { swapRoute } = useSwapRoutePersistStore();
|
|
25
|
+
const { expressEnabled, gasEnabled } = useUserParams();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get fallback addresses for cosmos chains
|
|
29
|
+
* This is needed by backend when a cosmos swap occurs between non coin118 chains
|
|
30
|
+
* The backend might need a coin118 address to send the funds to in case of failure for a swap happening between the two chains
|
|
31
|
+
*/
|
|
32
|
+
const getCosmosFallbackAddresses = useCallback(async () => {
|
|
33
|
+
// If not connected, user might have filled it manually
|
|
34
|
+
if (!isCosmosConnected) {
|
|
35
|
+
return swapRoute?.fallbackAddress
|
|
36
|
+
? ([
|
|
37
|
+
{
|
|
38
|
+
address: swapRoute?.fallbackAddress,
|
|
39
|
+
coinType: 118,
|
|
40
|
+
},
|
|
41
|
+
// TODO: Temp parsing here, should be included in v2 sdk
|
|
42
|
+
] as FallbackAddress[])
|
|
43
|
+
: undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// the user is connected to cosmos, we can get the fallback address from the wallet
|
|
47
|
+
// We only need coin118, so taking osmosis hub address by default
|
|
48
|
+
const osmosisAddress = await getCosmosAddressForChain(
|
|
49
|
+
osmosisChainId.mainnet
|
|
50
|
+
);
|
|
51
|
+
if (!osmosisAddress) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Always used the one set by user (if any), otherwise use the derived osmo address
|
|
56
|
+
// only return something if one of the fallback address is defined
|
|
57
|
+
if (!!swapRoute?.fallbackAddress || !!osmosisAddress) {
|
|
58
|
+
return [
|
|
59
|
+
{
|
|
60
|
+
address: swapRoute?.fallbackAddress ?? osmosisAddress,
|
|
61
|
+
coinType: 118,
|
|
62
|
+
},
|
|
63
|
+
] as FallbackAddress[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return undefined;
|
|
67
|
+
}, [getCosmosAddressForChain, isCosmosConnected, swapRoute?.fallbackAddress]);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Fetching route data from the API
|
|
71
|
+
* These data will be used to trigger the transaction
|
|
72
|
+
* @returns {Route} Route data
|
|
73
|
+
*/
|
|
74
|
+
return useMutation(
|
|
75
|
+
async ({
|
|
76
|
+
fromChain,
|
|
77
|
+
toChain,
|
|
78
|
+
fromToken,
|
|
79
|
+
toToken,
|
|
80
|
+
sourceUserAddress,
|
|
81
|
+
destinationAddress,
|
|
82
|
+
fromPrice,
|
|
83
|
+
}: {
|
|
84
|
+
fromChain: string;
|
|
85
|
+
toChain: string;
|
|
86
|
+
fromToken: Token;
|
|
87
|
+
toToken: Token;
|
|
88
|
+
sourceUserAddress: string;
|
|
89
|
+
destinationAddress: string;
|
|
90
|
+
fromPrice: string;
|
|
91
|
+
}) => {
|
|
92
|
+
const quoteOnly =
|
|
93
|
+
sourceUserAddress === undefined || destinationAddress === undefined;
|
|
94
|
+
|
|
95
|
+
const isEvmSwap = Number(fromChain) > 0 && Number(toChain) > 0;
|
|
96
|
+
|
|
97
|
+
const cosmosFallbackAddresses =
|
|
98
|
+
quoteOnly || isEvmSwap ? undefined : await getCosmosFallbackAddresses();
|
|
99
|
+
|
|
100
|
+
// TODO: Put RouteRequest back when it supports fallbackAddress
|
|
101
|
+
const params: RouteRequest = {
|
|
102
|
+
fromChain,
|
|
103
|
+
fromToken: fromToken.address,
|
|
104
|
+
fromAddress: sourceUserAddress ?? constants.AddressZero,
|
|
105
|
+
fromAmount: utils
|
|
106
|
+
.parseUnits(fromPrice?.toString() ?? "0", fromToken?.decimals)
|
|
107
|
+
.toString(),
|
|
108
|
+
toChain,
|
|
109
|
+
toToken: toToken.address,
|
|
110
|
+
toAddress: destinationAddress ?? "",
|
|
111
|
+
quoteOnly:
|
|
112
|
+
sourceUserAddress === undefined || destinationAddress === undefined,
|
|
113
|
+
slippageConfig: {
|
|
114
|
+
// We set slippage to undefined because automode will be picked if slippage is undefined
|
|
115
|
+
// Slippage to 1 = auto mode
|
|
116
|
+
slippage: config.slippage === 1 ? undefined : config.slippage,
|
|
117
|
+
autoMode: 1,
|
|
118
|
+
},
|
|
119
|
+
enableBoost: expressEnabled,
|
|
120
|
+
prefer: config.preferDex as DexName[] | undefined,
|
|
121
|
+
receiveGasOnDestination: gasEnabled,
|
|
122
|
+
onChainQuoting: !!config.onChainQuoting,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// If the swap is involving cosmos chains, we need to add the fallback addresses (if any)
|
|
126
|
+
if (
|
|
127
|
+
cosmosFallbackAddresses &&
|
|
128
|
+
cosmosFallbackAddresses.length > 0 &&
|
|
129
|
+
cosmosFallbackAddresses[0].address
|
|
130
|
+
) {
|
|
131
|
+
params.fallbackAddresses = cosmosFallbackAddresses;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const { route, requestId } = await squid!.getRoute({
|
|
135
|
+
...params,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
useSquidStore.setState({
|
|
139
|
+
currentRequestId: requestId,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return route;
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
};
|