mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +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:
parent
83dca873f8
commit
273769388c
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,3 +12,4 @@ public/storybook
|
||||
.artifacts
|
||||
.vercel
|
||||
repo-metadata.json
|
||||
src/@types/apollo
|
@ -17,6 +17,7 @@ before_script:
|
||||
# - './cc-test-reporter before-build'
|
||||
script:
|
||||
# will run `npm ci` automatically here
|
||||
- npm run apollo:codegen
|
||||
- npm run lint
|
||||
# - './cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT'
|
||||
- npm run build
|
||||
|
167
README.md
167
README.md
@ -13,6 +13,11 @@
|
||||
- [🏄 Get Started](#-get-started)
|
||||
- [Local components with Barge](#local-components-with-barge)
|
||||
- [🦑 Environment variables](#-environment-variables)
|
||||
- [🦀 Data Sources](#-data-sources)
|
||||
- [Aquarius](#aquarius)
|
||||
- [Ocean Protocol Subgraph](#ocean-protocol-subgraph)
|
||||
- [3Box](#3box)
|
||||
- [Purgatory](#purgatory)
|
||||
- [🎨 Storybook](#-storybook)
|
||||
- [✨ Code Style](#-code-style)
|
||||
- [👩🔬 Testing](#-testing)
|
||||
@ -71,8 +76,158 @@ For local development, you can use a `.env` file:
|
||||
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
|
||||
|
||||
> 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`.
|
||||
|
||||
To run the Storybook server, execute in your Terminal:
|
||||
@ -97,6 +252,8 @@ npm run format
|
||||
|
||||
## 👩🔬 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:
|
||||
|
||||
- [react-testing-library](https://github.com/kentcdodds/react-testing-library) for all React components
|
||||
@ -131,9 +288,15 @@ npm run serve
|
||||
|
||||
## ⬆️ 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
|
||||
|
||||
|
11
apollo.config.js
Normal file
11
apollo.config.js
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,12 @@ const createMarkdownPages = require('./gatsby/createMarkdownPages')
|
||||
const execSync = require('child_process').execSync
|
||||
|
||||
// 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 }) => {
|
||||
createFields(node, actions, getNode)
|
||||
|
2763
package-lock.json
generated
2763
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@
|
||||
"build": "gatsby build && cp _redirects public/_redirects",
|
||||
"serve": "serve -s public/",
|
||||
"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",
|
||||
"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",
|
||||
@ -18,9 +18,11 @@
|
||||
"storybook": "start-storybook -p 4000 -c .storybook",
|
||||
"storybook:build": "build-storybook -c .storybook -o public/storybook",
|
||||
"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": {
|
||||
"@apollo/client": "^3.3.6",
|
||||
"@coingecko/cryptoformat": "^0.4.2",
|
||||
"@loadable/component": "^5.14.1",
|
||||
"@oceanprotocol/art": "^3.0.0",
|
||||
@ -36,6 +38,7 @@
|
||||
"axios": "^0.21.1",
|
||||
"chart.js": "^2.9.4",
|
||||
"classnames": "^2.2.6",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"date-fns": "^2.16.1",
|
||||
"decimal.js": "^10.2.1",
|
||||
"dom-confetti": "^0.2.2",
|
||||
@ -109,6 +112,7 @@
|
||||
"@types/remove-markdown": "^0.1.1",
|
||||
"@types/shortid": "0.0.29",
|
||||
"@types/yup": "^0.29.11",
|
||||
"apollo": "^2.32.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.13.0",
|
||||
"@typescript-eslint/parser": "^4.13.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import Footer from './organisms/Footer'
|
||||
import Header from './organisms/Header'
|
||||
import Styles from '../global/Styles'
|
||||
@ -7,7 +7,14 @@ import { useSiteMetadata } from '../hooks/useSiteMetadata'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
import Alert from './atoms/Alert'
|
||||
import { graphql, PageProps, useStaticQuery } from 'gatsby'
|
||||
|
||||
import {
|
||||
ApolloClient,
|
||||
ApolloProvider,
|
||||
HttpLink,
|
||||
InMemoryCache,
|
||||
NormalizedCacheObject
|
||||
} from '@apollo/client'
|
||||
import fetch from 'cross-fetch'
|
||||
const contentQuery = graphql`
|
||||
query AppQuery {
|
||||
purgatory: allFile(filter: { relativePath: { eq: "purgatory.json" } }) {
|
||||
@ -37,18 +44,31 @@ export default function App({
|
||||
const { warning } = useSiteMetadata()
|
||||
const {
|
||||
isInPurgatory: isAccountInPurgatory,
|
||||
purgatoryData: accountPurgatory
|
||||
purgatoryData: accountPurgatory,
|
||||
config
|
||||
} = 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 (
|
||||
<Styles>
|
||||
<div className={styles.app}>
|
||||
<Header />
|
||||
|
||||
{(props as PageProps).uri === '/' && (
|
||||
<Alert text={warning} state="info" />
|
||||
)}
|
||||
|
||||
{isAccountInPurgatory && (
|
||||
<Alert
|
||||
title={purgatory.title}
|
||||
@ -57,8 +77,13 @@ export default function App({
|
||||
state="error"
|
||||
/>
|
||||
)}
|
||||
|
||||
<main className={styles.main}>{children}</main>
|
||||
{client ? (
|
||||
<ApolloProvider client={client}>
|
||||
<main className={styles.main}>{children}</main>
|
||||
</ApolloProvider>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Footer />
|
||||
</div>
|
||||
</Styles>
|
||||
|
@ -9,6 +9,33 @@ import styles from './PoolTransactions.module.css'
|
||||
import { useUserPreferences } from '../../providers/UserPreferences'
|
||||
import { Ocean } from '@oceanprotocol/lib'
|
||||
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(
|
||||
ocean: Ocean,
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable camelcase */
|
||||
import React, { ChangeEvent, ReactElement, useEffect, useState } from 'react'
|
||||
import { Line, defaults } from 'react-chartjs-2'
|
||||
import {
|
||||
@ -15,21 +16,16 @@ import useDarkMode from 'use-dark-mode'
|
||||
import { darkModeConfig } from '../../../../../app.config'
|
||||
import Button from '../../../atoms/Button'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
|
||||
export interface ChartDataLiqudity {
|
||||
oceanAddRemove: ChartData[]
|
||||
datatokenAddRemove: ChartData[]
|
||||
oceanReserveHistory: ChartData[]
|
||||
datatokenReserveHistory: ChartData[]
|
||||
datatokenPriceHistory: ChartData[]
|
||||
}
|
||||
import { useAsset } from '../../../../providers/Asset'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { PoolHistory } from '../../../../@types/apollo/PoolHistory'
|
||||
|
||||
declare type GraphType = 'liquidity' | 'price'
|
||||
|
||||
// Chart.js global defaults
|
||||
defaults.global.defaultFontFamily = `'Sharp Sans', -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', Helvetica, Arial, sans-serif`
|
||||
defaults.global.animation = { easing: 'easeInOutQuart', duration: 800 }
|
||||
defaults.global.animation = { easing: 'easeInOutQuart', duration: 1000 }
|
||||
|
||||
const lineStyle: Partial<ChartDataSets> = {
|
||||
fill: false,
|
||||
@ -57,28 +53,6 @@ const tooltipOptions: Partial<ChartTooltipOptions> = {
|
||||
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 {
|
||||
return {
|
||||
layout: {
|
||||
@ -122,18 +96,45 @@ function getOptions(locale: string, isDarkMode: boolean): ChartOptions {
|
||||
|
||||
const graphTypes = ['Liquidity', 'Price']
|
||||
|
||||
export default function Graph({
|
||||
data
|
||||
}: {
|
||||
data: ChartDataLiqudity
|
||||
}): ReactElement {
|
||||
const poolHistory = gql`
|
||||
query PoolHistory($id: String!, $block: Int) {
|
||||
poolTransactions(
|
||||
first: 1000
|
||||
where: { poolAddress: $id, block_gt: $block }
|
||||
orderBy: block
|
||||
) {
|
||||
block
|
||||
spotPrice
|
||||
timestamp
|
||||
oceanReserve
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Graph(): ReactElement {
|
||||
const { locale } = useUserPreferences()
|
||||
const darkMode = useDarkMode(false, darkModeConfig)
|
||||
|
||||
const [graphData, setGraphData] = useState<ChartData>()
|
||||
const [options, setOptions] = useState<ChartOptions>()
|
||||
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(() => {
|
||||
Logger.log('Fired GraphOptions!')
|
||||
const options = getOptions(locale, darkMode.value)
|
||||
@ -143,11 +144,53 @@ export default function Graph({
|
||||
useEffect(() => {
|
||||
if (!data) return
|
||||
Logger.log('Fired GraphData!')
|
||||
const graphData =
|
||||
graphType === 'liquidity'
|
||||
? constructGraphData(data.oceanReserveHistory)
|
||||
: constructGraphData(data.datatokenPriceHistory)
|
||||
setGraphData(graphData)
|
||||
|
||||
const latestTimestamps = [
|
||||
...timestamps,
|
||||
...data.poolTransactions.map((item) => {
|
||||
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])
|
||||
|
||||
function handleGraphTypeSwitch(e: ChangeEvent<HTMLButtonElement>) {
|
||||
@ -157,7 +200,11 @@ export default function Graph({
|
||||
|
||||
return (
|
||||
<div className={styles.graphWrap}>
|
||||
{graphData ? (
|
||||
{isLoading ? (
|
||||
<Loader />
|
||||
) : error ? (
|
||||
<small>{error.message}</small>
|
||||
) : (
|
||||
<>
|
||||
<nav className={styles.type}>
|
||||
{graphTypes.map((type: GraphType) => (
|
||||
@ -176,8 +223,6 @@ export default function Graph({
|
||||
</nav>
|
||||
<Line height={70} data={graphData} options={options} />
|
||||
</>
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
@ -14,9 +14,10 @@ import TokenList from './TokenList'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
import TokenBalance from '../../../../@types/TokenBalance'
|
||||
import Transactions from './Transactions'
|
||||
import Graph, { ChartDataLiqudity } from './Graph'
|
||||
import axios from 'axios'
|
||||
import Graph from './Graph'
|
||||
import { useAsset } from '../../../../providers/Asset'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { PoolLiquidity } from '../../../../@types/apollo/PoolLiquidity'
|
||||
|
||||
const contentQuery = graphql`
|
||||
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 {
|
||||
const data = useStaticQuery(contentQuery)
|
||||
const content = data.content.edges[0].node.childContentJson.pool
|
||||
|
||||
const { ocean, accountId, networkId, config } = useOcean()
|
||||
const { ocean, accountId, networkId } = useOcean()
|
||||
const {
|
||||
isInPurgatory,
|
||||
ddo,
|
||||
@ -75,10 +94,70 @@ export default function Pool(): ReactElement {
|
||||
const [creatorLiquidity, setCreatorLiquidity] = useState<TokenBalance>()
|
||||
const [creatorPoolTokens, setCreatorPoolTokens] = useState<string>()
|
||||
const [creatorPoolShare, setCreatorPoolShare] = useState<string>()
|
||||
const [graphData, setGraphData] = useState<ChartDataLiqudity>()
|
||||
|
||||
// the purpose of the value is just to trigger the effect
|
||||
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(() => {
|
||||
setIsRemoveDisabled(isInPurgatory && owner === accountId)
|
||||
@ -104,14 +183,6 @@ export default function Pool(): ReactElement {
|
||||
if (!ocean || !accountId || !price) return
|
||||
async function init() {
|
||||
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
|
||||
//
|
||||
@ -131,95 +202,12 @@ export default function Pool(): ReactElement {
|
||||
}
|
||||
|
||||
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) {
|
||||
Logger.error(error.message)
|
||||
}
|
||||
}
|
||||
init()
|
||||
}, [ocean, accountId, price, ddo, refreshPool, owner])
|
||||
|
||||
// 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])
|
||||
}, [ocean, accountId, price, ddo, refreshPool, owner, totalPoolTokens])
|
||||
|
||||
const refreshInfo = async () => {
|
||||
setRefreshPool(!refreshPool)
|
||||
@ -318,7 +306,7 @@ export default function Pool(): ReactElement {
|
||||
{weightOcean}/{weightDt}
|
||||
</span>
|
||||
)}
|
||||
<Graph data={graphData} />
|
||||
<Graph />
|
||||
</>
|
||||
}
|
||||
ocean={`${price?.ocean}`}
|
||||
|
@ -3,6 +3,7 @@ import styles from './index.module.css'
|
||||
import Compute from './Compute'
|
||||
import Consume from './Consume'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
|
||||
import Tabs from '../../atoms/Tabs'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
import compareAsBN from '../../../utils/compareAsBN'
|
||||
@ -13,6 +14,7 @@ import { useAsset } from '../../../providers/Asset'
|
||||
export default function AssetActions(): ReactElement {
|
||||
const { ocean, balance, accountId } = useOcean()
|
||||
const { price, ddo, metadata } = useAsset()
|
||||
|
||||
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>()
|
||||
const [dtBalance, setDtBalance] = useState<string>()
|
||||
|
||||
@ -21,6 +23,7 @@ export default function AssetActions(): ReactElement {
|
||||
// Get and set user DT balance
|
||||
useEffect(() => {
|
||||
if (!ocean || !accountId) return
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
const dtBalance = await ocean.datatokens.balance(
|
||||
|
Loading…
Reference in New Issue
Block a user