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

Merge branch 'main' into feature/compute

This commit is contained in:
Bogdan Fazakas 2021-03-05 13:51:26 +02:00 committed by GitHub
commit d1c4cdb29f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 293 additions and 114 deletions

1
.gitignore vendored
View File

@ -12,4 +12,5 @@ public/storybook
.artifacts .artifacts
.vercel .vercel
repo-metadata.json repo-metadata.json
networks-metadata.json
src/@types/apollo src/@types/apollo

View File

@ -4,11 +4,14 @@ module.exports = {
// networks in their wallet. // networks in their wallet.
// Ocean Protocol contracts are deployed for: 'mainnet', 'rinkeby', 'development' // Ocean Protocol contracts are deployed for: 'mainnet', 'rinkeby', 'development'
network: process.env.GATSBY_NETWORK || 'mainnet', network: process.env.GATSBY_NETWORK || 'mainnet',
infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx', infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx',
// The ETH address the marketplace fee will be sent to. // The ETH address the marketplace fee will be sent to.
marketFeeAddress: marketFeeAddress:
process.env.GATSBY_MARKET_FEE_ADDRESS || process.env.GATSBY_MARKET_FEE_ADDRESS ||
'0x903322C7E45A60d7c8C3EA236c5beA9Af86310c7', '0x903322C7E45A60d7c8C3EA236c5beA9Af86310c7',
// Used for conversion display, can be whatever coingecko API supports // Used for conversion display, can be whatever coingecko API supports
// see: https://api.coingecko.com/api/v3/simple/supported_vs_currencies // see: https://api.coingecko.com/api/v3/simple/supported_vs_currencies
currencies: [ currencies: [

View File

@ -11,6 +11,14 @@ execSync(`node ./scripts/write-repo-metadata > repo-metadata.json`, {
// Generate Apollo typings // Generate Apollo typings
execSync(`npm run apollo:codegen`, { stdio: 'inherit' }) 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 }) => { exports.onCreateNode = ({ node, actions, getNode }) => {
createFields(node, actions, getNode) createFields(node, actions, getNode)
} }

View File

@ -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, ' '))
})

View File

@ -1,8 +1,23 @@
import React, { ReactElement, ReactNode } from 'react' import React, { ReactElement, ReactNode, useEffect, useState } from 'react'
import { getNetworkName } from '../../utils/wallet' import { EthereumListsChain, getNetworkData } from '../../utils/wallet'
import { ReactComponent as External } from '../../images/external.svg' import { ReactComponent as External } from '../../images/external.svg'
import styles from './EtherscanLink.module.css' import styles from './EtherscanLink.module.css'
import { useSiteMetadata } from '../../hooks/useSiteMetadata' import { useSiteMetadata } from '../../hooks/useSiteMetadata'
import { graphql, useStaticQuery } from 'gatsby'
const networksQuery = graphql`
query {
allNetworksMetadataJson {
edges {
node {
chain
network
networkId
}
}
}
}
`
export default function EtherscanLink({ export default function EtherscanLink({
networkId, networkId,
@ -13,16 +28,27 @@ export default function EtherscanLink({
path: string path: string
children: ReactNode children: ReactNode
}): ReactElement { }): ReactElement {
const data = useStaticQuery(networksQuery)
const networksList: { node: EthereumListsChain }[] =
data.allNetworksMetadataJson.edges
const { appConfig } = useSiteMetadata() const { appConfig } = useSiteMetadata()
const [url, setUrl] = useState<string>()
useEffect(() => {
const networkData = networkId
? getNetworkData(networksList, networkId)
: null
const url = const url =
(!networkId && appConfig.network === 'mainnet') || networkId === 1 (!networkId && appConfig.network === 'mainnet') || networkId === 1
? `https://etherscan.io` ? `https://etherscan.io`
: `https://${ : `https://${
networkId networkData ? networkData.network : appConfig.network
? getNetworkName(networkId).toLowerCase()
: appConfig.network
}.etherscan.io` }.etherscan.io`
setUrl(url)
}, [networkId, networksList, appConfig.network])
return ( return (
<a <a
href={`${url}/${path}`} href={`${url}/${path}`}

View File

@ -40,7 +40,6 @@ export default function AssetListTitle({
!ddo && did && getAssetName() !ddo && did && getAssetName()
return () => { return () => {
console.log('canceled?')
source.cancel() source.cancel()
} }
}, [assetTitle, config?.metadataCacheUri, ddo, did, title]) }, [assetTitle, config?.metadataCacheUri, ddo, did, title])

View File

@ -12,6 +12,7 @@
transition: border 0.2s ease-out; transition: border 0.2s ease-out;
cursor: pointer; cursor: pointer;
min-width: 190px; min-width: 190px;
height: 100%;
} }
.button, .button,
@ -42,7 +43,7 @@
.address { .address {
text-transform: none; text-transform: none;
border-right: 1px solid var(--border-color); border-right: 1px solid var(--border-color);
padding-right: calc(var(--spacer) / 4); padding-right: calc(var(--spacer) / 3);
} }
.button svg { .button svg {
@ -51,7 +52,7 @@
fill: var(--border-color); fill: var(--border-color);
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
margin-left: calc(var(--spacer) / 4); margin-left: calc(var(--spacer) / 3);
transition: transform 0.2s ease-out; transition: transform 0.2s ease-out;
} }

View File

@ -24,8 +24,8 @@ const Blockies = ({ account }: { account: string | undefined }) => {
// Forward ref for Tippy.js // Forward ref for Tippy.js
// eslint-disable-next-line // eslint-disable-next-line
const Account = React.forwardRef((props, ref: any) => { const Account = React.forwardRef((props, ref: any) => {
const { accountId, status, connect, networkId, web3Modal } = useOcean() const { accountId, status, connect, web3Modal } = useOcean()
const hasSuccess = status === 1 && networkId === 1 const hasSuccess = status === 1
async function handleActivation(e: FormEvent<HTMLButtonElement>) { async function handleActivation(e: FormEvent<HTMLButtonElement>) {
// prevent accidentially submitting a form the button might be in // prevent accidentially submitting a form the button might be in

View File

@ -2,7 +2,6 @@ import React, { ReactElement } from 'react'
import Status from '../../atoms/Status' import Status from '../../atoms/Status'
import styles from './Feedback.module.css' import styles from './Feedback.module.css'
import { useOcean } from '@oceanprotocol/react' import { useOcean } from '@oceanprotocol/react'
import { getNetworkName } from '../../../utils/wallet'
export declare type Web3Error = { export declare type Web3Error = {
status: 'error' | 'warning' | 'success' status: 'error' | 'warning' | 'success'
@ -15,19 +14,13 @@ export default function Web3Feedback({
}: { }: {
isBalanceSufficient?: boolean isBalanceSufficient?: boolean
}): ReactElement { }): ReactElement {
const { account, status, networkId } = useOcean() const { account, status } = useOcean()
const isOceanConnectionError = status === -1 const isOceanConnectionError = status === -1
const isMainnet = networkId === 1
const showFeedback = const showFeedback =
!account || !account || isOceanConnectionError || isBalanceSufficient === false
isOceanConnectionError ||
!isMainnet ||
isBalanceSufficient === false
const state = !account const state = !account
? 'error' ? 'error'
: !isMainnet
? 'warning'
: account && isBalanceSufficient : account && isBalanceSufficient
? 'success' ? 'success'
: 'warning' : 'warning'
@ -36,8 +29,6 @@ export default function Web3Feedback({
? 'No account connected' ? 'No account connected'
: isOceanConnectionError : isOceanConnectionError
? 'Error connecting to Ocean' ? 'Error connecting to Ocean'
: !isMainnet
? getNetworkName(networkId)
: account : account
? isBalanceSufficient === false ? isBalanceSufficient === false
? 'Insufficient balance' ? 'Insufficient balance'
@ -48,8 +39,6 @@ export default function Web3Feedback({
? 'Please connect your Web3 wallet.' ? 'Please connect your Web3 wallet.'
: isOceanConnectionError : isOceanConnectionError
? 'Please try again.' ? 'Please try again.'
: !isMainnet
? undefined
: 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.'
: 'Something went wrong.' : 'Something went wrong.'

View File

@ -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);
}

View File

@ -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<boolean>()
const [networkName, setNetworkName] = useState<string>()
const [isTestnet, setIsTestnet] = useState<boolean>()
const [isSupportedNetwork, setIsSupportedNetwork] = useState<boolean>()
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 ? (
<div className={styles.network}>
{!isSupportedNetwork && (
<Tooltip content="No Ocean Protocol contracts are deployed to this network.">
<Status state="error" className={styles.warning} />
</Tooltip>
)}
<span className={styles.name}>{networkName}</span>
{isTestnet && <Badge label="Test" className={styles.badge} />}
</div>
) : null
}

View File

@ -0,0 +1,3 @@
.wallet {
display: flex;
}

View File

@ -2,14 +2,23 @@ import React, { ReactElement } from 'react'
import Account from './Account' import Account from './Account'
import Details from './Details' import Details from './Details'
import Tooltip from '../../atoms/Tooltip' import Tooltip from '../../atoms/Tooltip'
import Network from './Network'
import { useOcean } from '@oceanprotocol/react' import { useOcean } from '@oceanprotocol/react'
import styles from './index.module.css'
export default function Wallet(): ReactElement { export default function Wallet(): ReactElement {
const { accountId } = useOcean() const { accountId } = useOcean()
return ( return (
<Tooltip content={<Details />} trigger="click focus" disabled={!accountId}> <div className={styles.wallet}>
<Network />
<Tooltip
content={<Details />}
trigger="click focus"
disabled={!accountId}
>
<Account /> <Account />
</Tooltip> </Tooltip>
</div>
) )
} }

View File

@ -12,6 +12,23 @@ import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
import checkPreviousOrder from '../../../utils/checkPreviousOrder' import checkPreviousOrder from '../../../utils/checkPreviousOrder'
import { useAsset } from '../../../providers/Asset' import { useAsset } from '../../../providers/Asset'
import { secondsToString } from '../../../utils/metadata' import { secondsToString } from '../../../utils/metadata'
import { gql, useQuery } from '@apollo/client'
import { OrdersData } from '../../../@types/apollo/OrdersData'
import BigNumber from 'bignumber.js'
const previousOrderQuery = gql`
query PreviousOrder($id: String!, $account: String!) {
tokenOrders(
first: 1
where: { datatokenId: $id, payer: $account }
orderBy: timestamp
orderDirection: desc
) {
timestamp
tx
}
}
`
function getHelpText( function getHelpText(
token: { token: {
@ -58,6 +75,34 @@ export default function Consume({
const [isConsumable, setIsConsumable] = useState(true) const [isConsumable, setIsConsumable] = useState(true)
const [assetTimeout, setAssetTimeout] = useState('') const [assetTimeout, setAssetTimeout] = useState('')
const { data } = useQuery<OrdersData>(previousOrderQuery, {
variables: {
id: ddo.dataToken?.toLowerCase(),
account: accountId?.toLowerCase()
},
pollInterval: 5000
})
useEffect(() => {
if (!data || !assetTimeout || data.tokenOrders.length === 0) return
const lastOrder = data.tokenOrders[0]
if (assetTimeout === 'Forever') {
setPreviousOrderId(lastOrder.tx)
setHasPreviousOrder(true)
} else {
const expiry = new BigNumber(lastOrder.timestamp).plus(assetTimeout)
const unixTime = new BigNumber(Math.floor(Date.now() / 1000))
if (unixTime.isLessThan(expiry)) {
setPreviousOrderId(lastOrder.tx)
setHasPreviousOrder(true)
} else {
setHasPreviousOrder(false)
}
}
}, [data, assetTimeout])
useEffect(() => { useEffect(() => {
const { timeout } = ddo.findServiceByType('access').attributes.main const { timeout } = ddo.findServiceByType('access').attributes.main
setAssetTimeout(secondsToString(timeout)) setAssetTimeout(secondsToString(timeout))
@ -95,18 +140,6 @@ export default function Consume({
hasDatatoken hasDatatoken
]) ])
useEffect(() => {
if (!ocean || !accountId) return
async function checkOrders() {
// HEADS UP! checkPreviousOrder() also checks for expiration of possible set timeout.
const orderId = await checkPreviousOrder(ocean, accountId, ddo, 'access')
setPreviousOrderId(orderId)
setHasPreviousOrder(!!orderId)
}
checkOrders()
}, [ocean, ddo, accountId])
async function handleConsume() { async function handleConsume() {
!hasPreviousOrder && !hasDatatoken && (await buyDT('1')) !hasPreviousOrder && !hasDatatoken && (await buyDT('1'))
await consume( await consume(

View File

@ -4,52 +4,53 @@ import { useAsset } from '../../../providers/Asset'
import EtherscanLink from '../../atoms/EtherscanLink' import EtherscanLink from '../../atoms/EtherscanLink'
import Time from '../../atoms/Time' import Time from '../../atoms/Time'
import styles from './EditHistory.module.css' import styles from './EditHistory.module.css'
import { gql, useQuery } from '@apollo/client'
import { ReceiptData_datatokens_updates as ReceiptData } from '../../../@types/apollo/ReceiptData'
interface Receipt { const getReceipts = gql`
hash: string query ReceiptData($address: ID!) {
timestamp: string datatokens(where: { id: $address }) {
createTime
tx
updates(orderBy: timestamp, orderDirection: desc) {
id
tx
timestamp
} }
// TODO: fetch for real
const fakeReceipts = [
{
hash: '0xxxxxxxxx',
timestamp: '1607460269'
},
{
hash: '0xxxxxxxxx',
timestamp: '1606460159'
},
{
hash: '0xxxxxxxxx',
timestamp: '1506460159'
} }
] }
`
export default function EditHistory(): ReactElement { export default function EditHistory(): ReactElement {
const { networkId } = useOcean() const { networkId } = useOcean()
const { ddo } = useAsset() const { ddo } = useAsset()
const { data } = useQuery(getReceipts, {
variables: { address: ddo?.dataToken.toLowerCase() }
})
const [receipts, setReceipts] = useState<Receipt[]>() const [receipts, setReceipts] = useState<ReceiptData[]>()
const [creationTx, setCreationTx] = useState<string>()
useEffect(() => { useEffect(() => {
setReceipts(fakeReceipts) if (!data) return
}, []) setReceipts(data.datatokens[0].updates)
setCreationTx(data.datatokens[0].tx)
}, [data])
return ( return (
<> <>
<h3 className={styles.title}>Metadata History</h3> <h3 className={styles.title}>Metadata History</h3>
<ul className={styles.history}> <ul className={styles.history}>
{receipts?.map((receipt) => ( {receipts?.map((receipt) => (
<li key={receipt.hash} className={styles.item}> <li key={receipt.id} className={styles.item}>
<EtherscanLink networkId={networkId} path={`/tx/${receipt.hash}`}> <EtherscanLink networkId={networkId} path={`/tx/${receipt.tx}`}>
edited <Time date={receipt.timestamp} relative isUnix /> edited{' '}
<Time date={receipt.timestamp.toString()} relative isUnix />
</EtherscanLink> </EtherscanLink>
</li> </li>
))} ))}
<li className={styles.item}> <li className={styles.item}>
{/* TODO: get this initial metadata creation tx somehow */} <EtherscanLink networkId={networkId} path={`/tx/${creationTx}`}>
<EtherscanLink networkId={networkId} path="/tx/xxx">
published <Time date={ddo.created} relative /> published <Time date={ddo.created} relative />
</EtherscanLink> </EtherscanLink>
</li> </li>

View File

@ -25,6 +25,7 @@ export default function MetaFull(): ReactElement {
title="Owner" title="Owner"
content={<Publisher account={ddo?.publicKey[0].owner} />} content={<Publisher account={ddo?.publicKey[0].owner} />}
/> />
{type === 'algorithm' && ( {type === 'algorithm' && (
<MetaItem title="Docker Image" content={<DockerImage />} /> <MetaItem title="Docker Image" content={<DockerImage />} />
)} )}

View File

@ -15,7 +15,7 @@ import Button from '../../atoms/Button'
import Edit from '../AssetActions/Edit' import Edit from '../AssetActions/Edit'
import DebugOutput from '../../atoms/DebugOutput' import DebugOutput from '../../atoms/DebugOutput'
import MetaMain from './MetaMain' import MetaMain from './MetaMain'
// import EditHistory from './EditHistory' import EditHistory from './EditHistory'
export interface AssetContentProps { export interface AssetContentProps {
path?: string path?: string
@ -97,7 +97,7 @@ export default function AssetContent(props: AssetContentProps): ReactElement {
)} )}
<MetaFull /> <MetaFull />
{/* <EditHistory /> */} <EditHistory />
{debug === true && <DebugOutput title="DDO" output={ddo} />} {debug === true && <DebugOutput title="DDO" output={ddo} />}
</div> </div>
</div> </div>

View File

@ -1,10 +1,19 @@
import { import { infuraProjectId as infuraId, portisId } from '../../app.config'
infuraProjectId as infuraId,
portisId,
network
} from '../../app.config'
import WalletConnectProvider from '@walletconnect/web3-provider' 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 = { const web3ModalTheme = {
background: 'var(--background-body)', background: 'var(--background-body)',
main: 'var(--font-color-heading)', main: 'var(--font-color-heading)',
@ -47,28 +56,6 @@ export const web3ModalOpts = {
theme: web3ModalTheme 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 { export function accountTruncate(account: string): string {
if (!account) return if (!account) return
const middle = account.substring(6, 38) const middle = account.substring(6, 38)
@ -76,19 +63,25 @@ export function accountTruncate(account: string): string {
return truncated return truncated
} }
export function getNetworkName(networkId: number): string { export function getNetworkDisplayName(
switch (networkId) { data: EthereumListsChain,
case 1: networkId: number
return 'Main' ): string {
case 3: const displayName = data
return 'Ropsten' ? `${data.chain} ${data.network === 'mainnet' ? '' : data.network}`
case 4: : networkId === 8996
return 'Rinkeby' ? 'Development'
case 42: : 'Unknown'
return 'Kovan'
case 8996: return displayName
return 'Development'
default:
return 'Unknown'
} }
export function getNetworkData(
data: { node: EthereumListsChain }[],
networkId: number
): EthereumListsChain {
const networkData = data.filter(
({ node }: { node: EthereumListsChain }) => node.networkId === networkId
)[0]
return networkData.node
} }