1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Create wallet network switcher (#676)

* created component for wallet network switching

* component styling

* display network names

* created networks config

* fix get network config function

* wip

* moved switcher component inside consume

* use isAssetNetwork to show Switcher component, added to publish

* get network properties using networkList and oceanConfig

* error fix

* hide wallet network switcher if no provider

* use chainId from useAsset ddo

* added switcher component to Compute

* added component to edit metadata and compute settings

* added component to advance settings form

* fixed lint errors

* included component inside Web3Feedback

* updated text and icon design

* button design update, and Web3Feedback position on edit asset

* fixed lint error

* message update

* tag error fixes

* disabled pool and trade buttons if not asset network

* mainnet aquarius fallback url

* filename typo fix

* replace NetworkName component with getNetworkDisplayName function

* added method to switch to EthereumChain networks, removed logs

* fixed lint error

* style tweaks

* markup and styles simplification

* restrict add datatoken

Co-authored-by: Norbi <katunanorbert@gmai.com>
Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
Norbi 2021-07-15 18:03:03 +03:00 committed by GitHub
parent 483ce88d42
commit 565c0324f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 156 additions and 52 deletions

View File

@ -78,6 +78,7 @@
color: var(--brand-pink); color: var(--brand-pink);
box-shadow: none; box-shadow: none;
cursor: pointer; cursor: pointer;
min-width: auto;
} }
/* Size Modifiers */ /* Size Modifiers */

View File

@ -13,6 +13,8 @@ import { transformTags } from '../../utils/metadata'
import NetworkName from '../atoms/NetworkName' import NetworkName from '../atoms/NetworkName'
import { useWeb3 } from '../../providers/Web3' import { useWeb3 } from '../../providers/Web3'
import styles from './MetadataPreview.module.css' import styles from './MetadataPreview.module.css'
import Web3Feedback from './Web3Feedback'
import { useAsset } from '../../providers/Asset'
function Description({ description }: { description: string }) { function Description({ description }: { description: string }) {
const [fullDescription, setFullDescription] = useState<boolean>(false) const [fullDescription, setFullDescription] = useState<boolean>(false)
@ -95,6 +97,7 @@ export function MetadataPreview({
values: Partial<MetadataPublishFormDataset> values: Partial<MetadataPublishFormDataset>
}): ReactElement { }): ReactElement {
const { networkId } = useWeb3() const { networkId } = useWeb3()
const { isAssetNetwork } = useAsset()
return ( return (
<div className={styles.preview}> <div className={styles.preview}>
@ -126,6 +129,9 @@ export function MetadataPreview({
</header> </header>
<MetaFull values={values} /> <MetaFull values={values} />
{isAssetNetwork === false && (
<Web3Feedback isAssetNetwork={isAssetNetwork} />
)}
</div> </div>
) )
} }

View File

@ -0,0 +1,5 @@
.text {
color: var(--color-secondary);
margin-top: calc(var(--spacer) / 4);
margin-bottom: calc(var(--spacer) / 2);
}

View File

@ -0,0 +1,51 @@
import React, { ReactElement } from 'react'
import { useWeb3 } from '../../providers/Web3'
import {
addCustomNetwork,
getNetworkConfigObject,
getNetworkDisplayName,
getNetworkDataById
} from '../../utils/web3'
import Button from '../atoms/Button'
import styles from './WalletNetworkSwitcher.module.css'
import useNetworkMetadata from '../../hooks/useNetworkMetadata'
import { getOceanConfig } from '../../utils/ocean'
import { useAsset } from '../../providers/Asset'
export default function WalletNetworkSwitcher(): ReactElement {
const { networkId, web3Provider } = useWeb3()
const { ddo } = useAsset()
const oceanConfig = getOceanConfig(ddo.chainId)
const { networksList } = useNetworkMetadata()
const ddoNetworkData = getNetworkDataById(networksList, ddo.chainId)
const walletNetworkData = getNetworkDataById(networksList, networkId)
const ddoNetworkName = (
<strong>{getNetworkDisplayName(ddoNetworkData, ddo.chainId)}</strong>
)
const walletNetworkName = (
<strong>{getNetworkDisplayName(walletNetworkData, networkId)}</strong>
)
async function switchWalletNetwork() {
const networkNode = networksList.find(
(data) => data.node.chainId === ddo.chainId
).node
const network = { ...networkNode, providerUri: oceanConfig.providerUri }
const networkConfig = getNetworkConfigObject(network)
addCustomNetwork(web3Provider, networkConfig)
}
return (
<>
<p className={styles.text}>
This asset is published on {ddoNetworkName} but your wallet is connected
to {walletNetworkName}. Connect to {ddoNetworkName} to interact with
this asset.
</p>
<Button size="small" onClick={() => switchWalletNetwork()}>
Switch to {ddoNetworkName}
</Button>
</>
)
}

View File

@ -10,7 +10,7 @@
.feedback i { .feedback i {
position: absolute; position: absolute;
left: 0; left: 0;
top: calc(var(--spacer) / 1.5); top: calc(var(--spacer) / 1.45);
} }
.title { .title {

View File

@ -2,6 +2,7 @@ import React, { ReactElement } from 'react'
import { useWeb3 } from '../../providers/Web3' import { useWeb3 } from '../../providers/Web3'
import Status from '../atoms/Status' import Status from '../atoms/Status'
import styles from './Web3Feedback.module.css' import styles from './Web3Feedback.module.css'
import WalletNetworkSwitcher from './WalletNetworkSwitcher'
export declare type Web3Error = { export declare type Web3Error = {
status: 'error' | 'warning' | 'success' status: 'error' | 'warning' | 'success'
@ -34,7 +35,7 @@ export default function Web3Feedback({
: // : !ocean : // : !ocean
// ? 'Error connecting to Ocean' // ? 'Error connecting to Ocean'
accountId && isAssetNetwork === false accountId && isAssetNetwork === false
? 'Wrong network' ? 'Not connected to asset network'
: accountId : accountId
? isBalanceSufficient === false ? isBalanceSufficient === false
? 'Insufficient balance' ? 'Insufficient balance'
@ -47,15 +48,17 @@ export default function Web3Feedback({
// ? 'Please try again.' // ? 'Please try again.'
isBalanceSufficient === false isBalanceSufficient === false
? 'You do not have enough OCEAN in your wallet to purchase this asset.' ? 'You do not have enough OCEAN in your wallet to purchase this asset.'
: isAssetNetwork === false
? 'Connect to the asset network.'
: 'Something went wrong.' : 'Something went wrong.'
return showFeedback ? ( return showFeedback ? (
<section className={styles.feedback}> <section className={styles.feedback}>
<Status state={state} aria-hidden /> <Status state={state} aria-hidden />
<h3 className={styles.title}>{title}</h3> <h3 className={styles.title}>{title}</h3>
{message && <p className={styles.error}>{message}</p>} {isAssetNetwork === false ? (
<WalletNetworkSwitcher />
) : (
message && <p className={styles.error}>{message}</p>
)}
</section> </section>
) : null ) : null
} }

View File

@ -6,7 +6,6 @@
.info { .info {
display: flex; display: flex;
width: auto; width: auto;
padding: 0 calc(var(--spacer)) 0 calc(var(--spacer));
} }
.filewrapper { .filewrapper {
@ -15,4 +14,5 @@
.feedback { .feedback {
width: 100%; width: 100%;
margin-top: calc(var(--spacer));
} }

View File

@ -20,6 +20,7 @@ import {
setMinterToDispenser, setMinterToDispenser,
setMinterToPublisher setMinterToPublisher
} from '../../../../utils/freePrice' } from '../../../../utils/freePrice'
import Web3Feedback from '../../../molecules/Web3Feedback'
const contentQuery = graphql` const contentQuery = graphql`
query EditAvanceSettingsQuery { query EditAvanceSettingsQuery {
@ -72,7 +73,7 @@ export default function EditAdvancedSettings({
const { debug } = useUserPreferences() const { debug } = useUserPreferences()
const { accountId } = useWeb3() const { accountId } = useWeb3()
const { ocean } = useOcean() const { ocean } = useOcean()
const { metadata, ddo, refreshDdo, price } = useAsset() const { isAssetNetwork, ddo, refreshDdo, price } = useAsset()
const [success, setSuccess] = useState<string>() const [success, setSuccess] = useState<string>()
const [error, setError] = useState<string>() const [error, setError] = useState<string>()
const { appConfig } = useSiteMetadata() const { appConfig } = useSiteMetadata()
@ -168,7 +169,7 @@ export default function EditAdvancedSettings({
setShowEdit={setShowEdit} setShowEdit={setShowEdit}
/> />
</article> </article>
<Web3Feedback isAssetNetwork={isAssetNetwork} />
{debug === true && ( {debug === true && (
<div className={styles.grid}> <div className={styles.grid}>
<DebugEditCredential <DebugEditCredential

View File

@ -20,6 +20,7 @@ import {
setMinterToDispenser, setMinterToDispenser,
setMinterToPublisher setMinterToPublisher
} from '../../../../utils/freePrice' } from '../../../../utils/freePrice'
import Web3Feedback from '../../../molecules/Web3Feedback'
const contentQuery = graphql` const contentQuery = graphql`
query EditComputeDataQuery { query EditComputeDataQuery {
@ -66,7 +67,7 @@ export default function EditComputeDataset({
const { debug } = useUserPreferences() const { debug } = useUserPreferences()
const { ocean } = useOcean() const { ocean } = useOcean()
const { accountId } = useWeb3() const { accountId } = useWeb3()
const { ddo, refreshDdo, price } = useAsset() const { ddo, price, isAssetNetwork, refreshDdo } = useAsset()
const [success, setSuccess] = useState<string>() const [success, setSuccess] = useState<string>()
const [error, setError] = useState<string>() const [error, setError] = useState<string>()
@ -169,7 +170,7 @@ export default function EditComputeDataset({
setShowEdit={setShowEdit} setShowEdit={setShowEdit}
/> />
</article> </article>
<Web3Feedback isAssetNetwork={isAssetNetwork} />
{debug === true && ( {debug === true && (
<div className={styles.grid}> <div className={styles.grid}>
<DebugEditCompute values={values} ddo={ddo} /> <DebugEditCompute values={values} ddo={ddo} />

View File

@ -7,6 +7,7 @@ import { FormFieldProps } from '../../../../@types/Form'
import { useOcean } from '../../../../providers/Ocean' import { useOcean } from '../../../../providers/Ocean'
import { useWeb3 } from '../../../../providers/Web3' import { useWeb3 } from '../../../../providers/Web3'
import { AdvancedSettingsForm } from '../../../../models/FormEditCredential' import { AdvancedSettingsForm } from '../../../../models/FormEditCredential'
import { useAsset } from '../../../../providers/Asset'
export default function FormAdvancedSettings({ export default function FormAdvancedSettings({
data, data,
@ -16,6 +17,7 @@ export default function FormAdvancedSettings({
setShowEdit: (show: boolean) => void setShowEdit: (show: boolean) => void
}): ReactElement { }): ReactElement {
const { accountId } = useWeb3() const { accountId } = useWeb3()
const { isAssetNetwork } = useAsset()
const { ocean, config } = useOcean() const { ocean, config } = useOcean()
const { const {
isValid, isValid,
@ -45,9 +47,11 @@ export default function FormAdvancedSettings({
} }
/> />
))} ))}
<footer className={styles.actions}> <footer className={styles.actions}>
<Button style="primary" disabled={!ocean || !accountId || !isValid}> <Button
style="primary"
disabled={!ocean || !accountId || !isValid || !isAssetNetwork}
>
Submit Submit
</Button> </Button>
<Button style="text" onClick={() => setShowEdit(false)}> <Button style="text" onClick={() => setShowEdit(false)}>

View File

@ -17,7 +17,6 @@ import { ComputePrivacyForm } from '../../../../models/FormEditComputeDataset'
import { publisherTrustedAlgorithm as PublisherTrustedAlgorithm } from '@oceanprotocol/lib' import { publisherTrustedAlgorithm as PublisherTrustedAlgorithm } from '@oceanprotocol/lib'
import axios from 'axios' import axios from 'axios'
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata' import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
import { chainIds } from '../../../../../app.config'
export default function FormEditComputeDataset({ export default function FormEditComputeDataset({
data, data,
@ -31,7 +30,7 @@ export default function FormEditComputeDataset({
const { appConfig } = useSiteMetadata() const { appConfig } = useSiteMetadata()
const { accountId } = useWeb3() const { accountId } = useWeb3()
const { ocean } = useOcean() const { ocean } = useOcean()
const { ddo } = useAsset() const { ddo, isAssetNetwork } = useAsset()
const { isValid, values }: FormikContextType<ComputePrivacyForm> = const { isValid, values }: FormikContextType<ComputePrivacyForm> =
useFormikContext() useFormikContext()
const [allAlgorithms, setAllAlgorithms] = useState<AssetSelectionAsset[]>() const [allAlgorithms, setAllAlgorithms] = useState<AssetSelectionAsset[]>()
@ -89,7 +88,10 @@ export default function FormEditComputeDataset({
/> />
))} ))}
<footer className={styles.actions}> <footer className={styles.actions}>
<Button style="primary" disabled={!ocean || !accountId || !isValid}> <Button
style="primary"
disabled={!ocean || !accountId || !isValid || !isAssetNetwork}
>
Submit Submit
</Button> </Button>
<Button style="text" onClick={() => setShowEdit(false)}> <Button style="text" onClick={() => setShowEdit(false)}>

View File

@ -8,6 +8,7 @@ import { MetadataPublishFormDataset } from '../../../../@types/MetaData'
import { checkIfTimeoutInPredefinedValues } from '../../../../utils/metadata' import { checkIfTimeoutInPredefinedValues } from '../../../../utils/metadata'
import { useOcean } from '../../../../providers/Ocean' import { useOcean } from '../../../../providers/Ocean'
import { useWeb3 } from '../../../../providers/Web3' import { useWeb3 } from '../../../../providers/Web3'
import { useAsset } from '../../../../providers/Asset'
function handleTimeoutCustomOption( function handleTimeoutCustomOption(
data: FormFieldProps[], data: FormFieldProps[],
@ -58,6 +59,7 @@ export default function FormEditMetadata({
}): ReactElement { }): ReactElement {
const { accountId } = useWeb3() const { accountId } = useWeb3()
const { ocean, config } = useOcean() const { ocean, config } = useOcean()
const { isAssetNetwork } = useAsset()
const { const {
isValid, isValid,
validateField, validateField,
@ -99,7 +101,7 @@ export default function FormEditMetadata({
<footer className={styles.actions}> <footer className={styles.actions}>
<Button <Button
style="primary" style="primary"
disabled={!ocean || !accountId || !isValid} disabled={!ocean || !accountId || !isValid || !isAssetNetwork}
onClick={() => setTimeoutStringValue(values.timeout)} onClick={() => setTimeoutStringValue(values.timeout)}
> >
Submit Submit

View File

@ -64,7 +64,8 @@ export default function Pool(): ReactElement {
const { accountId, networkId } = useWeb3() const { accountId, networkId } = useWeb3()
const { ocean } = useOcean() const { ocean } = useOcean()
const { isInPurgatory, ddo, owner, price, refreshInterval } = useAsset() const { isInPurgatory, ddo, owner, price, refreshInterval, isAssetNetwork } =
useAsset()
const dtSymbol = ddo?.dataTokenInfo.symbol const dtSymbol = ddo?.dataTokenInfo.symbol
const [poolTokens, setPoolTokens] = useState<string>() const [poolTokens, setPoolTokens] = useState<string>()
@ -333,14 +334,18 @@ export default function Pool(): ReactElement {
style="primary" style="primary"
size="small" size="small"
onClick={() => setShowAdd(true)} onClick={() => setShowAdd(true)}
disabled={isInPurgatory} disabled={isInPurgatory || !isAssetNetwork}
> >
Add Liquidity Add Liquidity
</Button> </Button>
)} )}
{hasAddedLiquidity && !isRemoveDisabled && ( {hasAddedLiquidity && !isRemoveDisabled && (
<Button size="small" onClick={() => setShowRemove(true)}> <Button
size="small"
onClick={() => setShowRemove(true)}
disabled={!isAssetNetwork}
>
Remove Remove
</Button> </Button>
)} )}

View File

@ -14,6 +14,7 @@ import { FormTradeData, initialValues } from '../../../../models/FormTrade'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
import { useOcean } from '../../../../providers/Ocean' import { useOcean } from '../../../../providers/Ocean'
import { useWeb3 } from '../../../../providers/Web3' import { useWeb3 } from '../../../../providers/Web3'
import { useAsset } from '../../../../providers/Asset'
const contentQuery = graphql` const contentQuery = graphql`
query TradeQuery { query TradeQuery {
@ -49,6 +50,7 @@ export default function FormTrade({
const content = data.content.edges[0].node.childContentJson.trade const content = data.content.edges[0].node.childContentJson.trade
const { accountId } = useWeb3() const { accountId } = useWeb3()
const { ocean } = useOcean() const { ocean } = useOcean()
const { isAssetNetwork } = useAsset()
const { debug } = useUserPreferences() const { debug } = useUserPreferences()
const [txId, setTxId] = useState<string>() const [txId, setTxId] = useState<string>()
@ -139,7 +141,7 @@ export default function FormTrade({
</div> </div>
)} )}
<Actions <Actions
isDisabled={!isWarningAccepted} isDisabled={!isWarningAccepted || !isAssetNetwork}
isLoading={isSubmitting} isLoading={isSubmitting}
loaderMessage="Swapping tokens..." loaderMessage="Swapping tokens..."
successMessage="Successfully swapped tokens." successMessage="Successfully swapped tokens."

View File

@ -147,12 +147,10 @@ export default function AssetActions(): ReactElement {
<Permission eventType="consume"> <Permission eventType="consume">
<Tabs items={tabs} className={styles.actions} /> <Tabs items={tabs} className={styles.actions} />
</Permission> </Permission>
{type !== 'algorithm' && ( <Web3Feedback
<Web3Feedback isBalanceSufficient={isBalanceSufficient}
isBalanceSufficient={isBalanceSufficient} isAssetNetwork={isAssetNetwork}
isAssetNetwork={isAssetNetwork} />
/>
)}
</> </>
) )
} }

View File

@ -9,7 +9,7 @@ import AssetType from '../../atoms/AssetType'
import styles from './MetaMain.module.css' import styles from './MetaMain.module.css'
export default function MetaMain(): ReactElement { export default function MetaMain(): ReactElement {
const { ddo, owner, type } = useAsset() const { ddo, owner, type, isAssetNetwork } = useAsset()
const { networkId, web3ProviderInfo } = useWeb3() const { networkId, web3ProviderInfo } = useWeb3()
const isCompute = Boolean(ddo?.findServiceByType('compute')) const isCompute = Boolean(ddo?.findServiceByType('compute'))
const accessType = isCompute ? 'compute' : 'access' const accessType = isCompute ? 'compute' : 'access'
@ -35,7 +35,7 @@ export default function MetaMain(): ReactElement {
{`${ddo?.dataTokenInfo.name}${ddo?.dataTokenInfo.symbol}`} {`${ddo?.dataTokenInfo.name}${ddo?.dataTokenInfo.symbol}`}
</ExplorerLink> </ExplorerLink>
{web3ProviderInfo?.name === 'MetaMask' && ( {web3ProviderInfo?.name === 'MetaMask' && isAssetNetwork && (
<span className={styles.addWrap}> <span className={styles.addWrap}>
<AddToken <AddToken
address={ddo?.dataTokenInfo.address} address={ddo?.dataTokenInfo.address}

View File

@ -46,7 +46,7 @@ export default function AssetContent(props: AssetContentProps): ReactElement {
const content = data.purgatory.edges[0].node.childContentJson.asset const content = data.purgatory.edges[0].node.childContentJson.asset
const { debug } = useUserPreferences() const { debug } = useUserPreferences()
const { accountId } = useWeb3() const { accountId } = useWeb3()
const { owner, isInPurgatory, purgatoryData } = useAsset() const { owner, isInPurgatory, purgatoryData, isAssetNetwork } = useAsset()
const [showPricing, setShowPricing] = useState(false) const [showPricing, setShowPricing] = useState(false)
const [showEdit, setShowEdit] = useState<boolean>() const [showEdit, setShowEdit] = useState<boolean>()
const [showEditCompute, setShowEditCompute] = useState<boolean>() const [showEditCompute, setShowEditCompute] = useState<boolean>()
@ -110,7 +110,7 @@ export default function AssetContent(props: AssetContentProps): ReactElement {
<MetaSecondary /> <MetaSecondary />
{isOwner && ( {isOwner && isAssetNetwork && (
<div className={styles.ownerActions}> <div className={styles.ownerActions}>
<Button style="text" size="small" onClick={handleEditButton}> <Button style="text" size="small" onClick={handleEditButton}>
Edit Metadata Edit Metadata

View File

@ -56,7 +56,6 @@ function TabContent({
) : ( ) : (
<MetadataAlgorithmPreview values={values} /> <MetadataAlgorithmPreview values={values} />
)} )}
<Web3Feedback /> <Web3Feedback />
</div> </div>
</aside> </aside>

View File

@ -228,7 +228,6 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement {
// ----------------------------------- // -----------------------------------
useEffect(() => { useEffect(() => {
if (!networkId) return if (!networkId) return
const networkData = getNetworkDataById(networksList, networkId) const networkData = getNetworkDataById(networksList, networkId)
setNetworkData(networkData) setNetworkData(networkData)
Logger.log( Logger.log(

View File

@ -19,6 +19,16 @@ export interface NetworkObject {
urlList: string[] urlList: string[]
} }
export function getNetworkConfigObject(node: any): NetworkObject {
const networkConfig = {
name: node.chain,
symbol: node.nativeCurrency.symbol,
chainId: node.chainId,
urlList: [node.providerUri]
}
return networkConfig
}
export function accountTruncate(account: string): string { export function accountTruncate(account: string): string {
if (!account) return if (!account) return
const middle = account.substring(6, 38) const middle = account.substring(6, 38)
@ -64,6 +74,7 @@ export function getNetworkDataById(
data: { node: EthereumListsChain }[], data: { node: EthereumListsChain }[],
networkId: number networkId: number
): EthereumListsChain { ): EthereumListsChain {
if (!networkId) return
const networkData = data.filter( const networkData = data.filter(
({ node }: { node: EthereumListsChain }) => node.chainId === networkId ({ node }: { node: EthereumListsChain }) => node.chainId === networkId
) )
@ -71,34 +82,48 @@ export function getNetworkDataById(
return networkData[0]?.node return networkData[0]?.node
} }
export function addCustomNetwork( export async function addCustomNetwork(
web3Provider: any, web3Provider: any,
network: NetworkObject network: NetworkObject
): void { ): Promise<void> {
const newNewtworkData = { const newNewtworkData = {
chainId: `0x${network.chainId.toString(16)}`, chainId: `0x${network.chainId.toString(16)}`,
chainName: network.name, chainName: network.name,
rpcUrls: network.urlList rpcUrls: network.urlList
} }
web3Provider.request( try {
{ await web3Provider.request({
method: 'wallet_addEthereumChain', method: 'wallet_switchEthereumChain',
params: [newNewtworkData] params: [{ chainId: newNewtworkData.chainId }]
}, })
(err: string, added: any) => { } catch (switchError) {
if (err || 'error' in added) { if (switchError.code === 4902) {
Logger.error( web3Provider.request(
`Couldn't add ${network.name} (0x${ {
network.chainId method: 'wallet_addEthereumChain',
}) netowrk to MetaMask, error: ${err || added.error}` params: [newNewtworkData]
) },
} else { (err: string, added: any) => {
Logger.log( if (err || 'error' in added) {
`Added ${network.name} (0x${network.chainId}) network to MetaMask` Logger.error(
) `Couldn't add ${network.name} (0x${
} network.chainId
}) netowrk to MetaMask, error: ${err || added.error}`
)
} else {
Logger.log(
`Added ${network.name} (0x${network.chainId}) network to MetaMask`
)
}
}
)
} else {
Logger.error(
`Couldn't add ${network.name} (0x${network.chainId}) netowrk to MetaMask, error: ${switchError}`
)
} }
) }
Logger.log(`Added ${network.name} (0x${network.chainId}) network to MetaMask`)
} }
export async function addTokenToWallet( export async function addTokenToWallet(