1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-11-13 16:54:53 +01:00

Handle price fetching better (#265)

* fix price refresh, add AssetProvider

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* price comment

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* fix trade

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* fix trade

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* fix pool liquidity

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* fix AssetProvider

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* fix

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* fix import

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* remove console.log

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* pool refresh fix

* simplify, logging

* handle errors

* cleanup

Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
mihaisc 2020-11-20 15:31:28 +02:00 committed by GitHub
parent 7325e093ad
commit b5a11d6d0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 267 additions and 105 deletions

View File

@ -3,7 +3,7 @@ module.exports = {
// on start. App will automatically switch network configs when user switches
// networks in their wallet.
// Ocean Protocol contracts are deployed for: 'mainnet', 'rinkeby', 'development'
network: process.env.GATSBY_NETWORK || 'rinkeby',
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:

20
package-lock.json generated
View File

@ -3572,11 +3572,11 @@
"integrity": "sha512-MMyy81FvnRGwl2cQ4+cucq/YWjUTGzStHyAUVM6P2pFA8zMc3jouuWN2WSAjmvhxeKZU7jvJRwZCoi+miEYKjw=="
},
"@oceanprotocol/react": {
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/@oceanprotocol/react/-/react-0.3.19.tgz",
"integrity": "sha512-9esHRLJlfCtGRA8PRuohiYoCitos2DgZjGxY+og5k4udwiqSHKzHV1fJexu9rGknKQls7o+QIt4I/79jpnqETw==",
"version": "0.3.20",
"resolved": "https://registry.npmjs.org/@oceanprotocol/react/-/react-0.3.20.tgz",
"integrity": "sha512-98mRBP0Ij20v6wYsbb/B7GM1H3HclUyrlWJKybRB/K+Hbk3AJW0+RQxJ+C4WkrkR4C5KquNjGccvgr5sZyQFoQ==",
"requires": {
"@oceanprotocol/lib": "^0.9.12",
"@oceanprotocol/lib": "^0.9.14",
"axios": "^0.21.0",
"decimal.js": "^10.2.1",
"web3": "^1.3.0",
@ -33970,9 +33970,9 @@
},
"dependencies": {
"@types/node": {
"version": "12.19.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.3.tgz",
"integrity": "sha512-8Jduo8wvvwDzEVJCOvS/G6sgilOLvvhn1eMmK3TW8/T217O7u1jdrK6ImKLv80tVryaPSVeKu6sjDEiFjd4/eg=="
"version": "12.19.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.5.tgz",
"integrity": "sha512-Wgdl27uw/jUYUFyajUGKSjDNGxmJrZi9sjeG6UJImgUtKbJoO9aldx+1XODN1EpNDX9DirvbvHHmTsNlb8GwMA=="
}
}
},
@ -34170,9 +34170,9 @@
},
"dependencies": {
"@types/node": {
"version": "12.19.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.3.tgz",
"integrity": "sha512-8Jduo8wvvwDzEVJCOvS/G6sgilOLvvhn1eMmK3TW8/T217O7u1jdrK6ImKLv80tVryaPSVeKu6sjDEiFjd4/eg=="
"version": "12.19.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.5.tgz",
"integrity": "sha512-Wgdl27uw/jUYUFyajUGKSjDNGxmJrZi9sjeG6UJImgUtKbJoO9aldx+1XODN1EpNDX9DirvbvHHmTsNlb8GwMA=="
}
}
},

View File

@ -26,7 +26,7 @@
"@oceanprotocol/art": "^3.0.0",
"@oceanprotocol/lib": "^0.9.17",
"@oceanprotocol/list-datapartners": "^1.0.3",
"@oceanprotocol/react": "^0.3.19",
"@oceanprotocol/react": "^0.3.20",
"@oceanprotocol/typographies": "^0.1.0",
"@sindresorhus/slugify": "^1.0.0",
"@tippyjs/react": "^4.2.0",

View File

@ -17,6 +17,7 @@ export default function Price({
small?: boolean
conversion?: boolean
}): ReactElement {
// price is not fetched from the chain anymore , will update one AssetProvider is implemented
const { price } = useMetadata(ddo)
return price?.value ? (

View File

@ -7,14 +7,10 @@ import Price from '../../atoms/Price'
import Web3Feedback from '../../molecules/Wallet/Feedback'
import styles from './Consume.module.css'
import Loader from '../../atoms/Loader'
import {
useOcean,
useConsume,
usePricing,
useAsset
} from '@oceanprotocol/react'
import { useOcean, useConsume, usePricing } from '@oceanprotocol/react'
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
import checkPreviousOrder from '../../../utils/checkPreviousOrder'
import { useAsset } from '../../../providers/Asset'
export default function Consume({
ddo,

View File

@ -1,11 +1,6 @@
import React, { ReactElement, useEffect, useState } from 'react'
import {
useOcean,
useMetadata,
usePricing,
useAsset
} from '@oceanprotocol/react'
import { DDO, Logger } from '@oceanprotocol/lib'
import { useOcean, useMetadata, usePricing } from '@oceanprotocol/react'
import { BestPrice, DDO, Logger } from '@oceanprotocol/lib'
import styles from './index.module.css'
import stylesActions from './Actions.module.css'
import PriceUnit from '../../../atoms/Price/PriceUnit'
@ -21,6 +16,7 @@ import TokenBalance from '../../../../@types/TokenBalance'
import Transactions from './Transactions'
import Graph, { ChartDataLiqudity } from './Graph'
import axios from 'axios'
import { useAsset } from '../../../../providers/Asset'
const contentQuery = graphql`
query PoolQuery {
@ -41,16 +37,15 @@ const contentQuery = graphql`
}
`
const refreshInterval = 15000 // 15 sec.
export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
const data = useStaticQuery(contentQuery)
const content = data.content.edges[0].node.childContentJson.pool
const { ocean, accountId, networkId, config } = useOcean()
const { price, refreshPrice, owner } = useMetadata(ddo)
const { owner } = useMetadata(ddo)
const { dtSymbol } = usePricing(ddo)
const { isInPurgatory } = useAsset()
const { isInPurgatory, price, refreshInterval, refreshPrice } = useAsset()
const [poolTokens, setPoolTokens] = useState<string>()
const [totalPoolTokens, setTotalPoolTokens] = useState<string>()
@ -80,12 +75,6 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
// the purpose of the value is just to trigger the effect
const [refreshPool, setRefreshPool] = useState(false)
useEffect(() => {
// Re-fetch price periodically, triggering re-calculation of everything
const interval = setInterval(() => refreshPrice(), refreshInterval)
return () => clearInterval(interval)
}, [ddo, refreshPrice])
useEffect(() => {
setIsRemoveDisabled(isInPurgatory && owner === accountId)
}, [isInPurgatory, owner, accountId])
@ -108,7 +97,6 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
useEffect(() => {
if (!ocean || !accountId || !price) return
async function init() {
try {
//
@ -195,10 +183,6 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
}
}
init()
// Re-fetch price periodically, triggering re-calculation of everything
const interval = setInterval(() => refreshPrice(), refreshInterval)
return () => clearInterval(interval)
}, [ocean, accountId, price, ddo, refreshPool, owner])
// Get graph history data
@ -248,7 +232,10 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
refreshInfo={refreshInfo}
poolAddress={price.address}
totalPoolTokens={totalPoolTokens}
totalBalance={{ ocean: price.ocean, datatoken: price.datatoken }}
totalBalance={{
ocean: price.ocean,
datatoken: price.datatoken
}}
swapFee={swapFee}
dtSymbol={dtSymbol}
dtAddress={ddo.dataToken}

View File

@ -1,15 +1,14 @@
import React, { ReactElement, useEffect, useState } from 'react'
import { useOcean, useMetadata } from '@oceanprotocol/react'
import { useOcean } from '@oceanprotocol/react'
import { DDO } from '@oceanprotocol/lib'
import FormTrade from './FormTrade'
import TokenBalance from '../../../../@types/TokenBalance'
const refreshInterval = 10000 // 10 sec, if the interval is bellow 3-5 seconds the price will be 0 all the time
import { useAsset } from '../../../../providers/Asset'
export default function Trade({ ddo }: { ddo: DDO }): ReactElement {
const { ocean, balance, accountId, networkId, refreshBalance } = useOcean()
const { ocean, balance, accountId } = useOcean()
const [tokenBalance, setTokenBalance] = useState<TokenBalance>()
const { price, refreshPrice } = useMetadata(ddo)
const { price } = useAsset()
const [maxDt, setMaxDt] = useState(0)
const [maxOcean, setMaxOcean] = useState(0)
@ -27,18 +26,6 @@ export default function Trade({ ddo }: { ddo: DDO }): ReactElement {
getTokenBalance()
}, [balance.ocean, ocean, accountId, ddo.dataToken])
// Re-fetch price & balance periodically, triggering re-calculation of everything
useEffect(() => {
if (!ocean || !networkId || !accountId) return
const interval = setInterval(async () => {
refreshPrice()
refreshBalance()
}, refreshInterval)
return () => clearInterval(interval)
}, [ocean, ddo, networkId, accountId, refreshPrice, refreshBalance])
// Get maximum amount for either OCEAN or datatoken
useEffect(() => {
if (!ocean || !price || price.value === 0) return

View File

@ -4,14 +4,15 @@ import Compute from './Compute'
import Consume from './Consume'
import { DDO, Logger } from '@oceanprotocol/lib'
import Tabs from '../../atoms/Tabs'
import { useOcean, useMetadata } from '@oceanprotocol/react'
import { useOcean } from '@oceanprotocol/react'
import compareAsBN from '../../../utils/compareAsBN'
import Pool from './Pool'
import Trade from './Trade'
import { useAsset } from '../../../providers/Asset'
export default function AssetActions({ ddo }: { ddo: DDO }): ReactElement {
const { ocean, balance, accountId } = useOcean()
const { price } = useMetadata(ddo)
const { price } = useAsset()
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>()
const [dtBalance, setDtBalance] = useState<string>()
@ -21,7 +22,6 @@ export default function AssetActions({ ddo }: { ddo: DDO }): ReactElement {
// Get and set user DT balance
useEffect(() => {
if (!ocean || !accountId) return
async function init() {
try {
const dtBalance = await ocean.datatokens.balance(

View File

@ -3,61 +3,32 @@ import { Router } from '@reach/router'
import AssetContent from '../organisms/AssetContent'
import Page from './Page'
import { MetadataMarket } from '../../@types/MetaData'
import { MetadataCache, Logger, DDO } from '@oceanprotocol/lib'
import Alert from '../atoms/Alert'
import Loader from '../atoms/Loader'
import { useAsset, useOcean } from '@oceanprotocol/react'
import { useAsset } from '../../providers/Asset'
export default function PageTemplateAssetDetails({
did,
uri
}: {
did: string
uri: string
}): ReactElement {
const { config } = useOcean()
const { isInPurgatory, purgatoryData } = useAsset()
const [metadata, setMetadata] = useState<MetadataMarket>()
const [title, setTitle] = useState<string>()
const [error, setError] = useState<string>()
const [ddo, setDdo] = useState<DDO>()
const { ddo, error } = useAsset()
useEffect(() => {
if (!config?.metadataCacheUri) return
async function init() {
if (ddo) return
try {
const metadataCache = new MetadataCache(config.metadataCacheUri, Logger)
const ddo = await metadataCache.retrieveDDO(did)
if (!ddo) {
setTitle('Could not retrieve asset')
setError(
`The DDO for ${did} was not found in MetadataCache. If you just published a new data set, wait some seconds and refresh this page.`
)
return
}
setDdo(ddo)
const { attributes } = ddo.findServiceByType('metadata')
setTitle(attributes.main.name)
setMetadata((attributes as unknown) as MetadataMarket)
} catch (error) {
setTitle('Error retrieving asset')
setError(error.message)
}
if (!ddo || error) {
setTitle('Could not retrieve asset')
return
}
init()
// Periodically try to get DDO when not present yet
const timer = !ddo && setInterval(() => init(), 5000)
return () => clearInterval(timer)
}, [ddo, did, config.metadataCacheUri])
const { attributes } = ddo.findServiceByType('metadata')
setTitle(attributes.main.name)
setMetadata((attributes as unknown) as MetadataMarket)
}, [ddo, error])
return did && metadata ? (
return ddo && metadata ? (
<>
{isInPurgatory && purgatoryData && (
<Alert

View File

@ -27,7 +27,6 @@ export default function wrapRootElement({
}): ReactElement {
const { network } = appConfig
const oceanInitialConfig = getOceanConfig(network)
return (
<OceanProvider
initialConfig={oceanInitialConfig}

View File

@ -1,14 +1,18 @@
import React, { ReactElement } from 'react'
import React, { ReactElement, useEffect, useState } from 'react'
import { PageProps } from 'gatsby'
import PageTemplateAssetDetails from '../../components/templates/PageAssetDetails'
import { AssetProvider } from '@oceanprotocol/react'
import AssetProvider from '../../providers/Asset'
export default function PageGatsbyAssetDetails(props: PageProps): ReactElement {
const did = props.location.pathname.split('/')[2]
const [did, setDid] = useState<string>()
useEffect(() => {
setDid(props.location.pathname.split('/')[2])
}, [props.location.pathname])
return (
<AssetProvider asset={did}>
<PageTemplateAssetDetails did={did} uri={props.location.pathname} />
<PageTemplateAssetDetails uri={props.location.pathname} />
</AssetProvider>
)
}

206
src/providers/Asset.tsx Normal file
View File

@ -0,0 +1,206 @@
import React, {
useContext,
useState,
useEffect,
createContext,
ReactElement,
useCallback,
ReactNode
} from 'react'
import { Logger, DDO, Metadata, BestPrice } from '@oceanprotocol/lib'
import { PurgatoryData } from '@oceanprotocol/lib/dist/node/ddo/interfaces/PurgatoryData'
import { getDataTokenPrice, useOcean } from '@oceanprotocol/react'
import getAssetPurgatoryData from '../utils/purgatory'
import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
import axios, { CancelToken } from 'axios'
interface AssetProviderValue {
isInPurgatory: boolean
purgatoryData: PurgatoryData
ddo: DDO | undefined
did: string | undefined
metadata: Metadata | undefined
title: string | undefined
owner: string | undefined
price: BestPrice | undefined
error?: string
refreshInterval: number
refreshPrice: () => Promise<void>
}
const AssetContext = createContext({} as AssetProviderValue)
const refreshInterval = 10000 // 10 sec.
function AssetProvider({
asset,
children
}: {
asset: string | DDO
children: ReactNode
}): ReactElement {
const { ocean, status, config, networkId } = useOcean()
const [isInPurgatory, setIsInPurgatory] = useState(false)
const [purgatoryData, setPurgatoryData] = useState<PurgatoryData>()
const [ddo, setDDO] = useState<DDO>()
const [did, setDID] = useState<string>()
const [metadata, setMetadata] = useState<Metadata>()
const [title, setTitle] = useState<string>()
const [price, setPrice] = useState<BestPrice>()
const [owner, setOwner] = useState<string>()
const [error, setError] = useState<string>()
const refreshPrice = useCallback(async () => {
if (
!ddo ||
status !== 1 ||
networkId !== (config as ConfigHelperConfig).networkId
)
return
const newPrice = await getDataTokenPrice(
ocean,
ddo.dataToken,
ddo?.price?.type,
ddo.price.pools[0]
)
setPrice(newPrice)
Logger.log(`Refreshed asset price: ${newPrice?.value}`)
}, [ocean, config, ddo, networkId, status])
const getDDO = useCallback(
async (did: string, cancelToken: CancelToken): Promise<DDO | undefined> => {
if (!config.metadataCacheUri) return
try {
const request = await axios.get(
`${config.metadataCacheUri}/api/v1/aquarius/assets/ddo/${did}`,
{ cancelToken }
)
const ddo = request.data as DDO
return new DDO(ddo)
} catch (error) {
Logger.error(error.message)
return undefined
}
},
[config.metadataCacheUri]
)
//
// Get and set DDO based on passed DDO or DID
//
useEffect(() => {
if (!asset) return
const source = axios.CancelToken.source()
let isMounted = true
Logger.log('Init asset, get ddo')
async function init(): Promise<void> {
const ddo = await getDDO(asset as string, source.token)
if (!ddo) {
setError(
`The DDO for ${asset} was not found in MetadataCache. If you just published a new data set, wait some seconds and refresh this page.`
)
} else {
setError(undefined)
}
if (!isMounted) return
Logger.debug('DDO', ddo)
setDDO(ddo)
setDID(asset as string)
}
init()
return () => {
isMounted = false
source.cancel()
}
}, [asset, getDDO])
useEffect(() => {
// Re-fetch price periodically, triggering re-calculation of everything
let isMounted = true
const interval = setInterval(() => {
if (!isMounted) return
refreshPrice()
}, refreshInterval)
return () => {
clearInterval(interval)
isMounted = false
}
}, [ddo, networkId, refreshPrice])
const setPurgatory = useCallback(async (did: string): Promise<void> => {
if (!did) return
try {
const result = await getAssetPurgatoryData(did)
if (result?.did !== undefined) {
setIsInPurgatory(true)
setPurgatoryData(result)
} else {
setIsInPurgatory(false)
}
setPurgatoryData(result)
} catch (error) {
Logger.error(error)
}
}, [])
const initMetadata = useCallback(
async (ddo: DDO): Promise<void> => {
if (!ddo) return
Logger.log('Init metadata')
// Set price & metadata from DDO first
setPrice(ddo.price)
const { attributes } = ddo.findServiceByType('metadata')
setMetadata(attributes)
setTitle(attributes?.main.name)
setOwner(ddo.publicKey[0].owner)
await setPurgatory(ddo.id)
refreshPrice()
},
[refreshPrice, setPurgatory]
)
useEffect(() => {
if (!ddo) return
initMetadata(ddo)
}, [ddo, initMetadata])
return (
<AssetContext.Provider
value={
{
ddo,
did,
metadata,
title,
owner,
price,
error,
isInPurgatory,
purgatoryData,
refreshInterval,
refreshPrice
} as AssetProviderValue
}
>
{children}
</AssetContext.Provider>
)
}
// Helper hook to access the provider values
const useAsset = (): AssetProviderValue => useContext(AssetContext)
export { AssetProvider, useAsset, AssetProviderValue, AssetContext }
export default AssetProvider

11
src/utils/purgatory.ts Normal file
View File

@ -0,0 +1,11 @@
import { PurgatoryData } from '@oceanprotocol/lib'
import axios from 'axios'
const purgatoryUrl = 'https://market-purgatory.oceanprotocol.com/api/'
export default async function getAssetPurgatoryData(
did: string
): Promise<PurgatoryData> {
const response = await axios(`${purgatoryUrl}asset?did=${did}`)
const responseJson = await response.data[0]
return { did: responseJson?.did, reason: responseJson?.reason }
}