diff --git a/src/components/icons/WalletLogo.tsx b/src/components/icons/WalletLogo.tsx new file mode 100644 index 00000000..92be9a0c --- /dev/null +++ b/src/components/icons/WalletLogo.tsx @@ -0,0 +1,23 @@ +import Image from 'next/image'; + +import { WalletDetails } from '../../features/wallet/hooks/types'; +import Wallet from '../../images/icons/wallet.svg'; + +export function WalletLogo({ + walletDetails, + size, +}: { + walletDetails: WalletDetails; + size?: number; +}) { + return ( + + ); +} diff --git a/src/features/transfer/TransfersDetailsModal.tsx b/src/features/transfer/TransfersDetailsModal.tsx index 405dde48..597bb341 100644 --- a/src/features/transfer/TransfersDetailsModal.tsx +++ b/src/features/transfer/TransfersDetailsModal.tsx @@ -1,6 +1,7 @@ import Image from 'next/image'; import { useCallback, useEffect, useMemo, useState } from 'react'; +import { ProtocolType } from '@hyperlane-xyz/utils'; import { MessageStatus, MessageTimeline, useMessageTimeline } from '@hyperlane-xyz/widgets'; import { Spinner } from '../../components/animation/Spinner'; @@ -22,7 +23,7 @@ import { isTransferSent, } from '../../utils/transfer'; import { getChainDisplayName, hasPermissionlessChain } from '../chains/utils'; -import { useAccountForChain } from '../wallet/hooks/multiProtocol'; +import { useAccountForChain, useWalletDetails } from '../wallet/hooks/multiProtocol'; import { TransferContext, TransferStatus } from './types'; @@ -53,6 +54,8 @@ export function TransfersDetailsModal({ } = transfer || {}; const account = useAccountForChain(origin); + const walletDetails = useWalletDetails()[account?.protocol || ProtocolType.Ethereum]; + const multiProvider = getMultiProvider(); const getMessageUrls = useCallback(async () => { @@ -80,7 +83,7 @@ export function TransfersDetailsModal({ }, [transfer, getMessageUrls]); const isAccountReady = !!account?.isReady; - const connectorName = account?.connectorName || 'wallet'; + const connectorName = walletDetails.name || 'wallet'; const token = getWarpCore().findToken(origin, originTokenAddressOrDenom); const isPermissionlessRoute = hasPermissionlessChain([destination, origin]); diff --git a/src/features/wallet/SideBarMenu.tsx b/src/features/wallet/SideBarMenu.tsx index 4422569f..25662e7b 100644 --- a/src/features/wallet/SideBarMenu.tsx +++ b/src/features/wallet/SideBarMenu.tsx @@ -4,8 +4,7 @@ import { toast } from 'react-toastify'; import { SmallSpinner } from '../../components/animation/SmallSpinner'; import { ChainLogo } from '../../components/icons/ChainLogo'; -import { Identicon } from '../../components/icons/Identicon'; -import { PLACEHOLDER_COSMOS_CHAIN } from '../../consts/values'; +import { WalletLogo } from '../../components/icons/WalletLogo'; import { tryFindToken } from '../../context/context'; import ArrowRightIcon from '../../images/icons/arrow-right.svg'; import CollapseIcon from '../../images/icons/collapse-icon.svg'; @@ -19,7 +18,7 @@ import { useStore } from '../store'; import { TransfersDetailsModal } from '../transfer/TransfersDetailsModal'; import { TransferContext } from '../transfer/types'; -import { useAccounts, useDisconnectFns } from './hooks/multiProtocol'; +import { useAccounts, useDisconnectFns, useWalletDetails } from './hooks/multiProtocol'; import { AccountInfo } from './hooks/types'; export function SideBarMenu({ @@ -88,12 +87,9 @@ export function SideBarMenu({ Connected Wallets
- {readyAccounts.map((acc, i) => - acc.addresses.map((addr, j) => { - if (addr?.chainName?.includes(PLACEHOLDER_COSMOS_CHAIN)) return null; - return ; - }), - )} + {readyAccounts.map((acc, i) => ( + + ))} ); diff --git a/src/features/wallet/WalletControlBar.tsx b/src/features/wallet/WalletControlBar.tsx index 5003f903..b1ebf0a5 100644 --- a/src/features/wallet/WalletControlBar.tsx +++ b/src/features/wallet/WalletControlBar.tsx @@ -1,25 +1,29 @@ import Image from 'next/image'; import { useState } from 'react'; -import { shortenAddress } from '@hyperlane-xyz/utils'; +import { ProtocolType, shortenAddress } from '@hyperlane-xyz/utils'; import { SolidButton } from '../../components/buttons/SolidButton'; -import { Identicon } from '../../components/icons/Identicon'; +import { WalletLogo } from '../../components/icons/WalletLogo'; import Wallet from '../../images/icons/wallet.svg'; import { useIsSsr } from '../../utils/ssr'; import { SideBarMenu } from './SideBarMenu'; import { WalletEnvSelectionModal } from './WalletEnvSelectionModal'; -import { useAccounts } from './hooks/multiProtocol'; +import { useAccounts, useWalletDetails } from './hooks/multiProtocol'; export function WalletControlBar() { + const isSsr = useIsSsr(); + const [showEnvSelectModal, setShowEnvSelectModal] = useState(false); const [isSideBarOpen, setIsSideBarOpen] = useState(false); const { readyAccounts } = useAccounts(); - const isSsr = useIsSsr(); + const walletDetails = useWalletDetails(); const numReady = readyAccounts.length; + const firstAccount = readyAccounts[0]; + const firstWallet = walletDetails[firstAccount?.protocol || ProtocolType.Ethereum]; if (isSsr) { // https://github.com/wagmi-dev/wagmi/issues/542#issuecomment-1144178142 @@ -44,11 +48,9 @@ export function WalletControlBar() { {numReady === 1 && ( setIsSideBarOpen(true)} classes="px-2.5 py-1" color="white">
- +
-
- {readyAccounts[0].connectorName || 'Wallet'} -
+
{firstWallet.name || 'Wallet'}
{readyAccounts[0].addresses.length ? shortenAddress(readyAccounts[0].addresses[0].address, true) diff --git a/src/features/wallet/hooks/cosmos.ts b/src/features/wallet/hooks/cosmos.ts index 30f39918..f4c1c65f 100644 --- a/src/features/wallet/hooks/cosmos.ts +++ b/src/features/wallet/hooks/cosmos.ts @@ -11,7 +11,13 @@ import { logger } from '../../../utils/logger'; import { getCosmosChainNames } from '../../chains/metadata'; import { getChainMetadata } from '../../chains/utils'; -import { AccountInfo, ActiveChainInfo, ChainAddress, ChainTransactionFns } from './types'; +import { + AccountInfo, + ActiveChainInfo, + ChainAddress, + ChainTransactionFns, + WalletDetails, +} from './types'; export function useCosmosAccount(): AccountInfo { const chainToContext = useChains(getCosmosChainNames()); @@ -31,12 +37,24 @@ export function useCosmosAccount(): AccountInfo { protocol: ProtocolType.Cosmos, addresses, publicKey, - connectorName, isReady, }; }, [chainToContext]); } +export function useCosmosWalletDetails() { + const { wallet } = useChain(PLACEHOLDER_COSMOS_CHAIN); + const { logo, prettyName } = wallet || {}; + + return useMemo( + () => ({ + name: prettyName, + logoUrl: typeof logo === 'string' ? logo : undefined, + }), + [prettyName, logo], + ); +} + export function useCosmosConnectFn(): () => void { const { openView } = useChain(PLACEHOLDER_COSMOS_CHAIN); return openView; diff --git a/src/features/wallet/hooks/evm.ts b/src/features/wallet/hooks/evm.ts index b0da7d62..1277d12e 100644 --- a/src/features/wallet/hooks/evm.ts +++ b/src/features/wallet/hooks/evm.ts @@ -1,4 +1,5 @@ import { useConnectModal } from '@rainbow-me/rainbowkit'; +import { useQuery } from '@tanstack/react-query'; import { getNetwork, sendTransaction, switchNetwork, waitForTransaction } from '@wagmi/core'; import { useCallback, useMemo } from 'react'; import { useAccount, useDisconnect, useNetwork } from 'wagmi'; @@ -10,21 +11,52 @@ import { logger } from '../../../utils/logger'; import { getChainMetadata, tryGetChainMetadata } from '../../chains/utils'; import { ethers5TxToWagmiTx } from '../utils'; -import { AccountInfo, ActiveChainInfo, ChainTransactionFns } from './types'; +import { AccountInfo, ActiveChainInfo, ChainTransactionFns, WalletDetails } from './types'; export function useEvmAccount(): AccountInfo { const { address, isConnected, connector } = useAccount(); const isReady = !!(address && isConnected && connector); - const connectorName = connector?.name; return useMemo( () => ({ protocol: ProtocolType.Ethereum, addresses: address ? [{ address: `${address}` }] : [], - connectorName: connectorName, isReady: isReady, }), - [address, connectorName, isReady], + [address, isReady], + ); +} + +export function useEvmWalletDetails() { + const { connector } = useAccount(); + const name = connector?.name; + // @ts-ignore RainbowKit hooks on this extra useful info but it's not typed + const rainbowKitWalletDetails = connector?._wallets?.[0]; + + const { data } = useQuery({ + queryKey: ['useEvmWalletDetails', name], + queryFn: async () => { + logger.debug('Fetching wallet details'); + if (!rainbowKitWalletDetails) return null; + const { iconUrl, iconAccent: logoAccent } = rainbowKitWalletDetails; + let logoUrl: string | undefined = undefined; + if (typeof iconUrl === 'function') { + logoUrl = await iconUrl(); + } else if (typeof iconUrl === 'string') { + logoUrl = iconUrl; + } + return { logoUrl, logoAccent }; + }, + staleTime: Infinity, + }); + + return useMemo( + () => ({ + name, + logoUrl: data?.logoUrl, + logoAccent: data?.logoAccent, + }), + [name, data], ); } diff --git a/src/features/wallet/hooks/multiProtocol.tsx b/src/features/wallet/hooks/multiProtocol.tsx index 39f13e57..0c87469e 100644 --- a/src/features/wallet/hooks/multiProtocol.tsx +++ b/src/features/wallet/hooks/multiProtocol.tsx @@ -13,6 +13,7 @@ import { useCosmosConnectFn, useCosmosDisconnectFn, useCosmosTransactionFns, + useCosmosWalletDetails, } from './cosmos'; import { useEvmAccount, @@ -20,6 +21,7 @@ import { useEvmConnectFn, useEvmDisconnectFn, useEvmTransactionFns, + useEvmWalletDetails, } from './evm'; import { useSolAccount, @@ -27,8 +29,9 @@ import { useSolConnectFn, useSolDisconnectFn, useSolTransactionFns, + useSolWalletDetails, } from './solana'; -import { AccountInfo, ActiveChainInfo, ChainTransactionFns } from './types'; +import { AccountInfo, ActiveChainInfo, ChainTransactionFns, WalletDetails } from './types'; export function useAccounts(): { accounts: Record; @@ -104,6 +107,21 @@ export function getAccountAddressAndPubKey( return { address, publicKey }; } +export function useWalletDetails(): Record { + const evmWallet = useEvmWalletDetails(); + const solWallet = useSolWalletDetails(); + const cosmosWallet = useCosmosWalletDetails(); + + return useMemo( + () => ({ + [ProtocolType.Ethereum]: evmWallet, + [ProtocolType.Sealevel]: solWallet, + [ProtocolType.Cosmos]: cosmosWallet, + }), + [evmWallet, solWallet, cosmosWallet], + ); +} + export function useConnectFns(): Record void> { const onConnectEthereum = useEvmConnectFn(); const onConnectSolana = useSolConnectFn(); diff --git a/src/features/wallet/hooks/solana.ts b/src/features/wallet/hooks/solana.ts index 071d5bc1..df6e9e38 100644 --- a/src/features/wallet/hooks/solana.ts +++ b/src/features/wallet/hooks/solana.ts @@ -11,22 +11,33 @@ import { getMultiProvider } from '../../../context/context'; import { logger } from '../../../utils/logger'; import { getChainByRpcEndpoint } from '../../chains/utils'; -import { AccountInfo, ActiveChainInfo, ChainTransactionFns } from './types'; +import { AccountInfo, ActiveChainInfo, ChainTransactionFns, WalletDetails } from './types'; export function useSolAccount(): AccountInfo { const { publicKey, connected, wallet } = useWallet(); const isReady = !!(publicKey && wallet && connected); const address = publicKey?.toBase58(); - const connectorName = wallet?.adapter?.name; return useMemo( () => ({ protocol: ProtocolType.Sealevel, addresses: address ? [{ address: address }] : [], - connectorName: connectorName, isReady: isReady, }), - [address, connectorName, isReady], + [address, isReady], + ); +} + +export function useSolWalletDetails() { + const { wallet } = useWallet(); + const { name, icon } = wallet?.adapter || {}; + + return useMemo( + () => ({ + name, + logoUrl: icon, + }), + [name, icon], ); } diff --git a/src/features/wallet/hooks/types.ts b/src/features/wallet/hooks/types.ts index fed15dc1..7e39581b 100644 --- a/src/features/wallet/hooks/types.ts +++ b/src/features/wallet/hooks/types.ts @@ -14,10 +14,15 @@ export interface AccountInfo { // And another Cosmos exception, public keys are needed // for tx simulation and gas estimation publicKey?: Promise; - connectorName?: string; isReady: boolean; } +export interface WalletDetails { + name?: string; + logoUrl?: string; + logoAccent?: string; +} + export interface ActiveChainInfo { chainDisplayName?: string; chainName?: ChainName;