From 873726481683920f5ddf831c47e87c7ffc6140d3 Mon Sep 17 00:00:00 2001 From: Matthias Kretschmann Date: Thu, 4 Mar 2021 18:16:20 +0100 Subject: [PATCH] refactor network name output (#421) * refactor network output * fetch chain & network metadata from @ethereum-lists/chains * typed responses * switch warning icon for testnet badge * add supportedNetworks list, output warning based on it * markup & spacing tweaks * check networkId against ocean.js ConfigHelper * remove supportedNetworks app config * fetch EVM networks metadata on build time * fixes --- .gitignore | 1 + app.config.js | 3 + gatsby-node.js | 8 ++ scripts/write-networks-metadata.js | 11 +++ src/components/atoms/EtherscanLink.tsx | 44 ++++++++--- .../molecules/Wallet/Account.module.css | 5 +- src/components/molecules/Wallet/Account.tsx | 4 +- src/components/molecules/Wallet/Feedback.tsx | 15 +--- .../molecules/Wallet/Network.module.css | 27 +++++++ src/components/molecules/Wallet/Network.tsx | 74 ++++++++++++++++++ .../molecules/Wallet/index.module.css | 3 + src/components/molecules/Wallet/index.tsx | 15 +++- src/utils/wallet.ts | 77 +++++++++---------- 13 files changed, 215 insertions(+), 72 deletions(-) create mode 100644 scripts/write-networks-metadata.js create mode 100644 src/components/molecules/Wallet/Network.module.css create mode 100644 src/components/molecules/Wallet/Network.tsx create mode 100644 src/components/molecules/Wallet/index.module.css diff --git a/.gitignore b/.gitignore index b98ce5c84..eb5112601 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ public/storybook .artifacts .vercel repo-metadata.json +networks-metadata.json src/@types/apollo \ No newline at end of file diff --git a/app.config.js b/app.config.js index 49fd94e23..72da2d53c 100644 --- a/app.config.js +++ b/app.config.js @@ -4,11 +4,14 @@ module.exports = { // networks in their wallet. // Ocean Protocol contracts are deployed for: 'mainnet', 'rinkeby', 'development' network: process.env.GATSBY_NETWORK || 'mainnet', + infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx', + // The ETH address the marketplace fee will be sent to. marketFeeAddress: process.env.GATSBY_MARKET_FEE_ADDRESS || '0x903322C7E45A60d7c8C3EA236c5beA9Af86310c7', + // Used for conversion display, can be whatever coingecko API supports // see: https://api.coingecko.com/api/v3/simple/supported_vs_currencies currencies: [ diff --git a/gatsby-node.js b/gatsby-node.js index 1ac5bb150..28a888cc4 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -11,6 +11,14 @@ execSync(`node ./scripts/write-repo-metadata > repo-metadata.json`, { // Generate Apollo typings execSync(`npm run apollo:codegen`, { stdio: 'inherit' }) +// Fetch EVM networks metadata +execSync( + `node ./scripts/write-networks-metadata > content/networks-metadata.json`, + { + stdio: 'inherit' + } +) + exports.onCreateNode = ({ node, actions, getNode }) => { createFields(node, actions, getNode) } diff --git a/scripts/write-networks-metadata.js b/scripts/write-networks-metadata.js new file mode 100644 index 000000000..37f951836 --- /dev/null +++ b/scripts/write-networks-metadata.js @@ -0,0 +1,11 @@ +#!/usr/bin/env node +'use strict' + +const axios = require('axios') + +// https://github.com/ethereum-lists/chains +const chainDataUrl = 'https://chainid.network/chains.json' + +axios(chainDataUrl).then((response) => { + process.stdout.write(JSON.stringify(response.data, null, ' ')) +}) diff --git a/src/components/atoms/EtherscanLink.tsx b/src/components/atoms/EtherscanLink.tsx index 842473449..1d1fff9fd 100644 --- a/src/components/atoms/EtherscanLink.tsx +++ b/src/components/atoms/EtherscanLink.tsx @@ -1,8 +1,23 @@ -import React, { ReactElement, ReactNode } from 'react' -import { getNetworkName } from '../../utils/wallet' +import React, { ReactElement, ReactNode, useEffect, useState } from 'react' +import { EthereumListsChain, getNetworkData } from '../../utils/wallet' import { ReactComponent as External } from '../../images/external.svg' import styles from './EtherscanLink.module.css' import { useSiteMetadata } from '../../hooks/useSiteMetadata' +import { graphql, useStaticQuery } from 'gatsby' + +const networksQuery = graphql` + query { + allNetworksMetadataJson { + edges { + node { + chain + network + networkId + } + } + } + } +` export default function EtherscanLink({ networkId, @@ -13,15 +28,24 @@ export default function EtherscanLink({ path: string children: ReactNode }): ReactElement { + const data = useStaticQuery(networksQuery) + const networksList: { node: EthereumListsChain }[] = + data.allNetworksMetadataJson.edges + const { appConfig } = useSiteMetadata() - const url = - (!networkId && appConfig.network === 'mainnet') || networkId === 1 - ? `https://etherscan.io` - : `https://${ - networkId - ? getNetworkName(networkId).toLowerCase() - : appConfig.network - }.etherscan.io` + const [url, setUrl] = useState() + + useEffect(() => { + const networkData = getNetworkData(networksList, networkId) + const url = + (!networkId && appConfig.network === 'mainnet') || networkId === 1 + ? `https://etherscan.io` + : `https://${ + networkId ? networkData.network : appConfig.network + }.etherscan.io` + + setUrl(url) + }, [networkId, networksList, appConfig.network]) return ( { // Forward ref for Tippy.js // eslint-disable-next-line const Account = React.forwardRef((props, ref: any) => { - const { accountId, status, connect, networkId, web3Modal } = useOcean() - const hasSuccess = status === 1 && networkId === 1 + const { accountId, status, connect, web3Modal } = useOcean() + const hasSuccess = status === 1 async function handleActivation(e: FormEvent) { // prevent accidentially submitting a form the button might be in diff --git a/src/components/molecules/Wallet/Feedback.tsx b/src/components/molecules/Wallet/Feedback.tsx index c449b940c..77b02a723 100644 --- a/src/components/molecules/Wallet/Feedback.tsx +++ b/src/components/molecules/Wallet/Feedback.tsx @@ -2,7 +2,6 @@ import React, { ReactElement } from 'react' import Status from '../../atoms/Status' import styles from './Feedback.module.css' import { useOcean } from '@oceanprotocol/react' -import { getNetworkName } from '../../../utils/wallet' export declare type Web3Error = { status: 'error' | 'warning' | 'success' @@ -15,19 +14,13 @@ export default function Web3Feedback({ }: { isBalanceSufficient?: boolean }): ReactElement { - const { account, status, networkId } = useOcean() + const { account, status } = useOcean() const isOceanConnectionError = status === -1 - const isMainnet = networkId === 1 const showFeedback = - !account || - isOceanConnectionError || - !isMainnet || - isBalanceSufficient === false + !account || isOceanConnectionError || isBalanceSufficient === false const state = !account ? 'error' - : !isMainnet - ? 'warning' : account && isBalanceSufficient ? 'success' : 'warning' @@ -36,8 +29,6 @@ export default function Web3Feedback({ ? 'No account connected' : isOceanConnectionError ? 'Error connecting to Ocean' - : !isMainnet - ? getNetworkName(networkId) : account ? isBalanceSufficient === false ? 'Insufficient balance' @@ -48,8 +39,6 @@ export default function Web3Feedback({ ? 'Please connect your Web3 wallet.' : isOceanConnectionError ? 'Please try again.' - : !isMainnet - ? undefined : isBalanceSufficient === false ? 'You do not have enough OCEAN in your wallet to purchase this asset.' : 'Something went wrong.' diff --git a/src/components/molecules/Wallet/Network.module.css b/src/components/molecules/Wallet/Network.module.css new file mode 100644 index 000000000..8e676b984 --- /dev/null +++ b/src/components/molecules/Wallet/Network.module.css @@ -0,0 +1,27 @@ +.network { + border: 1px solid var(--border-color); + border-right: none; + border-top-left-radius: var(--border-radius); + border-bottom-left-radius: var(--border-radius); + padding: calc(var(--spacer) / 4) calc(var(--spacer) / 2); + white-space: nowrap; + margin-right: -3px; + display: inline-flex; + align-items: center; +} + +.name { + font-size: var(--font-size-small); + display: inline-block; + text-transform: capitalize; +} + +.badge { + margin-left: calc(var(--spacer) / 8); + background-color: var(--border-color); + color: var(--font-color-text); +} + +.warning { + margin-right: calc(var(--spacer) / 4); +} diff --git a/src/components/molecules/Wallet/Network.tsx b/src/components/molecules/Wallet/Network.tsx new file mode 100644 index 000000000..e345216d3 --- /dev/null +++ b/src/components/molecules/Wallet/Network.tsx @@ -0,0 +1,74 @@ +import React, { useState, useEffect, ReactElement } from 'react' +import { useOcean } from '@oceanprotocol/react' +import Status from '../../atoms/Status' +import { + EthereumListsChain, + getNetworkData, + getNetworkDisplayName +} from '../../../utils/wallet' +import { ConfigHelper } from '@oceanprotocol/lib' +import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper' +import styles from './Network.module.css' +import Badge from '../../atoms/Badge' +import Tooltip from '../../atoms/Tooltip' +import { graphql, useStaticQuery } from 'gatsby' + +const networksQuery = graphql` + query NetworksQuery { + allNetworksMetadataJson { + edges { + node { + chain + network + networkId + } + } + } + } +` + +export default function Network(): ReactElement { + const data = useStaticQuery(networksQuery) + const networksList: { node: EthereumListsChain }[] = + data.allNetworksMetadataJson.edges + + const { config, networkId } = useOcean() + const networkIdConfig = (config as ConfigHelperConfig).networkId + + const [isEthMainnet, setIsEthMainnet] = useState() + const [networkName, setNetworkName] = useState() + const [isTestnet, setIsTestnet] = useState() + const [isSupportedNetwork, setIsSupportedNetwork] = useState() + + useEffect(() => { + // take network from user when present, + // otherwise use the default configured one of app + const network = networkId || networkIdConfig + const isEthMainnet = network === 1 + setIsEthMainnet(isEthMainnet) + + // Check networkId against ocean.js ConfigHelper configs + // to figure out if network is supported. + const isSupportedNetwork = Boolean(new ConfigHelper().getConfig(network)) + setIsSupportedNetwork(isSupportedNetwork) + + // Figure out if we're on a chain's testnet, or not + const networkData = getNetworkData(networksList, network) + setIsTestnet(networkData.network !== 'mainnet') + + const networkName = getNetworkDisplayName(networkData, network) + setNetworkName(networkName) + }, [networkId, networkIdConfig, networksList]) + + return !isEthMainnet && networkName ? ( +
+ {!isSupportedNetwork && ( + + + + )} + {networkName} + {isTestnet && } +
+ ) : null +} diff --git a/src/components/molecules/Wallet/index.module.css b/src/components/molecules/Wallet/index.module.css new file mode 100644 index 000000000..68f94fd0b --- /dev/null +++ b/src/components/molecules/Wallet/index.module.css @@ -0,0 +1,3 @@ +.wallet { + display: flex; +} diff --git a/src/components/molecules/Wallet/index.tsx b/src/components/molecules/Wallet/index.tsx index 6ab20119a..282e17b1a 100644 --- a/src/components/molecules/Wallet/index.tsx +++ b/src/components/molecules/Wallet/index.tsx @@ -2,14 +2,23 @@ import React, { ReactElement } from 'react' import Account from './Account' import Details from './Details' import Tooltip from '../../atoms/Tooltip' +import Network from './Network' import { useOcean } from '@oceanprotocol/react' +import styles from './index.module.css' export default function Wallet(): ReactElement { const { accountId } = useOcean() return ( - } trigger="click focus" disabled={!accountId}> - - +
+ + } + trigger="click focus" + disabled={!accountId} + > + + +
) } diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index 59803db9f..9ffd6fe8c 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -1,10 +1,19 @@ -import { - infuraProjectId as infuraId, - portisId, - network -} from '../../app.config' +import { infuraProjectId as infuraId, portisId } from '../../app.config' import WalletConnectProvider from '@walletconnect/web3-provider' +export interface EthereumListsChain { + name: string + chainId: number + shortName: string + chain: string + network: string + networkId: number + nativeCurrency: { name: string; symbol: string; decimals: number } + rpc: string[] + faucets: string[] + infoURL: string +} + const web3ModalTheme = { background: 'var(--background-body)', main: 'var(--font-color-heading)', @@ -47,28 +56,6 @@ export const web3ModalOpts = { theme: web3ModalTheme } -export function getNetworkId(network: string): number { - switch (network) { - case 'mainnet': - return 1 - case 'ropsten': - return 3 - case 'rinkeby': - return 4 - case 'kovan': - return 42 - case 'development': - return 8996 - default: - return 0 - } -} - -export function isDefaultNetwork(networkId: number): boolean { - const configuredNetwork = getNetworkId(network) - return configuredNetwork === networkId -} - export function accountTruncate(account: string): string { if (!account) return const middle = account.substring(6, 38) @@ -76,19 +63,25 @@ export function accountTruncate(account: string): string { return truncated } -export function getNetworkName(networkId: number): string { - switch (networkId) { - case 1: - return 'Main' - case 3: - return 'Ropsten' - case 4: - return 'Rinkeby' - case 42: - return 'Kovan' - case 8996: - return 'Development' - default: - return 'Unknown' - } +export function getNetworkDisplayName( + data: EthereumListsChain, + networkId: number +): string { + const displayName = data + ? `${data.chain} ${data.network === 'mainnet' ? '' : data.network}` + : networkId === 8996 + ? 'Development' + : 'Unknown' + + return displayName +} + +export function getNetworkData( + data: { node: EthereumListsChain }[], + networkId: number +): EthereumListsChain { + const networkData = data.filter( + ({ node }: { node: EthereumListsChain }) => node.networkId === networkId + )[0] + return networkData.node }