@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,63 @@
|
|
|
1
|
+
import { useSquidStore } from "@/hooks/store/useSquidStore";
|
|
2
|
+
import { useSwap } from "@/hooks/swap/useSwap";
|
|
3
|
+
import { ChainType } from "@0xsquid/sdk/dist/types";
|
|
4
|
+
import { useMemo } from "react";
|
|
5
|
+
|
|
6
|
+
export const useUserParams = () => {
|
|
7
|
+
const { config } = useSquidStore();
|
|
8
|
+
const { fromToken, toToken, toChain, fromChain } = useSwap();
|
|
9
|
+
|
|
10
|
+
// =============
|
|
11
|
+
// GAS
|
|
12
|
+
// =============
|
|
13
|
+
const getGasOnDestSupportedForThisRoute = useMemo(
|
|
14
|
+
() =>
|
|
15
|
+
// Not supporting get gas on dest for same chains
|
|
16
|
+
fromChain?.chainId !== toChain?.chainId &&
|
|
17
|
+
// If the destination chain is cosmos, we don't support getting gas there
|
|
18
|
+
toChain?.chainType !== ChainType.COSMOS &&
|
|
19
|
+
// Not supporting get gas on dest for same tokens (bridge)
|
|
20
|
+
|
|
21
|
+
((fromToken?.subGraphIds?.some(
|
|
22
|
+
(sgi) => !!toToken?.subGraphIds?.includes(sgi)
|
|
23
|
+
) &&
|
|
24
|
+
toToken?.subGraphIds?.some(
|
|
25
|
+
(sgi) => !!fromToken?.subGraphIds?.includes(sgi)
|
|
26
|
+
)) ||
|
|
27
|
+
// Except for uusdc -> uusdc
|
|
28
|
+
(fromToken?.subGraphIds?.includes("uusdc") &&
|
|
29
|
+
toToken?.subGraphIds?.includes("uusdc"))),
|
|
30
|
+
[
|
|
31
|
+
fromChain?.chainId,
|
|
32
|
+
fromToken?.subGraphIds,
|
|
33
|
+
toChain?.chainId,
|
|
34
|
+
toToken?.subGraphIds,
|
|
35
|
+
toChain?.chainType,
|
|
36
|
+
]
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const gasEnabled = useMemo(
|
|
40
|
+
() => config.enableGetGasOnDestination && getGasOnDestSupportedForThisRoute,
|
|
41
|
+
[config.enableGetGasOnDestination, getGasOnDestSupportedForThisRoute]
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// =============
|
|
45
|
+
// BOOST
|
|
46
|
+
// =============
|
|
47
|
+
const expressSupportedForThisRoute = useMemo(
|
|
48
|
+
() => toChain?.chainType !== ChainType.COSMOS,
|
|
49
|
+
[toChain?.chainType]
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const expressEnabled = useMemo(
|
|
53
|
+
() => expressSupportedForThisRoute && config.enableExpress,
|
|
54
|
+
[config.enableExpress, expressSupportedForThisRoute]
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
gasEnabled,
|
|
59
|
+
expressEnabled,
|
|
60
|
+
expressSupportedForThisRoute,
|
|
61
|
+
getGasOnDestSupportedForThisRoute,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ChainData, Token } from "@0xsquid/sdk/dist/types";
|
|
2
|
+
import { ChainType } from "@0xsquid/sdk/dist/types";
|
|
3
|
+
import { useMutation } from "@tanstack/react-query";
|
|
4
|
+
import { useAccount, useNetwork, useSwitchNetwork } from "wagmi";
|
|
5
|
+
|
|
6
|
+
export const useAddToken = (
|
|
7
|
+
chainToCompare: ChainData | undefined,
|
|
8
|
+
tokenToCompare: Token | undefined
|
|
9
|
+
) => {
|
|
10
|
+
const { chain: currentEvmChain } = useNetwork();
|
|
11
|
+
const { connector } = useAccount();
|
|
12
|
+
|
|
13
|
+
const { switchNetworkAsync } = useSwitchNetwork({
|
|
14
|
+
throwForSwitchChainNotSupported: true,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Add token to wallet
|
|
19
|
+
*/
|
|
20
|
+
const addToken = useMutation(async (tokenToAdd: Token | void) => {
|
|
21
|
+
const token = tokenToAdd ?? tokenToCompare;
|
|
22
|
+
if (token && chainToCompare?.chainType === ChainType.EVM) {
|
|
23
|
+
const provider = await connector?.getProvider();
|
|
24
|
+
|
|
25
|
+
// Switch network if needed
|
|
26
|
+
if (currentEvmChain?.id.toString() !== token?.chainId) {
|
|
27
|
+
await switchNetworkAsync?.(+token.chainId);
|
|
28
|
+
// Metamask is not popping the second modal if we don't wait a bit
|
|
29
|
+
// eslint-disable-next-line no-promise-executor-return
|
|
30
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
31
|
+
}
|
|
32
|
+
// Add token to wallet
|
|
33
|
+
provider.request({
|
|
34
|
+
method: "wallet_watchAsset",
|
|
35
|
+
params: {
|
|
36
|
+
type: "ERC20",
|
|
37
|
+
options: {
|
|
38
|
+
address: token?.address,
|
|
39
|
+
symbol: token?.symbol,
|
|
40
|
+
decimals: token?.decimals,
|
|
41
|
+
image: token?.logoURI,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// TODO: Implement keplr add token
|
|
47
|
+
return false;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
addToken,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { PriorityConnectors } from "@/core/constants";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { useConnect } from "wagmi";
|
|
4
|
+
|
|
5
|
+
const PRIORITY_CONNECTOR_ID = [PriorityConnectors.Safe];
|
|
6
|
+
|
|
7
|
+
const WAGMI_LOCAL_STORAGE_WALLET_ID = "wagmi.wallet";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Auto connect to Safe provider if it's present
|
|
11
|
+
* Safe will always be the priority connector
|
|
12
|
+
* Because if it's present, this means the user is on a Safe app iframe
|
|
13
|
+
*/
|
|
14
|
+
export const useAutoConnect = () => {
|
|
15
|
+
const { connect, connectors } = useConnect();
|
|
16
|
+
|
|
17
|
+
const wagmiLocalStorageWalletID = localStorage.getItem(
|
|
18
|
+
WAGMI_LOCAL_STORAGE_WALLET_ID
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const queryParameters = new URLSearchParams(window.location.search);
|
|
23
|
+
// TODO: At the moment gnosis was published without the embed parameter in the manifest,
|
|
24
|
+
// but we should ask gnosis to change our app url to https://app.squidrouter.com/?embed="safe"
|
|
25
|
+
// So we could remove the priority connector array and just use the embed parameter
|
|
26
|
+
const embedType = queryParameters.get("embed") as PriorityConnectors;
|
|
27
|
+
let priorityConnector: any;
|
|
28
|
+
|
|
29
|
+
if (embedType) {
|
|
30
|
+
priorityConnector = connectors.find((c) => c.id === embedType);
|
|
31
|
+
} else {
|
|
32
|
+
priorityConnector = connectors.find(
|
|
33
|
+
(c) =>
|
|
34
|
+
PRIORITY_CONNECTOR_ID.includes(c.id as PriorityConnectors) && c.ready
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Connect priority provider if it's present
|
|
39
|
+
// Otherwise, connect to the last wallet used if authorized
|
|
40
|
+
if (priorityConnector) {
|
|
41
|
+
connect({ connector: priorityConnector });
|
|
42
|
+
} else if (wagmiLocalStorageWalletID) {
|
|
43
|
+
const wagmiLocalStorageWalletConnector = connectors.find(
|
|
44
|
+
(c) => c.id === JSON.parse(wagmiLocalStorageWalletID) && c.ready
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// If we do not check this, the wallet will try to connect even if the user disconnected on purpose
|
|
48
|
+
// Checking this will prevent the wallet to popup at each page refresh if the user is disconnected
|
|
49
|
+
wagmiLocalStorageWalletConnector?.isAuthorized().then((isAuthorized) => {
|
|
50
|
+
if (isAuthorized) {
|
|
51
|
+
connect({ connector: wagmiLocalStorageWalletConnector });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}, [connect, connectors]);
|
|
56
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useSwapRoutePersistStore } from "@/hooks/store/useSquidStore";
|
|
2
|
+
import { useSwap } from "@/hooks/swap/useSwap";
|
|
3
|
+
import { useMultiChainWallet } from "@/hooks/wallet/useMultiChainWallet";
|
|
4
|
+
import SafeAppsSDK from "@safe-global/safe-apps-sdk";
|
|
5
|
+
import { TransactionStatus as GnosisTransactionStatus } from "@safe-global/safe-apps-sdk/dist/src/types";
|
|
6
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
7
|
+
import { useAccount } from "wagmi";
|
|
8
|
+
|
|
9
|
+
export const useGnosisContext = () => {
|
|
10
|
+
const { connector } = useAccount();
|
|
11
|
+
const { fromChain } = useSwap();
|
|
12
|
+
const { swapRoute } = useSwapRoutePersistStore();
|
|
13
|
+
const { connectedAddress } = useMultiChainWallet(fromChain);
|
|
14
|
+
const [isGnosisContext, setisGnosisContext] = useState(false);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Method that will be used to send transaction
|
|
18
|
+
* TODO: could have loaded the sdk when app load and stored globally
|
|
19
|
+
*/
|
|
20
|
+
const getGnosisSafeContext = useCallback(async () => {
|
|
21
|
+
const appsSdk = new SafeAppsSDK();
|
|
22
|
+
const safe = await appsSdk.safe.getInfo();
|
|
23
|
+
const isSafeContext =
|
|
24
|
+
safe.chainId !== undefined &&
|
|
25
|
+
safe.safeAddress !== undefined &&
|
|
26
|
+
connector?.id === "safe";
|
|
27
|
+
|
|
28
|
+
setisGnosisContext(isSafeContext);
|
|
29
|
+
|
|
30
|
+
if (isSafeContext) return appsSdk;
|
|
31
|
+
return undefined;
|
|
32
|
+
}, [connector]);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
getGnosisSafeContext();
|
|
36
|
+
}, [connector]);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if we are in a Gnosis Safe Context
|
|
40
|
+
* And if source wallet address = destination address
|
|
41
|
+
* If swapRoute.destinationAddress is not defined, it means that it's the same from the source
|
|
42
|
+
*/
|
|
43
|
+
const isSameAddressAndGnosisContext = useMemo(() => {
|
|
44
|
+
const destAddressSameAsSource =
|
|
45
|
+
connectedAddress === swapRoute?.destinationAddress ||
|
|
46
|
+
swapRoute?.destinationAddress === undefined;
|
|
47
|
+
|
|
48
|
+
return isGnosisContext && destAddressSameAsSource;
|
|
49
|
+
}, [connectedAddress, swapRoute, isGnosisContext]);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* There's a specific way to get the transaction hash for the safe connector
|
|
53
|
+
* Using then initial hash received by the transaction, we can get the real hash
|
|
54
|
+
* @param hashReceived
|
|
55
|
+
* @returns
|
|
56
|
+
*/
|
|
57
|
+
const getGnosisTransactionHash = async (
|
|
58
|
+
initialHash: string
|
|
59
|
+
): Promise<string> => {
|
|
60
|
+
const safeSdk = await getGnosisSafeContext();
|
|
61
|
+
|
|
62
|
+
const tx = await safeSdk?.txs.getBySafeTxHash(initialHash);
|
|
63
|
+
const status: GnosisTransactionStatus | undefined = tx?.txStatus;
|
|
64
|
+
if (
|
|
65
|
+
status !== GnosisTransactionStatus.FAILED &&
|
|
66
|
+
status !== GnosisTransactionStatus.SUCCESS &&
|
|
67
|
+
status !== GnosisTransactionStatus.CANCELLED
|
|
68
|
+
) {
|
|
69
|
+
// Workaround, Wait 2 seconds before checking the gnosis status again
|
|
70
|
+
// TODO: There might be a better way to do this
|
|
71
|
+
// eslint-disable-next-line no-promise-executor-return
|
|
72
|
+
await new Promise((res) => setTimeout(res, 2000));
|
|
73
|
+
return getGnosisTransactionHash(initialHash);
|
|
74
|
+
}
|
|
75
|
+
return tx?.txHash ?? initialHash;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
getGnosisSafeContext,
|
|
80
|
+
isSameAddressAndGnosisContext,
|
|
81
|
+
isGnosisContext,
|
|
82
|
+
getGnosisTransactionHash,
|
|
83
|
+
};
|
|
84
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { PriorityConnectors } from "@/core/constants";
|
|
2
|
+
import { useGnosisContext } from "@/hooks/wallet/useGnosisContext";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
|
|
5
|
+
export const useIntegratorContext = () => {
|
|
6
|
+
const { isGnosisContext } = useGnosisContext();
|
|
7
|
+
/**
|
|
8
|
+
* Check if the wallet is handled externally
|
|
9
|
+
* Example: Ledger or Gnosis Safe
|
|
10
|
+
* Either by the embed parameter or by Gnosis context
|
|
11
|
+
*/
|
|
12
|
+
const walletHandledExternally: boolean = useMemo(() => {
|
|
13
|
+
const embedTypesHavingExternalWallet: PriorityConnectors[] = [
|
|
14
|
+
PriorityConnectors.LedgerLive,
|
|
15
|
+
PriorityConnectors.Safe,
|
|
16
|
+
];
|
|
17
|
+
const queryParameters = new URLSearchParams(window.location.search);
|
|
18
|
+
const embedType = queryParameters.get("embed") as PriorityConnectors;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
isGnosisContext || embedTypesHavingExternalWallet.includes(embedType)
|
|
22
|
+
);
|
|
23
|
+
}, [isGnosisContext]);
|
|
24
|
+
|
|
25
|
+
const isEmbed = useMemo(() => {
|
|
26
|
+
const queryParameters = new URLSearchParams(window.location.search);
|
|
27
|
+
const embedType = queryParameters.get("embed") as PriorityConnectors;
|
|
28
|
+
return !!embedType;
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* It's important to know if we can use certain features such as
|
|
33
|
+
* the clipboard reading
|
|
34
|
+
*/
|
|
35
|
+
const widgetInIframe = useMemo(() => {
|
|
36
|
+
try {
|
|
37
|
+
return window.self !== window.top;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
return { walletHandledExternally, isEmbed, widgetInIframe };
|
|
44
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { useCosmosContext } from "@/core/providers/CosmosProvider";
|
|
2
|
+
import type { AddEthereumChainParameter } from "@/core/types/wallet";
|
|
3
|
+
import { useCosmosForChain } from "@/hooks/cosmos/useCosmosForChain";
|
|
4
|
+
import { formatWalletAddress } from "@/services/internal/walletService";
|
|
5
|
+
import type { ChainData } from "@0xsquid/sdk/dist/types";
|
|
6
|
+
import { ChainType } from "@0xsquid/sdk/dist/types";
|
|
7
|
+
import { useMutation } from "@tanstack/react-query";
|
|
8
|
+
import { AddChainError, UserRejectedRequestError } from "@wagmi/core";
|
|
9
|
+
import { ethers } from "ethers";
|
|
10
|
+
import { useMemo } from "react";
|
|
11
|
+
import {
|
|
12
|
+
ChainNotConfiguredError,
|
|
13
|
+
SwitchChainError,
|
|
14
|
+
useAccount,
|
|
15
|
+
useNetwork,
|
|
16
|
+
useSwitchNetwork,
|
|
17
|
+
} from "wagmi";
|
|
18
|
+
|
|
19
|
+
export const useMultiChainWallet = (chainToCompare: ChainData | undefined) => {
|
|
20
|
+
const { chain: currentEvmChain } = useNetwork();
|
|
21
|
+
const { isConnected: isEvmConnected, connector, address } = useAccount();
|
|
22
|
+
const { isConnected: cosmosIsConnected } = useCosmosContext();
|
|
23
|
+
const { cosmosAddress } = useCosmosForChain(chainToCompare);
|
|
24
|
+
|
|
25
|
+
const { switchNetworkAsync } = useSwitchNetwork({
|
|
26
|
+
throwForSwitchChainNotSupported: true,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get connected address, depends on chainType
|
|
31
|
+
*/
|
|
32
|
+
const connectedAddress = useMemo(() => {
|
|
33
|
+
switch (chainToCompare?.chainType) {
|
|
34
|
+
case ChainType.EVM:
|
|
35
|
+
return address;
|
|
36
|
+
case ChainType.COSMOS:
|
|
37
|
+
return cosmosAddress;
|
|
38
|
+
default:
|
|
39
|
+
return address;
|
|
40
|
+
}
|
|
41
|
+
}, [address, chainToCompare?.chainType, cosmosAddress]);
|
|
42
|
+
|
|
43
|
+
const parsedAddress = useMemo(
|
|
44
|
+
() => formatWalletAddress(connectedAddress),
|
|
45
|
+
[connectedAddress]
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Change current network for desired chain
|
|
50
|
+
*/
|
|
51
|
+
const changeNetwork = useMutation(
|
|
52
|
+
async () => {
|
|
53
|
+
// throw for tenderly fork chain addition
|
|
54
|
+
if (chainToCompare?.rpc.includes("tenderly")) {
|
|
55
|
+
throw new SwitchChainError("Add tenderly chain");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (chainToCompare?.chainType === ChainType.EVM) {
|
|
59
|
+
await switchNetworkAsync?.(+chainToCompare.chainId);
|
|
60
|
+
}
|
|
61
|
+
// Implement keplr change network
|
|
62
|
+
// Looks like there are no method to do that at the moment
|
|
63
|
+
return false;
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
onError: async (error: any) => {
|
|
67
|
+
if (error instanceof UserRejectedRequestError) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (
|
|
71
|
+
(error instanceof ChainNotConfiguredError ||
|
|
72
|
+
error instanceof SwitchChainError ||
|
|
73
|
+
error instanceof AddChainError) &&
|
|
74
|
+
chainToCompare
|
|
75
|
+
) {
|
|
76
|
+
const provider = await connector?.getProvider();
|
|
77
|
+
const chainName = chainToCompare.rpc.includes("tenderly")
|
|
78
|
+
? `${chainToCompare.networkName} Tenderly fork`
|
|
79
|
+
: chainToCompare.networkName;
|
|
80
|
+
const chainParameters: AddEthereumChainParameter = {
|
|
81
|
+
chainId: ethers.utils.hexValue(
|
|
82
|
+
parseInt(chainToCompare.chainId, 10)
|
|
83
|
+
),
|
|
84
|
+
chainName,
|
|
85
|
+
nativeCurrency: chainToCompare.nativeCurrency,
|
|
86
|
+
rpcUrls: [chainToCompare.rpc],
|
|
87
|
+
blockExplorerUrls: chainToCompare.blockExplorerUrls,
|
|
88
|
+
iconUrls: [chainToCompare.chainIconURI],
|
|
89
|
+
};
|
|
90
|
+
provider.request({
|
|
91
|
+
method: "wallet_addEthereumChain",
|
|
92
|
+
params: [chainParameters],
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Handle multiple chains
|
|
101
|
+
*/
|
|
102
|
+
const networkConnected = useMemo(() => {
|
|
103
|
+
switch (chainToCompare?.chainType) {
|
|
104
|
+
case ChainType.EVM:
|
|
105
|
+
return isEvmConnected;
|
|
106
|
+
case ChainType.COSMOS:
|
|
107
|
+
return cosmosIsConnected;
|
|
108
|
+
default:
|
|
109
|
+
return isEvmConnected;
|
|
110
|
+
}
|
|
111
|
+
}, [isEvmConnected, chainToCompare, cosmosIsConnected]);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Checks if Network is connected and with the right chain
|
|
115
|
+
*/
|
|
116
|
+
const networkConnectedOnRightChain = useMemo(() => {
|
|
117
|
+
if (chainToCompare?.chainType === ChainType.EVM) {
|
|
118
|
+
// tenderly validation based on rpc
|
|
119
|
+
if (
|
|
120
|
+
currentEvmChain?.rpcUrls?.default?.http?.length &&
|
|
121
|
+
currentEvmChain?.rpcUrls?.default?.http[0].includes("tenderly")
|
|
122
|
+
) {
|
|
123
|
+
return (
|
|
124
|
+
isEvmConnected &&
|
|
125
|
+
currentEvmChain?.rpcUrls?.default?.http[0].toLowerCase() ===
|
|
126
|
+
chainToCompare.rpc.toLowerCase()
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
isEvmConnected &&
|
|
132
|
+
currentEvmChain?.id.toString() === chainToCompare.chainId
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
// TODO: Implement keplr check
|
|
136
|
+
return true;
|
|
137
|
+
}, [isEvmConnected, currentEvmChain, chainToCompare]);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
changeNetwork,
|
|
141
|
+
networkConnected,
|
|
142
|
+
networkConnectedOnRightChain,
|
|
143
|
+
connectedAddress,
|
|
144
|
+
parsedAddress,
|
|
145
|
+
};
|
|
146
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { wallets } from "@/core/constants";
|
|
2
|
+
import { useCosmosContext } from "@/core/providers/CosmosProvider";
|
|
3
|
+
import type { Wallet } from "@/core/types/wallet";
|
|
4
|
+
import { useSquidChains } from "@/hooks/chains/useSquidChains";
|
|
5
|
+
import { useSquidRouter } from "@/hooks/webapp-router/useSquidRouter";
|
|
6
|
+
import {
|
|
7
|
+
formatChainsForWagmi,
|
|
8
|
+
redirectExtensionStoreIfNotInstalled,
|
|
9
|
+
} from "@/services/internal/walletService";
|
|
10
|
+
import type { ChainData } from "@0xsquid/sdk/dist/types";
|
|
11
|
+
import { ChainType } from "@0xsquid/sdk/dist/types";
|
|
12
|
+
import { useMemo } from "react";
|
|
13
|
+
import { useAccount, useConnect, useDisconnect } from "wagmi";
|
|
14
|
+
|
|
15
|
+
export const useWallet = (chain: ChainData | undefined) => {
|
|
16
|
+
const { connector: activeConnector, isConnected: isEvmConnected } =
|
|
17
|
+
useAccount();
|
|
18
|
+
|
|
19
|
+
const { disconnectAsync: disconnectEvm } = useDisconnect();
|
|
20
|
+
const {
|
|
21
|
+
isConnected: cosmosIsConnected,
|
|
22
|
+
connectCosmos,
|
|
23
|
+
cosmosConnectedWallet,
|
|
24
|
+
} = useCosmosContext();
|
|
25
|
+
const { connectAsync } = useConnect();
|
|
26
|
+
const { previousRoute } = useSquidRouter();
|
|
27
|
+
const { chains } = useSquidChains();
|
|
28
|
+
|
|
29
|
+
const connectWallet = async (
|
|
30
|
+
wallet: Wallet,
|
|
31
|
+
redirect?: boolean,
|
|
32
|
+
direction?: "from" | "to"
|
|
33
|
+
) => {
|
|
34
|
+
redirectExtensionStoreIfNotInstalled(wallet);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
if (wallet && wallet.connector && wallet.type === ChainType.EVM) {
|
|
38
|
+
await disconnectEvm();
|
|
39
|
+
await connectAsync({
|
|
40
|
+
connector: wallet.connector(formatChainsForWagmi(chains)),
|
|
41
|
+
});
|
|
42
|
+
} else if (wallet.type === ChainType.COSMOS && chain) {
|
|
43
|
+
await connectCosmos?.mutateAsync({ chain, wallet, direction });
|
|
44
|
+
}
|
|
45
|
+
if (redirect) {
|
|
46
|
+
previousRoute();
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error(error);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get the connected wallet object
|
|
55
|
+
* @returns {Wallet | undefined}
|
|
56
|
+
*/
|
|
57
|
+
const currentWallet = useMemo(() => {
|
|
58
|
+
if (chain?.chainType === ChainType.EVM) {
|
|
59
|
+
if (activeConnector?.id && isEvmConnected) {
|
|
60
|
+
const activeWallet = wallets.find(
|
|
61
|
+
(w) => w.connectorId === activeConnector.id
|
|
62
|
+
);
|
|
63
|
+
return activeWallet;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (chain?.chainType === ChainType.COSMOS) {
|
|
67
|
+
if (cosmosIsConnected) {
|
|
68
|
+
return wallets.find(
|
|
69
|
+
(w) => w.connectorId === cosmosConnectedWallet?.connectorId
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}, [
|
|
75
|
+
activeConnector?.id,
|
|
76
|
+
chain?.chainType,
|
|
77
|
+
cosmosConnectedWallet?.connectorId,
|
|
78
|
+
cosmosIsConnected,
|
|
79
|
+
isEvmConnected,
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
currentWallet,
|
|
84
|
+
connectWallet,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { WidgetRoute } from "@/core/types/route";
|
|
2
|
+
import { useSquidRouterStore } from "@/hooks/store/useSquidStore";
|
|
3
|
+
|
|
4
|
+
export const useSquidRouter = () => {
|
|
5
|
+
const { history } = useSquidRouterStore();
|
|
6
|
+
|
|
7
|
+
const switchRoute = (
|
|
8
|
+
route: WidgetRoute,
|
|
9
|
+
params?: { [key: string]: any | undefined } | undefined,
|
|
10
|
+
addRouteToHistory = true
|
|
11
|
+
) => {
|
|
12
|
+
const currentHistory = useSquidRouterStore.getState().history;
|
|
13
|
+
if (addRouteToHistory) {
|
|
14
|
+
useSquidRouterStore.setState({
|
|
15
|
+
history: [...currentHistory, { route, params }],
|
|
16
|
+
});
|
|
17
|
+
} else {
|
|
18
|
+
useSquidRouterStore.setState({
|
|
19
|
+
history: [...currentHistory.slice(0, -1), { route, params }],
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const previousRoute = () => {
|
|
25
|
+
const currentHistory = useSquidRouterStore.getState().history;
|
|
26
|
+
currentHistory.pop();
|
|
27
|
+
|
|
28
|
+
useSquidRouterStore.setState({
|
|
29
|
+
history: currentHistory,
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const currentRoute = history[history.length - 1].route;
|
|
34
|
+
const currentRouteParams = history[history.length - 1].params;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
currentRoute,
|
|
38
|
+
switchRoute,
|
|
39
|
+
previousRoute,
|
|
40
|
+
currentRouteParams,
|
|
41
|
+
};
|
|
42
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { wallets } from "@/core/constants";
|
|
2
|
+
import { CosmosProvider } from "@/core/providers/CosmosProvider";
|
|
3
|
+
import { defaultOptions } from "@/core/queries/react-query-config";
|
|
4
|
+
import { useSquidStore } from "@/hooks/store/useSquidStore";
|
|
5
|
+
import { getConfigWithDefaults } from "@/services/internal/configService";
|
|
6
|
+
import { formatChainsForWagmi } from "@/services/internal/walletService";
|
|
7
|
+
import { Squid } from "@0xsquid/sdk";
|
|
8
|
+
import { ChainData, ChainType } from "@0xsquid/squid-types";
|
|
9
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
10
|
+
import { createContext, useEffect, useState } from "react";
|
|
11
|
+
import React from "react";
|
|
12
|
+
import { WagmiConfig, configureChains, createClient } from "wagmi";
|
|
13
|
+
import { jsonRpcProvider } from "wagmi/providers/jsonRpc";
|
|
14
|
+
|
|
15
|
+
interface Config {
|
|
16
|
+
integratorId: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ISdkProviderProps {
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
config: Config;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const SquidContext = createContext(undefined);
|
|
25
|
+
|
|
26
|
+
const verifyIntegratorIdValidity = (integratorId: string) => {
|
|
27
|
+
if (!integratorId) {
|
|
28
|
+
throw new Error("Integrator ID is required");
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const SquidProvider: React.FC<ISdkProviderProps> = ({
|
|
33
|
+
children,
|
|
34
|
+
config,
|
|
35
|
+
}: ISdkProviderProps) => {
|
|
36
|
+
console.log("SquidProvider");
|
|
37
|
+
verifyIntegratorIdValidity(config.integratorId);
|
|
38
|
+
|
|
39
|
+
const queryClient = new QueryClient({ defaultOptions });
|
|
40
|
+
const [wagmiClient, setWagmiClient] = useState<any>(null);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Initialize Wagmi client
|
|
44
|
+
* @param chains
|
|
45
|
+
*/
|
|
46
|
+
const initWagmiClient = (chains: ChainData[]) => {
|
|
47
|
+
const { provider, chains: wagmiChains } = configureChains(
|
|
48
|
+
formatChainsForWagmi(chains),
|
|
49
|
+
[
|
|
50
|
+
jsonRpcProvider({
|
|
51
|
+
rpc: (chain) => ({
|
|
52
|
+
http: chain.rpcUrls.default.http[0] ?? "",
|
|
53
|
+
webSocket: undefined,
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
56
|
+
]
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Get formatted connectors from Squid supported wallets
|
|
60
|
+
const evmWalletConnectors = wallets
|
|
61
|
+
.filter((w) => w.type === ChainType.EVM)
|
|
62
|
+
.map((w) => w.connector?.(wagmiChains));
|
|
63
|
+
|
|
64
|
+
const wClient = createClient({
|
|
65
|
+
persister: null,
|
|
66
|
+
provider,
|
|
67
|
+
connectors: [...evmWalletConnectors],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
setWagmiClient(wClient);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Initialize Squid SDK & wagmi client
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
const initializeSdk = async () => {
|
|
76
|
+
const squid = new Squid({
|
|
77
|
+
integratorId: config.integratorId,
|
|
78
|
+
baseUrl: "https://v2.api.squidrouter.com",
|
|
79
|
+
});
|
|
80
|
+
await squid.init();
|
|
81
|
+
|
|
82
|
+
useSquidStore.setState((_) => ({
|
|
83
|
+
config: getConfigWithDefaults({
|
|
84
|
+
integratorId: config.integratorId,
|
|
85
|
+
}),
|
|
86
|
+
squid,
|
|
87
|
+
}));
|
|
88
|
+
initWagmiClient(squid.chains);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
initializeSdk();
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<SquidContext.Provider value={undefined}>
|
|
96
|
+
<QueryClientProvider client={queryClient}>
|
|
97
|
+
{wagmiClient && (
|
|
98
|
+
<WagmiConfig client={wagmiClient}>
|
|
99
|
+
<CosmosProvider>{children}</CosmosProvider>
|
|
100
|
+
</WagmiConfig>
|
|
101
|
+
)}
|
|
102
|
+
</QueryClientProvider>
|
|
103
|
+
</SquidContext.Provider>
|
|
104
|
+
);
|
|
105
|
+
};
|