From b043eab047ca4b0a0172e8a2a8b762cd10901c3d Mon Sep 17 00:00:00 2001 From: Norby <37236152+KatunaNorbert@users.noreply.github.com> Date: Wed, 10 Feb 2021 17:16:24 +0200 Subject: [PATCH] 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 --- src/components/atoms/Price/Conversion.tsx | 7 +- src/components/atoms/Table.module.css | 6 +- src/components/atoms/Table.tsx | 10 +- .../AssetActions/Pool/Add/FormAdd.tsx | 1 + .../organisms/AssetActions/Pool/index.tsx | 2 - .../pages/History/PoolShares.module.css | 49 ++++- src/components/pages/History/PoolShares.tsx | 178 +++++++++++++----- 7 files changed, 202 insertions(+), 51 deletions(-) diff --git a/src/components/atoms/Price/Conversion.tsx b/src/components/atoms/Price/Conversion.tsx index cbab887b06..558d9d0733 100644 --- a/src/components/atoms/Price/Conversion.tsx +++ b/src/components/atoms/Price/Conversion.tsx @@ -9,10 +9,12 @@ const cx = classNames.bind(styles) export default function Conversion({ price, - className + className, + hideApproximateSymbol }: { price: string // expects price in OCEAN, not wei className?: string + hideApproximateSymbol?: boolean }): ReactElement { const { prices } = usePrices() const { currency, locale } = useUserPreferences() @@ -59,7 +61,8 @@ export default function Conversion({ className={styleClasses} title="Approximation based on current OCEAN spot price on Coingecko" > - ≈ {' '} + {!hideApproximateSymbol && '≈ '} + {' '} {!isFiat && currency} ) diff --git a/src/components/atoms/Table.module.css b/src/components/atoms/Table.module.css index 30804dd25d..7150eb98b7 100644 --- a/src/components/atoms/Table.module.css +++ b/src/components/atoms/Table.module.css @@ -15,10 +15,14 @@ .table [role='columnheader'] { text-transform: uppercase; - color: var(--color-secondary); 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) { border-color: var(--border-color); } diff --git a/src/components/atoms/Table.tsx b/src/components/atoms/Table.tsx index 9fd807aeaa..79a764f2b3 100644 --- a/src/components/atoms/Table.tsx +++ b/src/components/atoms/Table.tsx @@ -6,6 +6,9 @@ import styles from './Table.module.css' interface TableProps extends IDataTableProps { isLoading?: boolean emptyMessage?: string + sortField?: string + sortAsc?: boolean + className?: string } function Empty({ message }: { message?: string }): ReactElement { @@ -19,13 +22,16 @@ export default function Table({ emptyMessage, pagination, paginationPerPage, + sortField, + sortAsc, + className, ...props }: TableProps): ReactElement { return ( = 9} paginationPerPage={paginationPerPage || 10} @@ -33,6 +39,8 @@ export default function Table({ noDataComponent={} progressPending={isLoading} progressComponent={} + defaultSortField={sortField} + defaultSortAsc={sortAsc} {...props} /> ) diff --git a/src/components/organisms/AssetActions/Pool/Add/FormAdd.tsx b/src/components/organisms/AssetActions/Pool/Add/FormAdd.tsx index f63c730c14..793b36a4dc 100644 --- a/src/components/organisms/AssetActions/Pool/Add/FormAdd.tsx +++ b/src/components/organisms/AssetActions/Pool/Add/FormAdd.tsx @@ -115,6 +115,7 @@ export default function FormAdd({ type="number" name="amount" max={amountMax} + min="0" value={`${values.amount}`} step="any" prefix={} diff --git a/src/components/organisms/AssetActions/Pool/index.tsx b/src/components/organisms/AssetActions/Pool/index.tsx index 7712b8edd4..77e644ee1d 100644 --- a/src/components/organisms/AssetActions/Pool/index.tsx +++ b/src/components/organisms/AssetActions/Pool/index.tsx @@ -148,7 +148,6 @@ export default function Pool(): ReactElement { const totalCreatorLiquidityInOcean = creatorLiquidity?.ocean + creatorLiquidity?.datatoken * price?.value setCreatorTotalLiquidityInOcean(totalCreatorLiquidityInOcean) - const creatorPoolShare = price?.ocean && price?.datatoken && @@ -174,7 +173,6 @@ export default function Pool(): ReactElement { const totalUserLiquidityInOcean = userLiquidity?.ocean + userLiquidity?.datatoken * price?.value setTotalUserLiquidityInOcean(totalUserLiquidityInOcean) - const totalLiquidityInOcean = price?.ocean + price?.datatoken * price?.value setTotalLiquidityInOcean(totalLiquidityInOcean) }, [userLiquidity, price, poolTokens, totalPoolTokens]) diff --git a/src/components/pages/History/PoolShares.module.css b/src/components/pages/History/PoolShares.module.css index cd9d72bc3e..9cd00bbe61 100644 --- a/src/components/pages/History/PoolShares.module.css +++ b/src/components/pages/History/PoolShares.module.css @@ -3,7 +3,6 @@ font-weight: var(--font-weight-base) !important; font-size: var(--font-size-small); padding-left: var(--font-size-base); - padding-top: calc(var(--spacer) / 10); } .totalLiquidity strong { @@ -11,3 +10,51 @@ color: var(--font-color-text); 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; +} diff --git a/src/components/pages/History/PoolShares.tsx b/src/components/pages/History/PoolShares.tsx index b892c46586..ec44ff6b6d 100644 --- a/src/components/pages/History/PoolShares.tsx +++ b/src/components/pages/History/PoolShares.tsx @@ -1,26 +1,107 @@ -import { useMetadata, useOcean } from '@oceanprotocol/react' +import { useOcean } from '@oceanprotocol/react' import React, { ReactElement, useEffect, useState } from 'react' 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 styles from './PoolShares.module.css' 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 { - ddo: DDO - shares: string + userLiquidity: number + poolShare: PoolShare } -function TotalLiquidity({ ddo }: { ddo: DDO }): ReactElement { - const { price } = useMetadata(ddo) - const totalLiquidityInOcean = price?.ocean + price?.datatoken * price?.value +function calculateUserLiquidity(poolShare: PoolShare) { + const ocean = + (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 ( - +
+ + + +
) } @@ -28,59 +109,68 @@ const columns = [ { name: 'Data Set', selector: function getAssetRow(row: Asset) { - return + const did = web3.utils + .toChecksumAddress(row.poolShare.poolId.datatokenAddress) + .replace('0x', 'did:op:') + return }, grow: 2 }, { name: 'Datatoken', - selector: 'ddo.dataTokenInfo.symbol' + selector: function getSymbol(row: Asset) { + return + } }, { - name: 'Your Pool Shares', + name: 'Your Liquidity', selector: function getAssetRow(row: Asset) { - return + return }, right: true }, { - name: 'Total Pool Liquidity', + name: 'Pool Liquidity', selector: function getAssetRow(row: Asset) { - return + return }, right: true } ] export default function PoolShares(): ReactElement { - const { ocean, accountId, config } = useOcean() + const { accountId } = useOcean() const [assets, setAssets] = useState() - const [isLoading, setIsLoading] = useState(false) + const { data, loading } = useQuery(poolSharesQuery, { + variables: { + user: accountId?.toLowerCase() + }, + pollInterval: 20000 + }) useEffect(() => { - async function getAssets() { - if (!ocean || !accountId || !config?.metadataCacheUri) return - setIsLoading(true) - - try { - const pools = await ocean.pool.getPoolSharesByAddress(accountId) - const metadataCache = new MetadataCache(config.metadataCacheUri, Logger) - const result: Asset[] = [] - - for (const pool of pools) { - const ddo = await metadataCache.retrieveDDO(pool.did) - ddo && result.push({ ddo, shares: pool.shares }) - } + if (!data) return + const assetList: Asset[] = [] + data.poolShares.forEach((poolShare) => { + const userLiquidity = calculateUserLiquidity(poolShare) + assetList.push({ + poolShare: poolShare, + userLiquidity: userLiquidity + }) + }) + setAssets(assetList) + }, [data, loading]) - setAssets(result) - } catch (error) { - Logger.error(error.message) - } finally { - setIsLoading(false) - } - } - getAssets() - }, [ocean, accountId, config.metadataCacheUri]) - - return + return ( +
+ ) }