@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,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
|
+
};
|