diff --git a/src/sections/lending/components/UserDisplay.tsx b/src/sections/lending/components/UserDisplay.tsx index 0f6fb3f5e..e7b530e6b 100644 --- a/src/sections/lending/components/UserDisplay.tsx +++ b/src/sections/lending/components/UserDisplay.tsx @@ -9,6 +9,7 @@ import shallow from "zustand/shallow" import { Avatar, AvatarProps } from "./Avatar" import { BadgeSize, ExclamationBadge } from "./badges/ExclamationBadge" import { UserNameText, UserNameTextProps } from "./UserNameText" +import { MetaMaskAvatar } from "components/AccountAvatar/MetaMaskAvatar" type UserDisplayProps = { oneLiner?: boolean @@ -21,7 +22,6 @@ type UserDisplayProps = { export const UserDisplay: React.FC = ({ oneLiner = false, - avatarProps, titleProps, subtitleProps, withLink, @@ -38,21 +38,27 @@ export const UserDisplay: React.FC = ({ shallow, ) const { readOnlyMode } = useWeb3Context() - const fallbackImage = useMemo( - () => (account ? blo(account as `0x${string}`) : undefined), - [account], - ) + const loading = domainsLoading || accountLoading return ( - } - invisibleBadge={!readOnlyMode} - {...avatarProps} - /> + + + {readOnlyMode && ( + + + + )} + + {!oneLiner && defaultDomain?.name ? ( <> diff --git a/src/sections/lending/components/WalletConnection/ConnectWalletButton.tsx b/src/sections/lending/components/WalletConnection/ConnectWalletButton.tsx index f94314d56..847aa138b 100644 --- a/src/sections/lending/components/WalletConnection/ConnectWalletButton.tsx +++ b/src/sections/lending/components/WalletConnection/ConnectWalletButton.tsx @@ -1,33 +1,10 @@ -import { Button } from "@mui/material" -import React, { lazy, Suspense } from "react" -import { useWalletModalContext } from "sections/lending/hooks/useWalletModal" - -const WalletModal = lazy(async () => ({ - default: (await import("./WalletModal.js")).WalletModal, -})) +import React from "react" +import { Web3ConnectModalButton } from "sections/web3-connect/modal/Web3ConnectModalButton" export interface ConnectWalletProps { funnel?: string } -export const ConnectWalletButton: React.FC = ({ - funnel, -}) => { - const { setWalletModalOpen } = useWalletModalContext() - - return ( - <> - - - - - - ) +export const ConnectWalletButton: React.FC = () => { + return } diff --git a/src/sections/lending/components/WalletConnection/WalletModal.tsx b/src/sections/lending/components/WalletConnection/__WalletModal.tsx similarity index 100% rename from src/sections/lending/components/WalletConnection/WalletModal.tsx rename to src/sections/lending/components/WalletConnection/__WalletModal.tsx diff --git a/src/sections/lending/components/lists/ListHeaderWrapper.tsx b/src/sections/lending/components/lists/ListHeaderWrapper.tsx index 318bbed44..f8d32e40c 100644 --- a/src/sections/lending/components/lists/ListHeaderWrapper.tsx +++ b/src/sections/lending/components/lists/ListHeaderWrapper.tsx @@ -22,7 +22,6 @@ export const ListHeaderWrapper = ({ pb: 1, position: "sticky", top: 0, - zIndex: 100, bgcolor: "background.paper", borderBottom: "1px solid", borderColor: "divider", diff --git a/src/sections/lending/layouts/AppHeader.tsx b/src/sections/lending/layouts/AppHeader.tsx index 31025a5ea..6b7918637 100644 --- a/src/sections/lending/layouts/AppHeader.tsx +++ b/src/sections/lending/layouts/AppHeader.tsx @@ -212,7 +212,6 @@ export function AppHeader() { position: "sticky", top: 0, transition: theme.transitions.create("top"), - zIndex: theme.zIndex.appBar, bgcolor: theme.palette.background.header, padding: { xs: diff --git a/src/sections/lending/layouts/MainLayout.tsx b/src/sections/lending/layouts/MainLayout.tsx index 117f0d12e..f2716fceb 100644 --- a/src/sections/lending/layouts/MainLayout.tsx +++ b/src/sections/lending/layouts/MainLayout.tsx @@ -4,11 +4,28 @@ import { ReactNode } from "react" import { AppHeader } from "./AppHeader" import { LendingPageProviders } from "sections/lending/providers/LendingPageProviders" +import { + useAccount, + useEvmAccount, +} from "sections/web3-connect/Web3Connect.utils" export function MainLayout({ children }: { children: ReactNode }) { + const { account } = useAccount() + const { account: evmAccount } = useEvmAccount() return ( + + {account?.name}: {account?.displayAddress} {"->"} {evmAccount?.address} + (null) - - const networkConfig = getNetworkConfig(chainId) - let networkColor = "" - if (networkConfig?.isFork) { - networkColor = "#ff4a8d" - } else if (networkConfig?.isTestnet) { - networkColor = "#7157ff" - } else { - networkColor = "#65c970" - } - - const handleClose = () => { - setOpen(false) - } - - const handleClick = ( - event: React.MouseEvent, - ) => { - if (!connected) { - setWalletModalOpen(true) - } else { - setOpen(true) - setAnchorEl(event.currentTarget) - } - } - - const handleDisconnect = () => { - if (connected) { - disconnectWallet() - handleClose() - } - } - - const handleCopy = async () => { - navigator.clipboard.writeText(currentAccount) - handleClose() - } - - const handleSwitchWallet = (): void => { - setWalletModalOpen(true) - handleClose() - } - - const handleViewOnExplorer = (): void => { - handleClose() - } - const hideWalletAccountText = xsm && (ENABLE_TESTNET || STAGING_ENV || readOnlyModeAddress) - const Content = ({ - component = ListItem, - }: { - component?: typeof MenuItem | typeof ListItem - }) => ( - <> - - Account - - - - - - {readOnlyModeAddress && ( - - Read-only mode. - - )} - - - {!md && ( - - - - - )} - - - - - - - Network - - - - - - {networkConfig.name} - - - - - - - - - - - - - - Copy address - - - - {networkConfig?.explorerLinkBuilder && ( - - - - - - - - - View on Explorer - - - - )} - {md && ( - <> - - - - - - - )} - - ) - return ( <> {md && connected && open ? ( @@ -343,77 +47,45 @@ export default function WalletWidget({ ) : loading ? ( ) : ( - ) : ( - Connect wallet + )} - - )} - - {md ? ( - - - - - - ) : ( - - - - - + )} - - ) } diff --git a/src/sections/lending/libs/web3-data-provider/Web3Provider.tsx b/src/sections/lending/libs/web3-data-provider/Web3Provider.tsx index bf0f4b22d..c2fc35d11 100644 --- a/src/sections/lending/libs/web3-data-provider/Web3Provider.tsx +++ b/src/sections/lending/libs/web3-data-provider/Web3Provider.tsx @@ -3,22 +3,29 @@ import { SignatureLike } from "@ethersproject/bytes" import { JsonRpcProvider, TransactionResponse, - // Web3Provider, + Web3Provider, } from "@ethersproject/providers" -import { AbstractConnector } from "@web3-react/abstract-connector" import { useWeb3React } from "@web3-react/core" -import { TorusConnector } from "@web3-react/torus-connector" -import { WalletLinkConnector } from "@web3-react/walletlink-connector" import { BigNumber, PopulatedTransaction, providers } from "ethers" -import React, { ReactElement, useCallback, useEffect, useState } from "react" +import React, { + ReactElement, + useCallback, + useEffect, + useMemo, + useState, +} from "react" import { useRootStore } from "sections/lending/store/root" import { getNetworkConfig } from "sections/lending/utils/marketsAndNetworksConfig" import { hexToAscii } from "sections/lending/utils/utils" // import { isLedgerDappBrowserProvider } from 'web3-ledgerhq-frame-connector'; import { Web3Context } from "sections/lending/libs/hooks/useWeb3Context" -import { WalletConnectConnector } from "./WalletConnectConnector" -import { getWallet, ReadOnlyModeConnector, WalletType } from "./WalletOptions" +import { + useEnableWallet, + useEvmAccount, + useWallet, +} from "sections/web3-connect/Web3Connect.utils" +import { isMetaMask } from "utils/metamask" export type ERC20TokenType = { address: string @@ -29,8 +36,6 @@ export type ERC20TokenType = { } export type Web3Data = { - connectWallet: (wallet: WalletType) => Promise - connectReadOnlyMode: (address: string) => Promise disconnectWallet: () => void currentAccount: string connected: boolean @@ -55,26 +60,25 @@ export type Web3Data = { export const Web3ContextProvider: React.FC<{ children: ReactElement }> = ({ children, }) => { - const { - account, - chainId, - library: provider, - activate, - active, - error, - deactivate, - setError, - } = useWeb3React() + const evmAccount = useEvmAccount() + const { wallet, type } = useWallet() + const { disconnect: deactivate } = useEnableWallet(type) + const { error, setError } = useWeb3React() + + const account = evmAccount?.account?.address || "" + const chainId = evmAccount?.account?.chainId || null + const active = !!account + + const extension = wallet?.extension + + const provider = useMemo(() => { + if (isMetaMask(extension)) { + return new Web3Provider(extension) + } + }, [extension]) - // const [provider, setProvider] = useState(); - const [connector, setConnector] = useState() const [loading, setLoading] = useState(false) - const [tried, setTried] = useState(false) - const [deactivated, setDeactivated] = useState(false) - const [triedGnosisSafe, setTriedGnosisSafe] = useState(false) - const [triedCoinbase, setTriedCoinbase] = useState(false) const [readOnlyMode, setReadOnlyMode] = useState(false) - const [triedLedger, setTriedLedger] = useState(false) const [switchNetworkError, setSwitchNetworkError] = useState() const [setAccount, currentChainId] = useRootStore((store) => [ store.setAccount, @@ -92,278 +96,45 @@ export const Web3ContextProvider: React.FC<{ children: ReactElement }> = ({ // Wallet connection and disconnection // clean local storage - const cleanConnectorStorage = useCallback((): void => { - if (connector instanceof WalletConnectConnector) { - localStorage.removeItem("walletconnect") - } else if (connector instanceof WalletLinkConnector) { - localStorage.removeItem("-walletlink:https://www.walletlink.org:version") - localStorage.removeItem( - "-walletlink:https://www.walletlink.org:session:id", - ) - localStorage.removeItem( - "-walletlink:https://www.walletlink.org:session:secret", - ) - localStorage.removeItem( - "-walletlink:https://www.walletlink.org:session:linked", - ) - localStorage.removeItem( - "-walletlink:https://www.walletlink.org:AppVersion", - ) - localStorage.removeItem( - "-walletlink:https://www.walletlink.org:Addresses", - ) - localStorage.removeItem( - "-walletlink:https://www.walletlink.org:walletUsername", - ) - } else if (connector instanceof TorusConnector) { - localStorage.removeItem("loglevel:torus.js") - localStorage.removeItem("loglevel:torus-embed") - localStorage.removeItem("loglevel:http-helpers") - } - }, [connector]) const disconnectWallet = useCallback(async () => { - cleanConnectorStorage() - localStorage.removeItem("walletProvider") deactivate() - // @ts-expect-error close can be returned by wallet - if (connector && connector.close) { - // @ts-expect-error close can be returned by wallet - // close will remove wallet from DOM if provided by wallet - await connector.close() - } setWalletType(undefined) setLoading(false) - setDeactivated(true) setSwitchNetworkError(undefined) - }, [provider, connector]) - - const connectReadOnlyMode = (address: string): Promise => { - localStorage.setItem("readOnlyModeAddress", address) - return connectWallet(WalletType.READ_ONLY_MODE) - } - - // connect to the wallet specified by wallet type - const connectWallet = useCallback( - async (wallet: WalletType) => { - setLoading(true) - try { - const connector: AbstractConnector = getWallet( - wallet, - chainId, - currentChainId, - ) - - if (connector instanceof ReadOnlyModeConnector) { - setReadOnlyMode(true) - } else { - setReadOnlyMode(false) - } + }, [deactivate, setWalletType]) + + const sendTx = useCallback( + async ( + txData: transactionType | PopulatedTransaction, + ): Promise => { + if (provider) { + const { from, ...data } = txData + + /* createTransaction({ + xcall: { + data: txData as `0x${string}`, + abi: "[]", + }, + xcallMeta: {}, + }) */ - if (connector instanceof WalletConnectConnector) { - connector.walletConnectProvider = undefined - } - await activate(connector, undefined, true) - setConnector(connector) - setSwitchNetworkError(undefined) - setWalletType(wallet) - localStorage.setItem("walletProvider", wallet.toString()) - setDeactivated(false) - setLoading(false) - } catch (e) { - console.log("error on activation", e) - setError(e) - setWalletType(undefined) - // disconnectWallet(); - setLoading(false) + const signer = provider.getSigner(from) + const txResponse: TransactionResponse = await signer.sendTransaction({ + ...data, + value: data.value ? BigNumber.from(data.value) : undefined, + }) + return txResponse } + throw new Error("Error sending transaction. Provider not found") }, - [disconnectWallet, currentChainId], + [provider], ) - const activateInjectedProvider = ( - providerName: string | "MetaMask" | "CoinBase", - ) => { - // @ts-expect-error ethereum doesn't necessarily exist - const { ethereum } = window - - if (!ethereum?.providers) { - return true - } - - let provider - switch (providerName) { - case "CoinBase": - provider = ethereum.providers.find( - //@ts-expect-error no type - ({ isCoinbaseWallet, isCoinbaseBrowser }) => - isCoinbaseWallet || isCoinbaseBrowser, - ) - break - case "MetaMask": - //@ts-expect-error no type - provider = ethereum.providers.find(({ isMetaMask }) => isMetaMask) - break - default: - return false - } - - if (provider) { - ethereum.setSelectedProvider(provider) - return true - } - - return false - } - - // third, try connecting to ledger - useEffect(() => { - if (!triedLedger && triedGnosisSafe && triedCoinbase) { - // check if the DApp is hosted within Ledger iframe - // const canConnectToLedger = isLedgerDappBrowserProvider(); - if (false) { - // TODO check if we want to support - connectWallet(WalletType.LEDGER).finally(() => setTriedLedger(true)) - } else { - setTriedLedger(true) - } - } - }, [ - connectWallet, - triedGnosisSafe, - triedCoinbase, - triedLedger, - setTriedLedger, - ]) - - // second, try connecting to coinbase - useEffect(() => { - if (!triedCoinbase) { - // do check if condition applies to try and connect directly to coinbase - if (triedGnosisSafe) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const injectedProvider = (window as any)?.ethereum - if (injectedProvider?.isCoinbaseBrowser) { - const canConnectToCoinbase = activateInjectedProvider("CoinBase") - if (canConnectToCoinbase) { - connectWallet(WalletType.INJECTED) - .then(() => { - setTriedCoinbase(true) - }) - .catch(() => { - setTriedCoinbase(true) - }) - } else { - // @ts-expect-error ethereum might not be in window - const { ethereum } = window - - if (ethereum) { - activateInjectedProvider("CoinBase") - ethereum.request({ method: "eth_requestAccounts" }) - } - - setTriedCoinbase(true) - } - } else { - setTriedCoinbase(true) - } - } - } - }, [connectWallet, triedGnosisSafe, setTriedCoinbase, triedCoinbase]) - - // first, try connecting to a gnosis safe - useEffect(() => { - if (!triedGnosisSafe) { - const gnosisConnector = getWallet(WalletType.GNOSIS) - // @ts-expect-error isSafeApp not in abstract connector type - gnosisConnector.isSafeApp().then((loadedInSafe) => { - if (loadedInSafe) { - connectWallet(WalletType.GNOSIS) - .then(() => { - setTriedGnosisSafe(true) - }) - .catch(() => { - setTriedGnosisSafe(true) - }) - } else { - setTriedGnosisSafe(true) - } - }) - } - }, [connectWallet, setTriedGnosisSafe, triedGnosisSafe]) - - // handle logic to eagerly connect to the injected ethereum provider, - // if it exists and has granted access already - useEffect(() => { - const lastWalletProvider = localStorage.getItem("walletProvider") - if ( - !active && - !deactivated && - triedGnosisSafe && - triedCoinbase && - triedLedger - ) { - if (!!lastWalletProvider) { - connectWallet(lastWalletProvider as WalletType).catch(() => { - setTried(true) - }) - } else { - setTried(true) - // For now we will not eagerly connect to injected provider - // const injected = getWallet(WalletType.INJECTED); - // // @ts-expect-error isAuthorized not in AbstractConnector type. But method is there for - // // injected provider - // injected.isAuthorized().then((isAuthorized: boolean) => { - // if (isAuthorized) { - // connectWallet(WalletType.INJECTED).catch(() => { - // setTried(true); - // }); - // } else { - // setTried(true); - // } - // }); - } - } - }, [ - activate, - setTried, - active, - connectWallet, - deactivated, - triedGnosisSafe, - triedCoinbase, - triedLedger, - ]) - - // if the connection worked, wait until we get confirmation of that to flip the flag - useEffect(() => { - if (!tried && active) { - setTried(true) - } - }, [tried, active]) - - // Tx methods - - // TODO: we use from instead of currentAccount because of the mock wallet. - // If we used current account then the tx could get executed - const sendTx = async ( - txData: transactionType | PopulatedTransaction, - ): Promise => { - if (provider) { - const { from, ...data } = txData - const signer = provider.getSigner(from) - const txResponse: TransactionResponse = await signer.sendTransaction({ - ...data, - value: data.value ? BigNumber.from(data.value) : undefined, - }) - return txResponse - } - throw new Error("Error sending transaction. Provider not found") - } - // TODO: recheck that it works on all wallets const signTxData = async (unsignedData: string): Promise => { if (provider && account) { + console.log("SIGNING") const signature: SignatureLike = await provider.send( "eth_signTypedData_v4", [account, unsignedData], @@ -371,7 +142,6 @@ export const Web3ContextProvider: React.FC<{ children: ReactElement }> = ({ return signature } - throw new Error("Error initializing permit signature") } const switchNetwork = async (newChainId: number) => { @@ -422,7 +192,7 @@ export const Web3ContextProvider: React.FC<{ children: ReactElement }> = ({ const getTxError = async (txHash: string): Promise => { if (provider) { const tx = await provider.getTransaction(txHash) - // @ts-expect-error TODO: need think about "tx" type + // @ts-ignore TODO: need think about "tx" type const code = await provider.call(tx, tx.blockNumber) const error = hexToAscii(code.substr(138)) return error @@ -474,8 +244,6 @@ export const Web3ContextProvider: React.FC<{ children: ReactElement }> = ({ { const { account } = useAccount() const { wallet } = useWallet() - const address = account?.displayAddress ?? "" + const address = account?.address ?? "" + + const evmAddress = useMemo(() => { + if (!address) return "" + if (isEvmAccount(address)) return H160.fromAccount(address) + return H160.fromSS58(address) + }, [address]) const evm = useQuery( QUERY_KEYS.evmChainInfo(address), async () => { const chainId = isMetaMask(wallet?.extension) ? await wallet?.extension?.request({ method: "eth_chainId" }) - : null + : import.meta.env.VITE_EVM_CHAIN_ID return { chainId: Number(chainId), } }, { - enabled: !!address, + enabled: !!address && !!wallet?.extension, }, ) @@ -74,7 +82,7 @@ export const useEvmAccount = () => { account: { ...evm.data, name: account?.name ?? "", - address: account?.displayAddress, + address: evmAddress, }, } } @@ -218,6 +226,10 @@ export const useEnableWallet = ( options?: MutationObserverOptions, ) => { const { wallet } = getWalletProviderByType(provider) + const disconnect = useWeb3ConnectStore( + useShallow((state) => state.disconnect), + ) + const meta = useWeb3ConnectStore(useShallow((state) => state.meta)) const { mutate: enable, ...mutation } = useMutation( async () => { @@ -237,6 +249,7 @@ export const useEnableWallet = ( return { enable, + disconnect, ...mutation, } } diff --git a/src/utils/evm.ts b/src/utils/evm.ts index 9bcc243a1..46ea23fbc 100644 --- a/src/utils/evm.ts +++ b/src/utils/evm.ts @@ -1,4 +1,5 @@ import { encodeAddress, decodeAddress } from "@polkadot/util-crypto" +import { u8aToHex } from "@polkadot/util" import { Buffer } from "buffer" import { HYDRA_ADDRESS_PREFIX } from "utils/api" @@ -48,6 +49,12 @@ export class H160 { safeConvertAddressH160(Buffer.from(addressBytes).toString("hex")) ?? "" ) } + + static fromSS58 = (address: string) => { + const decodedBytes = decodeAddress(address) + const slicedBytes = decodedBytes.slice(0, 20) + return u8aToHex(slicedBytes) + } } export function getEvmTxLink(txHash: string, chain = "hydradx") {