UNPKG

@hyperlane-xyz/widgets

Version:

Common react components for Hyperlane projects

135 lines 6.1 kB
import { useConnectModal } from '@rainbow-me/rainbowkit'; import { getAccount, sendTransaction, switchChain, waitForTransactionReceipt, watchAsset, } from '@wagmi/core'; import { useCallback, useMemo } from 'react'; import { useAccount, useConfig, useDisconnect } from 'wagmi'; import { LOCKBOX_STANDARDS, ProviderType, chainMetadataToViemChain, } from '@hyperlane-xyz/sdk'; import { ProtocolType, assert, sleep } from '@hyperlane-xyz/utils'; import { widgetLogger } from '../logger.js'; import { ethers5TxToWagmiTx, getChainsForProtocol } from './utils.js'; const logger = widgetLogger.child({ module: 'walletIntegrations/ethereum' }); export function useEthereumAccount(_multiProvider) { const { address, isConnected, connector } = useAccount(); const isReady = !!(address && isConnected && connector); return useMemo(() => ({ protocol: ProtocolType.Ethereum, addresses: address ? [{ address: `${address}` }] : [], isReady: isReady, }), [address, isReady]); } export function useEthereumWalletDetails() { const { connector } = useAccount(); const name = connector?.name; const logoUrl = connector?.icon; return useMemo(() => ({ name, logoUrl, }), [name, logoUrl]); } export function useEthereumConnectFn() { const { openConnectModal } = useConnectModal(); return useCallback(() => openConnectModal?.(), [openConnectModal]); } export function useEthereumDisconnectFn() { const { disconnectAsync } = useDisconnect(); return disconnectAsync; } export function useEthereumActiveChain(multiProvider) { const { chain } = useAccount(); return useMemo(() => ({ chainDisplayName: chain?.name, chainName: chain ? multiProvider.tryGetChainMetadata(chain.id)?.name : undefined, }), [chain, multiProvider]); } export function useEthereumSwitchNetwork(multiProvider) { const config = useConfig(); const onSwitchNetwork = useCallback(async (chainName) => { const chainId = multiProvider.getChainMetadata(chainName) .chainId; await switchChain(config, { chainId }); // Some wallets seem to require a brief pause after switch await sleep(2000); }, [config, multiProvider]); return { switchNetwork: onSwitchNetwork }; } export function useEthereumWatchAsset(multiProvider) { const { switchNetwork } = useEthereumSwitchNetwork(multiProvider); const config = useConfig(); const onAddAsset = useCallback(async (token, activeChainName) => { const chainName = token.chainName; // If the active chain is different from tx origin chain, try to switch network first if (activeChainName && activeChainName !== chainName) await switchNetwork(chainName); let tokenAddress = ''; if (LOCKBOX_STANDARDS.includes(token.standard)) { const adapter = token.getAdapter(multiProvider); tokenAddress = await adapter.getWrappedTokenAddress(); } else { tokenAddress = token.collateralAddressOrDenom || token.addressOrDenom; } return watchAsset(config, { type: 'ERC20', options: { address: tokenAddress, decimals: token.decimals, symbol: token.symbol, }, }); }, [config, switchNetwork, multiProvider]); return { addAsset: onAddAsset }; } export function useEthereumTransactionFns(multiProvider) { const config = useConfig(); const { switchNetwork } = useEthereumSwitchNetwork(multiProvider); // Note, this doesn't use wagmi's prepare + send pattern because we're potentially sending two transactions // The prepare hooks are recommended to use pre-click downtime to run async calls, but since the flow // may require two serial txs, the prepare hooks aren't useful and complicate hook architecture considerably. // See https://github.com/hyperlane-xyz/hyperlane-warp-ui-template/issues/19 // See https://github.com/wagmi-dev/wagmi/discussions/1564 const onSendTx = useCallback(async ({ tx, chainName, activeChainName, }) => { if (tx.type !== ProviderType.EthersV5) throw new Error(`Unsupported tx type: ${tx.type}`); // If the active chain is different from tx origin chain, try to switch network first if (activeChainName && activeChainName !== chainName) await switchNetwork(chainName); // Since the network switching is not foolproof, we also force a network check here const chainId = multiProvider.getChainMetadata(chainName) .chainId; logger.debug('Checking wallet current chain'); const latestNetwork = await getAccount(config); assert(latestNetwork?.chain?.id === chainId, `Wallet not on chain ${chainName} (ChainMismatchError)`); logger.debug(`Sending tx on chain ${chainName}`); const wagmiTx = ethers5TxToWagmiTx(tx.transaction); const hash = await sendTransaction(config, { chainId, ...wagmiTx, }); const confirm = () => { const foo = waitForTransactionReceipt(config, { chainId, hash, confirmations: 1, }); return foo.then((r) => ({ type: ProviderType.Viem, receipt: { ...r, contractAddress: r.contractAddress || null }, })); }; return { hash, confirm }; }, [config, switchNetwork, multiProvider]); const onMultiSendTx = useCallback(async ({ txs: _, chainName: __, activeChainName: ___, }) => { throw new Error('Multi Transactions not supported on EVM'); }, []); return { sendTransaction: onSendTx, sendMultiTransaction: onMultiSendTx, switchNetwork, }; } // Metadata formatted for use in Wagmi config export function getWagmiChainConfigs(multiProvider) { return getChainsForProtocol(multiProvider, ProtocolType.Ethereum).map(chainMetadataToViemChain); } //# sourceMappingURL=ethereum.js.map