diff --git a/src/components/atoms/AddToken.module.css b/src/components/atoms/AddToken.module.css new file mode 100644 index 000000000..a7fcdc78f --- /dev/null +++ b/src/components/atoms/AddToken.module.css @@ -0,0 +1,71 @@ +.button { + display: inline-block; + position: relative; + min-width: auto; +} + +.button:hover, +.button:focus { + transform: none; +} + +.logoWrap { + position: relative; + display: inline-block; + z-index: 1; +} + +.logoWrap::before { + content: '+'; + color: var(--color-secondary); + font-family: var(--font-family-base); + font-weight: var(--font-weight-base); + font-size: 1.25em; + position: absolute; + right: 0.05em; + top: 0.05em; + line-height: 0; +} + +.logo { + width: 1.6em; + height: 1.6em; + display: inline-block; + margin-bottom: -0.35em; + border-radius: 50%; + border: 0.065rem solid var(--color-secondary); + margin-right: calc(var(--spacer) / 10); + transition: 0.2s ease-out; +} + +.button:hover .logo, +.button:focus .logo { + border-color: var(--color-primary); +} + +.button:hover .logoWrap::before, +.button:focus .logoWrap::before { + color: var(--color-primary); +} + +.text { + display: inline-block; + position: relative; +} + +.minimal .text { + opacity: 0; + transform: translate3d(-1rem, 0, 0); + transition: 0.2s ease-out; + z-index: 0; + white-space: pre; + position: absolute; + left: 100%; + top: 0.15rem; +} + +.minimal:hover .text, +.minimal:focus .text { + opacity: 1; + transform: translate3d(0, 0, 0); +} diff --git a/src/components/atoms/AddToken.tsx b/src/components/atoms/AddToken.tsx new file mode 100644 index 000000000..e940a5838 --- /dev/null +++ b/src/components/atoms/AddToken.tsx @@ -0,0 +1,53 @@ +import React, { ReactElement } from 'react' +import classNames from 'classnames/bind' +import { addTokenToWallet } from '../../utils/web3' +import { useWeb3 } from '../../providers/Web3' +import Button from './Button' +import styles from './AddToken.module.css' + +const cx = classNames.bind(styles) + +export default function AddToken({ + address, + symbol, + logo, + text, + className, + minimal +}: { + address: string + symbol: string + logo: string // needs to be a remote image + text?: string + className?: string + minimal?: boolean +}): ReactElement { + const { web3Provider } = useWeb3() + + const styleClasses = cx({ + button: true, + minimal: minimal, + [className]: className + }) + + async function handleAddToken() { + if (!web3Provider) return + + await addTokenToWallet(web3Provider, address, symbol, logo) + } + + return ( + + + + + + {text || `Add ${symbol}`} + + ) +} diff --git a/src/components/atoms/ExplorerLink.tsx b/src/components/atoms/ExplorerLink.tsx index b1cb4f732..4104e4e86 100644 --- a/src/components/atoms/ExplorerLink.tsx +++ b/src/components/atoms/ExplorerLink.tsx @@ -1,20 +1,30 @@ import React, { ReactElement, ReactNode, useEffect, useState } from 'react' import { ReactComponent as External } from '../../images/external.svg' -import styles from './ExplorerLink.module.css' +import classNames from 'classnames/bind' import { ConfigHelperConfig } from '@oceanprotocol/lib' import { useOcean } from '../../providers/Ocean' +import styles from './ExplorerLink.module.css' + +const cx = classNames.bind(styles) export default function ExplorerLink({ path, - children + children, + className }: { networkId: number path: string children: ReactNode + className?: string }): ReactElement { const { config } = useOcean() const [url, setUrl] = useState() + const styleClasses = cx({ + link: true, + [className]: className + }) + useEffect(() => { setUrl((config as ConfigHelperConfig).explorerUri) }, [config]) @@ -25,7 +35,7 @@ export default function ExplorerLink({ title={`View on ${(config as ConfigHelperConfig).explorerUri}`} target="_blank" rel="noreferrer" - className={styles.link} + className={styleClasses} > {children} diff --git a/src/components/atoms/Publisher/index.tsx b/src/components/atoms/Publisher/index.tsx index 086bbca17..ca0ebc014 100644 --- a/src/components/atoms/Publisher/index.tsx +++ b/src/components/atoms/Publisher/index.tsx @@ -68,7 +68,6 @@ export default function Publisher({ > {name} - {' — '} {profile && ( diff --git a/src/components/molecules/NetworkBanner.tsx b/src/components/molecules/NetworkBanner.tsx index badbc96aa..57dba707f 100644 --- a/src/components/molecules/NetworkBanner.tsx +++ b/src/components/molecules/NetworkBanner.tsx @@ -2,7 +2,6 @@ import React, { ReactElement, useEffect, useState } from 'react' import { useWeb3 } from '../../providers/Web3' import { addCustomNetwork, NetworkObject } from '../../utils/web3' import { getOceanConfig } from '../../utils/ocean' -import { getProviderInfo } from 'web3modal' import { useOcean } from '../../providers/Ocean' import { useSiteMetadata } from '../../hooks/useSiteMetadata' import AnnouncementBanner, { @@ -19,7 +18,7 @@ const networkMatic: NetworkObject = { } export default function NetworkBanner(): ReactElement { - const { web3Provider } = useWeb3() + const { web3Provider, web3ProviderInfo } = useWeb3() const { config, connect } = useOcean() const { announcement } = useSiteMetadata() @@ -51,10 +50,9 @@ export default function NetworkBanner(): ReactElement { } useEffect(() => { - if (!web3Provider && !config) return + if (!web3ProviderInfo || (!web3Provider && !config)) return - const providerInfo = getProviderInfo(web3Provider) - switch (providerInfo?.name) { + switch (web3ProviderInfo.name) { case 'Web3': if (config.networkId !== 137) { setText(announcement.main) @@ -80,7 +78,7 @@ export default function NetworkBanner(): ReactElement { setAction(undefined) } } - }, [web3Provider, config, announcement]) + }, [web3Provider, web3ProviderInfo, config, announcement]) return } diff --git a/src/components/molecules/Wallet/Details.module.css b/src/components/molecules/Wallet/Details.module.css index 99c3b7b50..99a362d04 100644 --- a/src/components/molecules/Wallet/Details.module.css +++ b/src/components/molecules/Wallet/Details.module.css @@ -42,7 +42,7 @@ justify-content: space-between; } -.actions span { +.walletLogoWrap { display: block; } @@ -84,3 +84,7 @@ .walletInfo button { margin-top: calc(var(--spacer) / 5) !important; } + +.addToken { + margin-left: 0.3rem; +} diff --git a/src/components/molecules/Wallet/Details.tsx b/src/components/molecules/Wallet/Details.tsx index 39247c642..58477b1d0 100644 --- a/src/components/molecules/Wallet/Details.tsx +++ b/src/components/molecules/Wallet/Details.tsx @@ -1,33 +1,29 @@ import React, { ReactElement, useEffect, useState } from 'react' -import Button from '../../atoms/Button' -import styles from './Details.module.css' -import { useOcean } from '../../../providers/Ocean' -import Web3Feedback from './Feedback' -import { getProviderInfo, IProviderInfo } from 'web3modal' -import Conversion from '../../atoms/Price/Conversion' import { formatCurrency } from '@coingecko/cryptoformat' +import { useOcean } from '../../../providers/Ocean' import { useUserPreferences } from '../../../providers/UserPreferences' +import Button from '../../atoms/Button' +import AddToken from '../../atoms/AddToken' +import Conversion from '../../atoms/Price/Conversion' import { useWeb3 } from '../../../providers/Web3' -import { addOceanToWallet } from '../../../utils/web3' -import { Logger } from '@oceanprotocol/lib' + +import Web3Feedback from './Feedback' +import styles from './Details.module.css' export default function Details(): ReactElement { - const { web3Provider, connect, logout, networkData } = useWeb3() + const { + web3Provider, + web3ProviderInfo, + connect, + logout, + networkData + } = useWeb3() const { balance, config } = useOcean() const { locale } = useUserPreferences() - const [providerInfo, setProviderInfo] = useState() const [mainCurrency, setMainCurrency] = useState() // const [portisNetwork, setPortisNetwork] = useState() - // Workaround cause getInjectedProviderName() always returns `MetaMask` - // https://github.com/oceanprotocol/market/issues/332 - useEffect(() => { - if (!web3Provider) return - const providerInfo = getProviderInfo(web3Provider) - setProviderInfo(providerInfo) - }, [web3Provider]) - useEffect(() => { if (!networkData) return @@ -61,11 +57,11 @@ export default function Details(): ReactElement { - - - {providerInfo?.name} + + + {web3ProviderInfo?.name} - {/* {providerInfo?.name === 'Portis' && ( + {/* {web3ProviderInfo?.name === 'Portis' && ( )} */} - {providerInfo?.name === 'MetaMask' && ( - { - addOceanToWallet(config, web3Provider) - }} - > - {`Add ${config.oceanTokenSymbol}`} - + {web3ProviderInfo?.name === 'MetaMask' && ( + )} - {providerInfo?.name === 'Portis' && ( + {web3ProviderInfo?.name === 'Portis' && ( {`${ddo?.dataTokenInfo.name} — ${ddo?.dataTokenInfo.symbol}`} + + {web3ProviderInfo?.name === 'MetaMask' && ( + + + + )} diff --git a/src/providers/Web3.tsx b/src/providers/Web3.tsx index 695212fab..b9410fb53 100644 --- a/src/providers/Web3.tsx +++ b/src/providers/Web3.tsx @@ -8,7 +8,7 @@ import React, { useCallback } from 'react' import Web3 from 'web3' -import Web3Modal from 'web3modal' +import Web3Modal, { getProviderInfo, IProviderInfo } from 'web3modal' import { infuraProjectId as infuraId, portisId } from '../../app.config' import WalletConnectProvider from '@walletconnect/web3-provider' import { Logger } from '@oceanprotocol/lib' @@ -24,6 +24,7 @@ interface Web3ProviderValue { web3: Web3 web3Provider: any web3Modal: Web3Modal + web3ProviderInfo: IProviderInfo accountId: string networkId: number networkDisplayName: string @@ -106,6 +107,7 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { const [web3, setWeb3] = useState() const [web3Provider, setWeb3Provider] = useState() const [web3Modal, setWeb3Modal] = useState() + const [web3ProviderInfo, setWeb3ProviderInfo] = useState() const [networkId, setNetworkId] = useState() const [networkDisplayName, setNetworkDisplayName] = useState() const [networkData, setNetworkData] = useState() @@ -209,6 +211,18 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { getBlock() }, [web3, networkId]) + // ----------------------------------- + // Get and set web3 provider info + // ----------------------------------- + // Workaround cause getInjectedProviderName() always returns `MetaMask` + // https://github.com/oceanprotocol/market/issues/332 + useEffect(() => { + if (!web3Provider) return + + const providerInfo = getProviderInfo(web3Provider) + setWeb3ProviderInfo(providerInfo) + }, [web3Provider]) + // ----------------------------------- // Logout helper // ----------------------------------- @@ -255,6 +269,7 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { web3, web3Provider, web3Modal, + web3ProviderInfo, accountId, networkId, networkDisplayName, diff --git a/src/utils/web3.ts b/src/utils/web3.ts index 2b29fe334..d35841009 100644 --- a/src/utils/web3.ts +++ b/src/utils/web3.ts @@ -1,4 +1,4 @@ -import { Logger, ConfigHelperConfig } from '@oceanprotocol/lib' +import { Logger } from '@oceanprotocol/lib' export interface EthereumListsChain { name: string @@ -79,32 +79,33 @@ export function addCustomNetwork( ) } -export function addOceanToWallet( - config: ConfigHelperConfig, - web3Provider: any -): void { +export async function addTokenToWallet( + web3Provider: any, + address: string, + symbol: string, + logo?: string +): Promise { + const image = + logo || + 'https://raw.githubusercontent.com/oceanprotocol/art/main/logo/token.png' + const tokenMetadata = { type: 'ERC20', - options: { - address: config.oceanTokenAddress, - symbol: config.oceanTokenSymbol, - decimals: 18, - image: - 'https://raw.githubusercontent.com/oceanprotocol/art/main/logo/token.png' - } + options: { address, symbol, image, decimals: 18 } } + web3Provider.sendAsync( { method: 'wallet_watchAsset', params: tokenMetadata, id: Math.round(Math.random() * 100000) }, - (err: string, added: any) => { + (err: { code: number; message: string }, added: any) => { if (err || 'error' in added) { Logger.error( `Couldn't add ${tokenMetadata.options.symbol} (${ tokenMetadata.options.address - }) to MetaMask, error: ${err || added.error}` + }) to MetaMask, error: ${err.message || added.error}` ) } else { Logger.log(
- {providerInfo?.name === 'Portis' && ( + {web3ProviderInfo?.name === 'Portis' && ( {`${ddo?.dataTokenInfo.name} — ${ddo?.dataTokenInfo.symbol}`} + + {web3ProviderInfo?.name === 'MetaMask' && ( + + + + )}