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

Get pool shares using The Graph (#360)

* added graph query

* fixed negative liquidity value for input error

* used graph to get poolShares

* replaced total pool liquidity with user liquidity, get ddo on row

* get symbol from graph, calculate userLiquidity

* fixed userLiquidity price and sorted table by userLiquidity

* removed ordering by balance

* displayed pool and client liquidity, disabled table header hover

* order Your Liquidity before Pool Liquidity

* removed line height on asset title in pool shares table

* limit table to 5 rows, refactor liquidity comp, changed balance color

* code climate similar blocks fix

* changed lockedValue to valueLocked in pool shares query

* removed husky file
This commit is contained in:
Norby 2021-02-10 17:16:24 +02:00 committed by GitHub
parent 3e2ad9674c
commit b043eab047
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 202 additions and 51 deletions

View File

@ -9,10 +9,12 @@ const cx = classNames.bind(styles)
export default function Conversion({ export default function Conversion({
price, price,
className className,
hideApproximateSymbol
}: { }: {
price: string // expects price in OCEAN, not wei price: string // expects price in OCEAN, not wei
className?: string className?: string
hideApproximateSymbol?: boolean
}): ReactElement { }): ReactElement {
const { prices } = usePrices() const { prices } = usePrices()
const { currency, locale } = useUserPreferences() const { currency, locale } = useUserPreferences()
@ -59,7 +61,8 @@ export default function Conversion({
className={styleClasses} className={styleClasses}
title="Approximation based on current OCEAN spot price on Coingecko" title="Approximation based on current OCEAN spot price on Coingecko"
> >
<strong dangerouslySetInnerHTML={{ __html: priceConverted }} />{' '} {!hideApproximateSymbol && '≈ '}
<strong dangerouslySetInnerHTML={{ __html: priceConverted }} />{' '}
{!isFiat && currency} {!isFiat && currency}
</span> </span>
) )

View File

@ -15,10 +15,14 @@
.table [role='columnheader'] { .table [role='columnheader'] {
text-transform: uppercase; text-transform: uppercase;
color: var(--color-secondary);
font-size: var(--font-size-small); font-size: var(--font-size-small);
} }
.table [role='columnheader'] > span,
.table [role='columnheader'] > div {
color: var(--color-secondary);
}
.table [role='row']:not(:last-of-type) { .table [role='row']:not(:last-of-type) {
border-color: var(--border-color); border-color: var(--border-color);
} }

View File

@ -6,6 +6,9 @@ import styles from './Table.module.css'
interface TableProps extends IDataTableProps { interface TableProps extends IDataTableProps {
isLoading?: boolean isLoading?: boolean
emptyMessage?: string emptyMessage?: string
sortField?: string
sortAsc?: boolean
className?: string
} }
function Empty({ message }: { message?: string }): ReactElement { function Empty({ message }: { message?: string }): ReactElement {
@ -19,13 +22,16 @@ export default function Table({
emptyMessage, emptyMessage,
pagination, pagination,
paginationPerPage, paginationPerPage,
sortField,
sortAsc,
className,
...props ...props
}: TableProps): ReactElement { }: TableProps): ReactElement {
return ( return (
<DataTable <DataTable
columns={columns} columns={columns}
data={data} data={data}
className={styles.table} className={className ? styles.table + ` ${className}` : styles.table}
noHeader noHeader
pagination={pagination || data?.length >= 9} pagination={pagination || data?.length >= 9}
paginationPerPage={paginationPerPage || 10} paginationPerPage={paginationPerPage || 10}
@ -33,6 +39,8 @@ export default function Table({
noDataComponent={<Empty message={emptyMessage} />} noDataComponent={<Empty message={emptyMessage} />}
progressPending={isLoading} progressPending={isLoading}
progressComponent={<Loader />} progressComponent={<Loader />}
defaultSortField={sortField}
defaultSortAsc={sortAsc}
{...props} {...props}
/> />
) )

View File

@ -115,6 +115,7 @@ export default function FormAdd({
type="number" type="number"
name="amount" name="amount"
max={amountMax} max={amountMax}
min="0"
value={`${values.amount}`} value={`${values.amount}`}
step="any" step="any"
prefix={<CoinSelect dtSymbol={dtSymbol} setCoin={setCoin} />} prefix={<CoinSelect dtSymbol={dtSymbol} setCoin={setCoin} />}

View File

@ -148,7 +148,6 @@ export default function Pool(): ReactElement {
const totalCreatorLiquidityInOcean = const totalCreatorLiquidityInOcean =
creatorLiquidity?.ocean + creatorLiquidity?.datatoken * price?.value creatorLiquidity?.ocean + creatorLiquidity?.datatoken * price?.value
setCreatorTotalLiquidityInOcean(totalCreatorLiquidityInOcean) setCreatorTotalLiquidityInOcean(totalCreatorLiquidityInOcean)
const creatorPoolShare = const creatorPoolShare =
price?.ocean && price?.ocean &&
price?.datatoken && price?.datatoken &&
@ -174,7 +173,6 @@ export default function Pool(): ReactElement {
const totalUserLiquidityInOcean = const totalUserLiquidityInOcean =
userLiquidity?.ocean + userLiquidity?.datatoken * price?.value userLiquidity?.ocean + userLiquidity?.datatoken * price?.value
setTotalUserLiquidityInOcean(totalUserLiquidityInOcean) setTotalUserLiquidityInOcean(totalUserLiquidityInOcean)
const totalLiquidityInOcean = price?.ocean + price?.datatoken * price?.value const totalLiquidityInOcean = price?.ocean + price?.datatoken * price?.value
setTotalLiquidityInOcean(totalLiquidityInOcean) setTotalLiquidityInOcean(totalLiquidityInOcean)
}, [userLiquidity, price, poolTokens, totalPoolTokens]) }, [userLiquidity, price, poolTokens, totalPoolTokens])

View File

@ -3,7 +3,6 @@
font-weight: var(--font-weight-base) !important; font-weight: var(--font-weight-base) !important;
font-size: var(--font-size-small); font-size: var(--font-size-small);
padding-left: var(--font-size-base); padding-left: var(--font-size-base);
padding-top: calc(var(--spacer) / 10);
} }
.totalLiquidity strong { .totalLiquidity strong {
@ -11,3 +10,51 @@
color: var(--font-color-text); color: var(--font-color-text);
line-height: 1; line-height: 1;
} }
.poolSharesTable [role='gridcell'] {
align-items: flex-start;
margin: calc(var(--spacer) / 2) 0;
}
.poolSharesTable [class*='AssetListTitle-module--title'] {
line-height: 0 !important;
}
.poolSharesTable [class*='Token-module--token'] div {
color: var(--color-secondary);
}
@media (min-width: 30rem) {
.poolSharesTable [class*='AssetListTitle-module--title'] {
line-height: 0 !important;
}
}
.yourLiquidity {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.yourLiquidity [class*='Conversion-module--'] {
margin-bottom: calc(var(--spacer) / 8);
}
.yourLiquidity [class*='Conversion-module--'] strong {
font-size: var(--font-size-base);
}
.yourLiquidity [class*='Token-module--token'] {
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: calc(var(--spacer) / 8);
}
.yourLiquidity [class*='Token-module--token'] div {
font-size: var(--font-size-small);
}
.yourLiquidity [class*='Token-module--icon'] {
display: none;
}

View File

@ -1,26 +1,107 @@
import { useMetadata, useOcean } from '@oceanprotocol/react' import { useOcean } from '@oceanprotocol/react'
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import Table from '../../atoms/Table' import Table from '../../atoms/Table'
import { DDO, Logger, MetadataCache } from '@oceanprotocol/lib'
import PriceUnit from '../../atoms/Price/PriceUnit'
import Conversion from '../../atoms/Price/Conversion' import Conversion from '../../atoms/Price/Conversion'
import styles from './PoolShares.module.css' import styles from './PoolShares.module.css'
import AssetTitle from '../../molecules/AssetListTitle' import AssetTitle from '../../molecules/AssetListTitle'
import { gql, useQuery } from '@apollo/client'
import {
PoolShares as PoolSharesList,
PoolShares_poolShares as PoolShare,
PoolShares_poolShares_poolId_tokens as PoolSharePoolIdTokens
} from '../../../@types/apollo/PoolShares'
import web3 from 'web3'
import Token from '../../organisms/AssetActions/Pool/Token'
const poolSharesQuery = gql`
query PoolShares($user: String) {
poolShares(where: { userAddress: $user, balance_gt: 0.001 }, first: 1000) {
id
balance
userAddress {
id
}
poolId {
id
datatokenAddress
valueLocked
tokens {
tokenId {
symbol
}
}
oceanReserve
datatokenReserve
totalShares
consumePrice
spotPrice
}
}
}
`
interface Asset { interface Asset {
ddo: DDO userLiquidity: number
shares: string poolShare: PoolShare
} }
function TotalLiquidity({ ddo }: { ddo: DDO }): ReactElement { function calculateUserLiquidity(poolShare: PoolShare) {
const { price } = useMetadata(ddo) const ocean =
const totalLiquidityInOcean = price?.ocean + price?.datatoken * price?.value (poolShare.balance / poolShare.poolId.totalShares) *
poolShare.poolId.oceanReserve
const datatokens =
(poolShare.balance / poolShare.poolId.totalShares) *
poolShare.poolId.datatokenReserve
const totalLiquidity = ocean + datatokens * poolShare.poolId.consumePrice
return totalLiquidity
}
function findValidToken(tokens: PoolSharePoolIdTokens[]) {
const symbol = tokens.find((token) => token.tokenId !== null)
return symbol.tokenId.symbol
}
function Symbol({ tokens }: { tokens: PoolSharePoolIdTokens[] }) {
return <>{findValidToken(tokens)}</>
}
function Liquidity({ row, type }: { row: Asset; type: string }) {
let price = ``
let oceanTokenBalance = ''
let dataTokenBalance = ''
if (type === 'user') {
price = `${row.userLiquidity}`
const userShare = row.poolShare.balance / row.poolShare.poolId.totalShares
oceanTokenBalance = (
userShare * row.poolShare.poolId.oceanReserve
).toString()
dataTokenBalance = (
userShare * row.poolShare.poolId.datatokenReserve
).toString()
}
if (type === 'pool') {
price = `${
Number(row.poolShare.poolId.oceanReserve) +
Number(row.poolShare.poolId.datatokenReserve) *
row.poolShare.poolId.consumePrice
}`
oceanTokenBalance = row.poolShare.poolId.oceanReserve.toString()
dataTokenBalance = row.poolShare.poolId.datatokenReserve.toString()
}
return ( return (
<div className={styles.yourLiquidity}>
<Conversion <Conversion
price={`${totalLiquidityInOcean}`} price={price}
className={styles.totalLiquidity} className={styles.totalLiquidity}
hideApproximateSymbol
/> />
<Token symbol="OCEAN" balance={oceanTokenBalance} noIcon />
<Token
symbol={findValidToken(row.poolShare.poolId.tokens)}
balance={dataTokenBalance}
noIcon
/>
</div>
) )
} }
@ -28,59 +109,68 @@ const columns = [
{ {
name: 'Data Set', name: 'Data Set',
selector: function getAssetRow(row: Asset) { selector: function getAssetRow(row: Asset) {
return <AssetTitle ddo={row.ddo} /> const did = web3.utils
.toChecksumAddress(row.poolShare.poolId.datatokenAddress)
.replace('0x', 'did:op:')
return <AssetTitle did={did} />
}, },
grow: 2 grow: 2
}, },
{ {
name: 'Datatoken', name: 'Datatoken',
selector: 'ddo.dataTokenInfo.symbol' selector: function getSymbol(row: Asset) {
return <Symbol tokens={row.poolShare.poolId.tokens} />
}
}, },
{ {
name: 'Your Pool Shares', name: 'Your Liquidity',
selector: function getAssetRow(row: Asset) { selector: function getAssetRow(row: Asset) {
return <PriceUnit price={row.shares} symbol="pool shares" small /> return <Liquidity row={row} type="user" />
}, },
right: true right: true
}, },
{ {
name: 'Total Pool Liquidity', name: 'Pool Liquidity',
selector: function getAssetRow(row: Asset) { selector: function getAssetRow(row: Asset) {
return <TotalLiquidity ddo={row.ddo} /> return <Liquidity row={row} type="pool" />
}, },
right: true right: true
} }
] ]
export default function PoolShares(): ReactElement { export default function PoolShares(): ReactElement {
const { ocean, accountId, config } = useOcean() const { accountId } = useOcean()
const [assets, setAssets] = useState<Asset[]>() const [assets, setAssets] = useState<Asset[]>()
const [isLoading, setIsLoading] = useState(false) const { data, loading } = useQuery<PoolSharesList>(poolSharesQuery, {
variables: {
user: accountId?.toLowerCase()
},
pollInterval: 20000
})
useEffect(() => { useEffect(() => {
async function getAssets() { if (!data) return
if (!ocean || !accountId || !config?.metadataCacheUri) return const assetList: Asset[] = []
setIsLoading(true) data.poolShares.forEach((poolShare) => {
const userLiquidity = calculateUserLiquidity(poolShare)
assetList.push({
poolShare: poolShare,
userLiquidity: userLiquidity
})
})
setAssets(assetList)
}, [data, loading])
try { return (
const pools = await ocean.pool.getPoolSharesByAddress(accountId) <Table
const metadataCache = new MetadataCache(config.metadataCacheUri, Logger) columns={columns}
const result: Asset[] = [] className={styles.poolSharesTable}
data={assets}
for (const pool of pools) { pagination
const ddo = await metadataCache.retrieveDDO(pool.did) paginationPerPage={5}
ddo && result.push({ ddo, shares: pool.shares }) isLoading={loading}
} sortField="userLiquidity"
sortAsc={false}
setAssets(result) />
} catch (error) { )
Logger.error(error.message)
} finally {
setIsLoading(false)
}
}
getAssets()
}, [ocean, accountId, config.metadataCacheUri])
return <Table columns={columns} data={assets} isLoading={isLoading} />
} }