1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-11-14 17:24:51 +01:00

Pool statistics from the graph (#288)

* graphql

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

* ignore generated

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

* delete generated

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

* fix travis

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

* fix travis

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

* fix fetch

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

* fix travis

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

* fix fetch

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

* update readme

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

* pool creator liquidit& statistics

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

* graph with the graph

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

* cleanup

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

* fix query

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

* update poll interval

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

* update graph url

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

* ocean bump

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

* run apollo codegen before starting gatsby

* put back graph loading state

* typing fix

* graph tweak, add error state

* readme update

* remove unused functions, move graph provider

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

* fix package-lock

* fix graph when switching tabs

* generate apollo files into one folder

* fix loading

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

* fix codegen camelcase

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

* bump apollo packages

* document subgraph usage, add example

* rewrite into Data Sources, add quick examples

* more data sources docs

* docs updates, typos

Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
mihaisc 2021-01-21 17:02:48 +02:00 committed by GitHub
parent 83dca873f8
commit 273769388c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 3169 additions and 179 deletions

1
.gitignore vendored
View File

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

View File

@ -17,6 +17,7 @@ before_script:
# - './cc-test-reporter before-build' # - './cc-test-reporter before-build'
script: script:
# will run `npm ci` automatically here # will run `npm ci` automatically here
- npm run apollo:codegen
- npm run lint - npm run lint
# - './cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT' # - './cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT'
- npm run build - npm run build

167
README.md
View File

@ -13,6 +13,11 @@
- [🏄 Get Started](#-get-started) - [🏄 Get Started](#-get-started)
- [Local components with Barge](#local-components-with-barge) - [Local components with Barge](#local-components-with-barge)
- [🦑 Environment variables](#-environment-variables) - [🦑 Environment variables](#-environment-variables)
- [🦀 Data Sources](#-data-sources)
- [Aquarius](#aquarius)
- [Ocean Protocol Subgraph](#ocean-protocol-subgraph)
- [3Box](#3box)
- [Purgatory](#purgatory)
- [🎨 Storybook](#-storybook) - [🎨 Storybook](#-storybook)
- [✨ Code Style](#-code-style) - [✨ Code Style](#-code-style)
- [👩‍🔬 Testing](#-testing) - [👩‍🔬 Testing](#-testing)
@ -71,8 +76,158 @@ For local development, you can use a `.env` file:
cp .env.example .env cp .env.example .env
``` ```
## 🦀 Data Sources
All displayed data in the app is presented around the concept of one data set, which is a combination of:
- metadata about a data set
- the actual data set files
- the datatoken which represents the data set
- financial data connected to this datatoken, either a pool or a fixed rate exchange contract
- calculations and conversions based on financial data
- metadata about publishers
All this data then comes from multiple sources:
### Aquarius
All initial data sets and their metadata (DDO) is retrieved client-side on run-time from the [Aquarius](https://github.com/oceanprotocol/aquarius) instance for each network. All app calls to Aquarius are done with 2 internal methods which mimic the same methods in ocean.js, but allow us:
- to cancel requests when components get unmounted in combination with [axios](https://github.com/axios/axios)
- hit Aquarius as early as possible without relying on any ocean.js initialization
Aquarius runs Elasticsearch under the hood so its stored metadata can be queried with [Elasticsearch queries](https://www.elastic.co/guide/en/elasticsearch/reference/current/full-text-queries.html) like so:
```tsx
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import { queryMetadata } from '../../utils/aquarius'
const queryLatest = {
page: 1,
offset: 9,
query: {
nativeSearch: 1,
// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html
query_string: { query: `-isInPurgatory:true` }
},
sort: { created: -1 }
}
function Component() {
const { config } = useOcean()
const [result, setResult] = useState<QueryResult>()
useEffect(() => {
if (!config?.metadataCacheUri) return
const source = axios.CancelToken.source()
async function init() {
const result = await queryMetadata(
query,
config.metadataCacheUri,
source.token
)
setResult(result)
}
init()
return () => {
source.cancel()
}
}, [config?.metadataCacheUri, query])
return <div>{result}</div>
}
```
For components within a single data set view the `useAsset()` hook can be used, which in the background gets the respective metadata from Aquarius.
```tsx
import { useAsset } from '../../../providers/Asset'
function Component() {
const { ddo } = useAsset()
return <div>{ddo}</div>
}
```
### Ocean Protocol Subgraph
Most financial data in the market is retrieved with GraphQL from [our own subgraph](https://github.com/oceanprotocol/ocean-subgraph), rendered on top of the initial data coming from Aquarius.
The app has [Apollo Client](https://www.apollographql.com/docs/react/) setup to query the respective subgraph based on network. In any component this client can be used like so:
```tsx
import { gql, useQuery } from '@apollo/client'
const query = gql`
query PoolLiquidity($id: ID!, $shareId: ID) {
pool(id: $id) {
id
totalShares
}
}
`
function Component() {
const { data } = useQuery(query, {}, pollInterval: 5000 })
return <div>{data}</div>
}
```
### 3Box
Publishers can create a profile on [3Box Hub](https://www.3box.io/hub) and when found, it will be displayed in the app.
For this our own [3box-proxy](https://github.com/oceanprotocol/3box-proxy) is used, within the app the utility method `get3BoxProfile()` can be used to get all info:
```tsx
import get3BoxProfile from '../../../utils/profile'
function Component() {
const [profile, setProfile] = useState<Profile>()
useEffect(() => {
if (!account) return
const source = axios.CancelToken.source()
async function get3Box() {
const profile = await get3BoxProfile(account, source.token)
if (!profile) return
setProfile(profile)
}
get3Box()
return () => {
source.cancel()
}
}, [account])
return (
<div>
{profile.emoji} {profile.name}
</div>
)
}
```
### Purgatory
Based on [list-purgatory](https://github.com/oceanprotocol/list-purgatory) some data sets get additional data. Within most components this can be done with the internal `useAsset()` hook which fetches data from the [market-purgatory](https://github.com/oceanprotocol/market-purgatory) endpoint in the background.
```tsx
import { useAsset } from '../../../providers/Asset'
function Component() {
const { isInPurgatory, purgatoryData } = useAsset()
return isInPurgatory ? <div>{purgatoryData.reason}</div> : null
}
```
## 🎨 Storybook ## 🎨 Storybook
> TODO: this is broken for most components. See https://github.com/oceanprotocol/market/issues/128
[Storybook](https://storybook.js.org) is set up for this project and is used for UI development of components. Stories are created inside `src/components/` alongside each component in the form of `ComponentName.stories.tsx`. [Storybook](https://storybook.js.org) is set up for this project and is used for UI development of components. Stories are created inside `src/components/` alongside each component in the form of `ComponentName.stories.tsx`.
To run the Storybook server, execute in your Terminal: To run the Storybook server, execute in your Terminal:
@ -97,6 +252,8 @@ npm run format
## 👩‍🔬 Testing ## 👩‍🔬 Testing
> TODO: this is broken and never runs in CI. See https://github.com/oceanprotocol/market/issues/128
Test suite for unit tests is setup with [Jest](https://jestjs.io) as a test runner and: Test suite for unit tests is setup with [Jest](https://jestjs.io) as a test runner and:
- [react-testing-library](https://github.com/kentcdodds/react-testing-library) for all React components - [react-testing-library](https://github.com/kentcdodds/react-testing-library) for all React components
@ -131,9 +288,15 @@ npm run serve
## ⬆️ Deployment ## ⬆️ Deployment
Every branch or Pull Request is automatically deployed by [Netlify](https://netlify.com) with their GitHub integration. A link to a deployment will appear under each Pull Request. Every branch or Pull Request is automatically deployed to multiple hosts for redundancy and emergency reasons:
The latest deployment of the `main` branch is automatically aliased to `market.oceanprotocol.com`. - [Netlify](https://netlify.com)
- [Vercel](https://vercel.com)
- [S3](https://aws.amazon.com/s3/)
A link to a deployment will appear under each Pull Request.
The latest deployment of the `main` branch is automatically aliased to `market.oceanprotocol.com`, where the deployment on Netlify is the current live deployment.
## 💖 Contributing ## 💖 Contributing

11
apollo.config.js Normal file
View File

@ -0,0 +1,11 @@
module.exports = {
client: {
service: {
name: 'ocean',
url:
'https://subgraph.mainnet.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph',
// optional disable SSL validation check
skipSSLValidation: true
}
}
}

View File

@ -4,7 +4,12 @@ const createMarkdownPages = require('./gatsby/createMarkdownPages')
const execSync = require('child_process').execSync const execSync = require('child_process').execSync
// Write out repo metadata // Write out repo metadata
execSync(`node ./scripts/write-repo-metadata > repo-metadata.json`) execSync(`node ./scripts/write-repo-metadata > repo-metadata.json`, {
stdio: 'inherit'
})
// Generate Apollo typings
execSync(`npm run apollo:codegen`, { stdio: 'inherit' })
exports.onCreateNode = ({ node, actions, getNode }) => { exports.onCreateNode = ({ node, actions, getNode }) => {
createFields(node, actions, getNode) createFields(node, actions, getNode)

2763
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
"build": "gatsby build && cp _redirects public/_redirects", "build": "gatsby build && cp _redirects public/_redirects",
"serve": "serve -s public/", "serve": "serve -s public/",
"jest": "NODE_ENV=test jest -c tests/unit/jest.config.js", "jest": "NODE_ENV=test jest -c tests/unit/jest.config.js",
"test": "npm run lint && npm run jest", "test": "npm run apollo:codegen && npm run lint && npm run jest",
"test:watch": "npm run lint && npm run jest -- --watch", "test:watch": "npm run lint && npm run jest -- --watch",
"lint": "npm run write:repoMetadata && eslint --ignore-path .gitignore --ext .js --ext .ts --ext .tsx . && npm run type-check", "lint": "npm run write:repoMetadata && eslint --ignore-path .gitignore --ext .js --ext .ts --ext .tsx . && npm run type-check",
"format": "prettier --ignore-path .gitignore './**/*.{css,yml,js,ts,tsx,json}' --write", "format": "prettier --ignore-path .gitignore './**/*.{css,yml,js,ts,tsx,json}' --write",
@ -18,9 +18,11 @@
"storybook": "start-storybook -p 4000 -c .storybook", "storybook": "start-storybook -p 4000 -c .storybook",
"storybook:build": "build-storybook -c .storybook -o public/storybook", "storybook:build": "build-storybook -c .storybook -o public/storybook",
"write:repoMetadata": "node ./scripts/write-repo-metadata > repo-metadata.json", "write:repoMetadata": "node ./scripts/write-repo-metadata > repo-metadata.json",
"deploy": "./scripts/deploy.sh" "deploy": "./scripts/deploy.sh",
"apollo:codegen": "mkdir -p src/@types/apollo/ && apollo client:codegen --target typescript --tsFileExtension='d.ts' --outputFlat './src/@types/apollo/' && find ./src/@types/apollo -type f -exec sed -i -r 's/_([a-z])/\\U\\1/gi' {} \\;"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.3.6",
"@coingecko/cryptoformat": "^0.4.2", "@coingecko/cryptoformat": "^0.4.2",
"@loadable/component": "^5.14.1", "@loadable/component": "^5.14.1",
"@oceanprotocol/art": "^3.0.0", "@oceanprotocol/art": "^3.0.0",
@ -36,6 +38,7 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"chart.js": "^2.9.4", "chart.js": "^2.9.4",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"cross-fetch": "^3.0.6",
"date-fns": "^2.16.1", "date-fns": "^2.16.1",
"decimal.js": "^10.2.1", "decimal.js": "^10.2.1",
"dom-confetti": "^0.2.2", "dom-confetti": "^0.2.2",
@ -109,6 +112,7 @@
"@types/remove-markdown": "^0.1.1", "@types/remove-markdown": "^0.1.1",
"@types/shortid": "0.0.29", "@types/shortid": "0.0.29",
"@types/yup": "^0.29.11", "@types/yup": "^0.29.11",
"apollo": "^2.32.1",
"@typescript-eslint/eslint-plugin": "^4.13.0", "@typescript-eslint/eslint-plugin": "^4.13.0",
"@typescript-eslint/parser": "^4.13.0", "@typescript-eslint/parser": "^4.13.0",
"babel-loader": "^8.2.2", "babel-loader": "^8.2.2",

View File

@ -1,4 +1,4 @@
import React, { ReactElement } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import Footer from './organisms/Footer' import Footer from './organisms/Footer'
import Header from './organisms/Header' import Header from './organisms/Header'
import Styles from '../global/Styles' import Styles from '../global/Styles'
@ -7,7 +7,14 @@ import { useSiteMetadata } from '../hooks/useSiteMetadata'
import { useOcean } from '@oceanprotocol/react' import { useOcean } from '@oceanprotocol/react'
import Alert from './atoms/Alert' import Alert from './atoms/Alert'
import { graphql, PageProps, useStaticQuery } from 'gatsby' import { graphql, PageProps, useStaticQuery } from 'gatsby'
import {
ApolloClient,
ApolloProvider,
HttpLink,
InMemoryCache,
NormalizedCacheObject
} from '@apollo/client'
import fetch from 'cross-fetch'
const contentQuery = graphql` const contentQuery = graphql`
query AppQuery { query AppQuery {
purgatory: allFile(filter: { relativePath: { eq: "purgatory.json" } }) { purgatory: allFile(filter: { relativePath: { eq: "purgatory.json" } }) {
@ -37,18 +44,31 @@ export default function App({
const { warning } = useSiteMetadata() const { warning } = useSiteMetadata()
const { const {
isInPurgatory: isAccountInPurgatory, isInPurgatory: isAccountInPurgatory,
purgatoryData: accountPurgatory purgatoryData: accountPurgatory,
config
} = useOcean() } = useOcean()
const [client, setClient] = useState<ApolloClient<NormalizedCacheObject>>()
useEffect(() => {
const newClient = new ApolloClient({
link: new HttpLink({
uri: `${
(config as any).subgraphUri
}/subgraphs/name/oceanprotocol/ocean-subgraph`,
fetch
}),
cache: new InMemoryCache()
})
setClient(newClient)
}, [config])
return ( return (
<Styles> <Styles>
<div className={styles.app}> <div className={styles.app}>
<Header /> <Header />
{(props as PageProps).uri === '/' && ( {(props as PageProps).uri === '/' && (
<Alert text={warning} state="info" /> <Alert text={warning} state="info" />
)} )}
{isAccountInPurgatory && ( {isAccountInPurgatory && (
<Alert <Alert
title={purgatory.title} title={purgatory.title}
@ -57,8 +77,13 @@ export default function App({
state="error" state="error"
/> />
)} )}
{client ? (
<main className={styles.main}>{children}</main> <ApolloProvider client={client}>
<main className={styles.main}>{children}</main>
</ApolloProvider>
) : (
<></>
)}
<Footer /> <Footer />
</div> </div>
</Styles> </Styles>

View File

@ -9,6 +9,33 @@ import styles from './PoolTransactions.module.css'
import { useUserPreferences } from '../../providers/UserPreferences' import { useUserPreferences } from '../../providers/UserPreferences'
import { Ocean } from '@oceanprotocol/lib' import { Ocean } from '@oceanprotocol/lib'
import { formatPrice } from '../atoms/Price/PriceUnit' import { formatPrice } from '../atoms/Price/PriceUnit'
import { gql } from '@apollo/client'
const txHistoryQuery = gql`
query Pool($id: ID!, $user: String!) {
pool(id: $id) {
transactions(orderBy: timestamp, where: { userAddressStr: $user }) {
tx
timestamp
spotPrice
event
sharesTransferAmount
tokens {
type
value
tokenAddress
poolToken {
tokenId {
symbol
name
}
tokenAddress
}
}
}
}
}
`
async function getSymbol( async function getSymbol(
ocean: Ocean, ocean: Ocean,

View File

@ -1,3 +1,4 @@
/* eslint-disable camelcase */
import React, { ChangeEvent, ReactElement, useEffect, useState } from 'react' import React, { ChangeEvent, ReactElement, useEffect, useState } from 'react'
import { Line, defaults } from 'react-chartjs-2' import { Line, defaults } from 'react-chartjs-2'
import { import {
@ -15,21 +16,16 @@ import useDarkMode from 'use-dark-mode'
import { darkModeConfig } from '../../../../../app.config' import { darkModeConfig } from '../../../../../app.config'
import Button from '../../../atoms/Button' import Button from '../../../atoms/Button'
import { Logger } from '@oceanprotocol/lib' import { Logger } from '@oceanprotocol/lib'
import { useAsset } from '../../../../providers/Asset'
export interface ChartDataLiqudity { import { gql, useQuery } from '@apollo/client'
oceanAddRemove: ChartData[] import { PoolHistory } from '../../../../@types/apollo/PoolHistory'
datatokenAddRemove: ChartData[]
oceanReserveHistory: ChartData[]
datatokenReserveHistory: ChartData[]
datatokenPriceHistory: ChartData[]
}
declare type GraphType = 'liquidity' | 'price' declare type GraphType = 'liquidity' | 'price'
// Chart.js global defaults // Chart.js global defaults
defaults.global.defaultFontFamily = `'Sharp Sans', -apple-system, BlinkMacSystemFont, defaults.global.defaultFontFamily = `'Sharp Sans', -apple-system, BlinkMacSystemFont,
'Segoe UI', Helvetica, Arial, sans-serif` 'Segoe UI', Helvetica, Arial, sans-serif`
defaults.global.animation = { easing: 'easeInOutQuart', duration: 800 } defaults.global.animation = { easing: 'easeInOutQuart', duration: 1000 }
const lineStyle: Partial<ChartDataSets> = { const lineStyle: Partial<ChartDataSets> = {
fill: false, fill: false,
@ -57,28 +53,6 @@ const tooltipOptions: Partial<ChartTooltipOptions> = {
caretSize: 7 caretSize: 7
} }
function constructGraphData(data: ChartData[]): ChartData {
const timestamps = data.map((item: any) => {
// convert timestamps from epoch to locale date & time string
const date = new Date(item[1] * 1000)
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
})
const values = data.map((item: any) => item[0])
return {
labels: timestamps,
datasets: [
{
...lineStyle,
label: 'Liquidity (OCEAN)',
data: values,
borderColor: `#8b98a9`,
pointBackgroundColor: `#8b98a9`
}
]
}
}
function getOptions(locale: string, isDarkMode: boolean): ChartOptions { function getOptions(locale: string, isDarkMode: boolean): ChartOptions {
return { return {
layout: { layout: {
@ -122,18 +96,45 @@ function getOptions(locale: string, isDarkMode: boolean): ChartOptions {
const graphTypes = ['Liquidity', 'Price'] const graphTypes = ['Liquidity', 'Price']
export default function Graph({ const poolHistory = gql`
data query PoolHistory($id: String!, $block: Int) {
}: { poolTransactions(
data: ChartDataLiqudity first: 1000
}): ReactElement { where: { poolAddress: $id, block_gt: $block }
orderBy: block
) {
block
spotPrice
timestamp
oceanReserve
}
}
`
export default function Graph(): ReactElement {
const { locale } = useUserPreferences() const { locale } = useUserPreferences()
const darkMode = useDarkMode(false, darkModeConfig) const darkMode = useDarkMode(false, darkModeConfig)
const [graphData, setGraphData] = useState<ChartData>()
const [options, setOptions] = useState<ChartOptions>() const [options, setOptions] = useState<ChartOptions>()
const [graphType, setGraphType] = useState<GraphType>('liquidity') const [graphType, setGraphType] = useState<GraphType>('liquidity')
const { price } = useAsset()
const [lastBlock, setLastBlock] = useState(0)
const [priceHistory, setPriceHistory] = useState([])
const [liquidityHistory, setLiquidityHistory] = useState([])
const [timestamps, setTimestamps] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [graphData, setGraphData] = useState<ChartData>()
const { data, refetch, error } = useQuery<PoolHistory>(poolHistory, {
variables: {
id: price.address.toLowerCase(),
block: lastBlock
},
pollInterval: 20000
})
useEffect(() => { useEffect(() => {
Logger.log('Fired GraphOptions!') Logger.log('Fired GraphOptions!')
const options = getOptions(locale, darkMode.value) const options = getOptions(locale, darkMode.value)
@ -143,11 +144,53 @@ export default function Graph({
useEffect(() => { useEffect(() => {
if (!data) return if (!data) return
Logger.log('Fired GraphData!') Logger.log('Fired GraphData!')
const graphData =
graphType === 'liquidity' const latestTimestamps = [
? constructGraphData(data.oceanReserveHistory) ...timestamps,
: constructGraphData(data.datatokenPriceHistory) ...data.poolTransactions.map((item) => {
setGraphData(graphData) const date = new Date(item.timestamp * 1000)
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
})
]
setTimestamps(latestTimestamps)
const latestLiquidtyHistory = [
...liquidityHistory,
...data.poolTransactions.map((item) => item.oceanReserve)
]
setLiquidityHistory(latestLiquidtyHistory)
const latestPriceHistory = [
...priceHistory,
...data.poolTransactions.map((item) => item.spotPrice)
]
setPriceHistory(latestPriceHistory)
if (data.poolTransactions.length > 0) {
setLastBlock(
data.poolTransactions[data.poolTransactions.length - 1].block
)
refetch()
} else {
setIsLoading(false)
setGraphData({
labels: timestamps.slice(0),
datasets: [
{
...lineStyle,
label: 'Liquidity (OCEAN)',
data:
graphType === 'liquidity'
? latestLiquidtyHistory.slice(0)
: latestPriceHistory.slice(0),
borderColor: `#8b98a9`,
pointBackgroundColor: `#8b98a9`
}
]
})
}
}, [data, graphType]) }, [data, graphType])
function handleGraphTypeSwitch(e: ChangeEvent<HTMLButtonElement>) { function handleGraphTypeSwitch(e: ChangeEvent<HTMLButtonElement>) {
@ -157,7 +200,11 @@ export default function Graph({
return ( return (
<div className={styles.graphWrap}> <div className={styles.graphWrap}>
{graphData ? ( {isLoading ? (
<Loader />
) : error ? (
<small>{error.message}</small>
) : (
<> <>
<nav className={styles.type}> <nav className={styles.type}>
{graphTypes.map((type: GraphType) => ( {graphTypes.map((type: GraphType) => (
@ -176,8 +223,6 @@ export default function Graph({
</nav> </nav>
<Line height={70} data={graphData} options={options} /> <Line height={70} data={graphData} options={options} />
</> </>
) : (
<Loader />
)} )}
</div> </div>
) )

View File

@ -14,9 +14,10 @@ import TokenList from './TokenList'
import { graphql, useStaticQuery } from 'gatsby' import { graphql, useStaticQuery } from 'gatsby'
import TokenBalance from '../../../../@types/TokenBalance' import TokenBalance from '../../../../@types/TokenBalance'
import Transactions from './Transactions' import Transactions from './Transactions'
import Graph, { ChartDataLiqudity } from './Graph' import Graph from './Graph'
import axios from 'axios'
import { useAsset } from '../../../../providers/Asset' import { useAsset } from '../../../../providers/Asset'
import { gql, useQuery } from '@apollo/client'
import { PoolLiquidity } from '../../../../@types/apollo/PoolLiquidity'
const contentQuery = graphql` const contentQuery = graphql`
query PoolQuery { query PoolQuery {
@ -36,12 +37,30 @@ const contentQuery = graphql`
} }
} }
` `
const poolLiquidityQuery = gql`
query PoolLiquidity($id: ID!, $shareId: ID) {
pool(id: $id) {
id
totalShares
swapFee
tokens {
tokenAddress
balance
denormWeight
}
shares(where: { id: $shareId }) {
id
balance
}
}
}
`
export default function Pool(): ReactElement { export default function Pool(): ReactElement {
const data = useStaticQuery(contentQuery) const data = useStaticQuery(contentQuery)
const content = data.content.edges[0].node.childContentJson.pool const content = data.content.edges[0].node.childContentJson.pool
const { ocean, accountId, networkId, config } = useOcean() const { ocean, accountId, networkId } = useOcean()
const { const {
isInPurgatory, isInPurgatory,
ddo, ddo,
@ -75,10 +94,70 @@ export default function Pool(): ReactElement {
const [creatorLiquidity, setCreatorLiquidity] = useState<TokenBalance>() const [creatorLiquidity, setCreatorLiquidity] = useState<TokenBalance>()
const [creatorPoolTokens, setCreatorPoolTokens] = useState<string>() const [creatorPoolTokens, setCreatorPoolTokens] = useState<string>()
const [creatorPoolShare, setCreatorPoolShare] = useState<string>() const [creatorPoolShare, setCreatorPoolShare] = useState<string>()
const [graphData, setGraphData] = useState<ChartDataLiqudity>()
// the purpose of the value is just to trigger the effect // the purpose of the value is just to trigger the effect
const [refreshPool, setRefreshPool] = useState(false) const [refreshPool, setRefreshPool] = useState(false)
const { data: dataLiquidity } = useQuery<PoolLiquidity>(poolLiquidityQuery, {
variables: {
id: ddo.price.address.toLowerCase(),
shareId: `${ddo.price.address.toLowerCase()}-${ddo.publicKey[0].owner.toLowerCase()}`
},
pollInterval: 5000
})
useEffect(() => {
async function init() {
if (!dataLiquidity || !dataLiquidity.pool) return
// Total pool shares
const totalPoolTokens = dataLiquidity.pool.totalShares
setTotalPoolTokens(totalPoolTokens)
// Get swap fee
// swapFee is tricky: to get 0.1% you need to convert from 0.001
setSwapFee(`${Number(dataLiquidity.pool.swapFee) * 100}`)
// Get weights
const weightDt = dataLiquidity.pool.tokens.filter(
(token: any) => token.tokenAddress === ddo.dataToken.toLowerCase()
)[0].denormWeight
setWeightDt(`${Number(weightDt) * 10}`)
setWeightOcean(`${100 - Number(weightDt) * 10}`)
//
// Get everything the creator put into the pool
//
const creatorPoolTokens = dataLiquidity.pool.shares[0].balance
setCreatorPoolTokens(creatorPoolTokens)
// Calculate creator's provided liquidity based on pool tokens
const creatorOceanBalance =
(Number(creatorPoolTokens) / Number(totalPoolTokens)) * price.ocean
const creatorDtBalance =
(Number(creatorPoolTokens) / Number(totalPoolTokens)) * price.datatoken
const creatorLiquidity = {
ocean: creatorOceanBalance,
datatoken: creatorDtBalance
}
setCreatorLiquidity(creatorLiquidity)
const totalCreatorLiquidityInOcean =
creatorLiquidity?.ocean + creatorLiquidity?.datatoken * price?.value
setCreatorTotalLiquidityInOcean(totalCreatorLiquidityInOcean)
const creatorPoolShare =
price?.ocean &&
price?.datatoken &&
creatorLiquidity &&
((Number(creatorPoolTokens) / Number(totalPoolTokens)) * 100).toFixed(2)
setCreatorPoolShare(creatorPoolShare)
}
init()
}, [dataLiquidity, ddo.dataToken, price.datatoken, price.ocean, price?.value])
useEffect(() => { useEffect(() => {
setIsRemoveDisabled(isInPurgatory && owner === accountId) setIsRemoveDisabled(isInPurgatory && owner === accountId)
@ -104,14 +183,6 @@ export default function Pool(): ReactElement {
if (!ocean || !accountId || !price) return if (!ocean || !accountId || !price) return
async function init() { async function init() {
try { try {
//
// Get everything which is in the pool
//
const totalPoolTokens = await ocean.pool.getPoolSharesTotalSupply(
price.address
)
setTotalPoolTokens(totalPoolTokens)
// //
// Get everything the user has put into the pool // Get everything the user has put into the pool
// //
@ -131,95 +202,12 @@ export default function Pool(): ReactElement {
} }
setUserLiquidity(userLiquidity) setUserLiquidity(userLiquidity)
//
// Get everything the creator put into the pool
//
const creatorPoolTokens = await ocean.pool.sharesBalance(
owner,
price.address
)
setCreatorPoolTokens(creatorPoolTokens)
// Calculate creator's provided liquidity based on pool tokens
const creatorOceanBalance =
(Number(creatorPoolTokens) / Number(totalPoolTokens)) * price.ocean
const creatorDtBalance =
(Number(creatorPoolTokens) / Number(totalPoolTokens)) *
price.datatoken
const creatorLiquidity = {
ocean: creatorOceanBalance,
datatoken: creatorDtBalance
}
setCreatorLiquidity(creatorLiquidity)
const totalCreatorLiquidityInOcean =
creatorLiquidity?.ocean + creatorLiquidity?.datatoken * price?.value
setCreatorTotalLiquidityInOcean(totalCreatorLiquidityInOcean)
const creatorPoolShare =
price?.ocean &&
price?.datatoken &&
creatorLiquidity &&
((Number(creatorPoolTokens) / Number(totalPoolTokens)) * 100).toFixed(
2
)
setCreatorPoolShare(creatorPoolShare)
// Get swap fee
// swapFee is tricky: to get 0.1% you need to convert from 0.001
const swapFee = await ocean.pool.getSwapFee(price.address)
setSwapFee(`${Number(swapFee) * 100}`)
// Get weights
const weightDt = await ocean.pool.getDenormalizedWeight(
price.address,
ddo.dataToken
)
setWeightDt(`${Number(weightDt) * 10}`)
setWeightOcean(`${100 - Number(weightDt) * 10}`)
} catch (error) { } catch (error) {
Logger.error(error.message) Logger.error(error.message)
} }
} }
init() init()
}, [ocean, accountId, price, ddo, refreshPool, owner]) }, [ocean, accountId, price, ddo, refreshPool, owner, totalPoolTokens])
// Get graph history data
useEffect(() => {
if (
!price?.address ||
!price?.ocean ||
!price?.value ||
!config?.metadataCacheUri
)
return
const source = axios.CancelToken.source()
const url = `${config.metadataCacheUri}/api/v1/aquarius/pools/history/${price.address}`
async function getData() {
Logger.log('Fired GetGraphData!')
try {
const response = await axios(url, { cancelToken: source.token })
if (!response || response.status !== 200) return
setGraphData(response.data)
} catch (error) {
if (axios.isCancel(error)) {
Logger.log(error.message)
} else {
Logger.error(error.message)
}
}
}
getData()
return () => {
source.cancel()
}
}, [config.metadataCacheUri, price?.address, price?.ocean, price?.value])
const refreshInfo = async () => { const refreshInfo = async () => {
setRefreshPool(!refreshPool) setRefreshPool(!refreshPool)
@ -318,7 +306,7 @@ export default function Pool(): ReactElement {
{weightOcean}/{weightDt} {weightOcean}/{weightDt}
</span> </span>
)} )}
<Graph data={graphData} /> <Graph />
</> </>
} }
ocean={`${price?.ocean}`} ocean={`${price?.ocean}`}

View File

@ -3,6 +3,7 @@ import styles from './index.module.css'
import Compute from './Compute' import Compute from './Compute'
import Consume from './Consume' import Consume from './Consume'
import { Logger } from '@oceanprotocol/lib' import { Logger } from '@oceanprotocol/lib'
import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
import Tabs from '../../atoms/Tabs' import Tabs from '../../atoms/Tabs'
import { useOcean } from '@oceanprotocol/react' import { useOcean } from '@oceanprotocol/react'
import compareAsBN from '../../../utils/compareAsBN' import compareAsBN from '../../../utils/compareAsBN'
@ -13,6 +14,7 @@ import { useAsset } from '../../../providers/Asset'
export default function AssetActions(): ReactElement { export default function AssetActions(): ReactElement {
const { ocean, balance, accountId } = useOcean() const { ocean, balance, accountId } = useOcean()
const { price, ddo, metadata } = useAsset() const { price, ddo, metadata } = useAsset()
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>() const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>()
const [dtBalance, setDtBalance] = useState<string>() const [dtBalance, setDtBalance] = useState<string>()
@ -21,6 +23,7 @@ export default function AssetActions(): ReactElement {
// Get and set user DT balance // Get and set user DT balance
useEffect(() => { useEffect(() => {
if (!ocean || !accountId) return if (!ocean || !accountId) return
async function init() { async function init() {
try { try {
const dtBalance = await ocean.datatokens.balance( const dtBalance = await ocean.datatokens.balance(