Restore Pool Shares section (#1139)

* get poolShares dt addresses

* style fixes

* class names fix

* remove useless changes

* fix

* try/catch blocks, loading fix

* show pool shares fix

* delete logs, fix build

* more try/catch blocks

* check subgraph url, add try/catch block

* fixes

* pool fields fix

* minor code fixes

* fix subgraph fetch

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

* remove unused function, fixes

* use LoggerInstance, remove useless setter

* error messages fix, get rid of dt column

* small tweaks and tests

* fixes

* fixes

* modified flow for pool shares

* loading UX fixes

* unified calculations for pool liquidity

* stop the refetch madness

* profile provider already sets interval fetching for pool shares
* pool shares will change when chainIds, accountId is changed so no need to listen for changes again

* calculation tweaks

* pool stats tweak

* fix pool transactions

* fix data display in pool shares section

* minor fix, delete comment

* subgraph typings generation fix

* pool stats display tweaks

* price sizing fix

* rabbit hole fixes

* more price UI fixes

* cleanup

* wording consistency

* render all frontpage sections by default, load in assets after

Co-authored-by: ClaudiaHolhos <claudia@oceanprotocol.com>
Co-authored-by: mihaisc <mihai.scarlat@smartcontrol.ro>
Co-authored-by: mihaisc <mihai@oceanprotocol.com>
Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
claudiaHash 2022-03-09 14:58:54 +02:00 committed by GitHub
parent af60018500
commit 4331c24c0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 656 additions and 592 deletions

View File

@ -172,7 +172,6 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
.mul(100)
.toFixed(2)
: '0'
const newPoolOwnerInfo = {
liquidity,
poolShares: ownerPoolShares,

View File

@ -154,7 +154,11 @@ function ProfileProvider({
await fetchPoolShares(accountId, chainIds, isEthAddress)
if (poolSharesInterval) return
const interval = setInterval(async () => {
LoggerInstance.log(
`[profile] Re-fetching pool shares after ${refreshInterval / 1000}s.`
)
await fetchPoolShares(accountId, chainIds, isEthAddress)
}, refreshInterval)
setPoolSharesInterval(interval)

View File

@ -8,7 +8,7 @@ import {
TokensPriceQuery,
TokensPriceQuery_tokens as TokensPrice
} from '../@types/subgraph/TokensPriceQuery'
import { Asset, ProviderInstance } from '@oceanprotocol/lib'
import { Asset, LoggerInstance, ProviderInstance } from '@oceanprotocol/lib'
import { AssetExtended } from 'src/@types/AssetExtended'
import { calculateBuyPrice } from './pool'
import { getFixedBuyPrice } from './fixedRateExchange'
@ -229,41 +229,42 @@ function getAccessDetailsFromTokenPrice(
*/
export async function getOrderPriceAndFees(
asset: AssetExtended,
accountId?: string
accountId: string
): Promise<OrderPriceAndFees> {
const orderPriceAndFee = {
price: '0',
publisherMarketOrderFee: '0',
publisherMarketPoolSwapFee: '0',
publisherMarketFixedSwapFee: '0',
consumeMarketOrderFee: '0',
consumeMarketPoolSwapFee: '0',
consumeMarketFixedSwapFee: '0',
providerFee: {},
opcFee: '0'
} as OrderPriceAndFees
const { accessDetails } = asset
const { appConfig } = getSiteMetadata()
// fetch publish market order fee
orderPriceAndFee.publisherMarketOrderFee =
asset.accessDetails.publisherMarketOrderFee
// fetch consume market order fee
orderPriceAndFee.consumeMarketOrderFee = appConfig.consumeMarketOrderFee
const orderPriceAndFee = {
price: '0',
publisherMarketOrderFee:
asset?.accessDetails?.publisherMarketOrderFee || '0',
publisherMarketPoolSwapFee: '0',
publisherMarketFixedSwapFee: '0',
consumeMarketOrderFee: appConfig.consumeMarketOrderFee || '0',
consumeMarketPoolSwapFee: '0',
consumeMarketFixedSwapFee: '0',
providerFee: {
providerFeeAmount: '0'
},
opcFee: '0'
} as OrderPriceAndFees
// fetch provider fee
const initializeData = await ProviderInstance.initialize(
asset.id,
asset?.id,
asset.services[0].id,
0,
accountId,
asset.services[0].serviceEndpoint
asset?.services[0].serviceEndpoint
)
orderPriceAndFee.providerFee = initializeData.providerFee
// fetch price and swap fees
switch (accessDetails.type) {
switch (asset?.accessDetails?.type) {
case 'dynamic': {
const poolPrice = await calculateBuyPrice(accessDetails, asset.chainId)
const poolPrice = await calculateBuyPrice(
asset?.accessDetails,
asset?.chainId
)
orderPriceAndFee.price = poolPrice.tokenAmount
orderPriceAndFee.liquidityProviderSwapFee =
poolPrice.liquidityProviderSwapFeeAmount
@ -274,7 +275,7 @@ export async function getOrderPriceAndFees(
break
}
case 'fixed': {
const fixed = await getFixedBuyPrice(accessDetails, asset.chainId)
const fixed = await getFixedBuyPrice(asset?.accessDetails, asset?.chainId)
orderPriceAndFee.price = fixed.baseTokenAmount
orderPriceAndFee.opcFee = fixed.oceanFeeAmount
orderPriceAndFee.publisherMarketFixedSwapFee = fixed.marketFeeAmount
@ -297,9 +298,9 @@ export async function getOrderPriceAndFees(
/**
* @param {number} chain
* @param {string} datatokenAddress
* @param {number=} timeout timout of the service, this is needed to return order details
* @param {string=} account account that wants to buy, is needed to return order details
* @param {bool=} includeOrderPriceAndFees if false price will be spot price (pool) and rate (fre), if true you will get the order price including fees !! fees not yet done
* @param {number} timeout timout of the service, this is needed to return order details
* @param {string} account account that wants to buy, is needed to return order details
* @param {bool} includeOrderPriceAndFees if false price will be spot price (pool) and rate (fre), if true you will get the order price including fees !! fees not yet done
* @returns {Promise<AccessDetails>}
*/
export async function getAccessDetails(
@ -308,22 +309,26 @@ export async function getAccessDetails(
timeout?: number,
account = ''
): Promise<AccessDetails> {
const queryContext = getQueryContext(Number(chainId))
const tokenQueryResult: OperationResult<
TokenPriceQuery,
{ datatokenId: string; account: string }
> = await fetchData(
TokenPriceQuery,
{
datatokenId: datatokenAddress.toLowerCase(),
account: account?.toLowerCase()
},
queryContext
)
try {
const queryContext = getQueryContext(Number(chainId))
const tokenQueryResult: OperationResult<
TokenPriceQuery,
{ datatokenId: string; account: string }
> = await fetchData(
TokenPriceQuery,
{
datatokenId: datatokenAddress.toLowerCase(),
account: account?.toLowerCase()
},
queryContext
)
const tokenPrice: TokenPrice = tokenQueryResult.data.token
const accessDetails = getAccessDetailsFromTokenPrice(tokenPrice, timeout)
return accessDetails
const tokenPrice: TokenPrice = tokenQueryResult.data.token
const accessDetails = getAccessDetailsFromTokenPrice(tokenPrice, timeout)
return accessDetails
} catch (error) {
LoggerInstance.error('Error getting access details: ', error.message)
}
}
export async function getAccessDetailsForAssets(
@ -333,43 +338,48 @@ export async function getAccessDetailsForAssets(
const assetsExtended: AssetExtended[] = assets
const chainAssetLists: { [key: number]: string[] } = {}
for (const asset of assets) {
if (chainAssetLists[asset.chainId]) {
chainAssetLists[asset.chainId].push(
asset.services[0].datatokenAddress.toLowerCase()
)
} else {
chainAssetLists[asset.chainId] = []
chainAssetLists[asset.chainId].push(
asset.services[0].datatokenAddress.toLowerCase()
)
try {
for (const asset of assets) {
if (chainAssetLists[asset.chainId]) {
chainAssetLists[asset.chainId].push(
asset.services[0].datatokenAddress.toLowerCase()
)
} else {
chainAssetLists[asset.chainId] = []
chainAssetLists[asset.chainId].push(
asset.services[0].datatokenAddress.toLowerCase()
)
}
}
}
for (const chainKey in chainAssetLists) {
const queryContext = getQueryContext(Number(chainKey))
const tokenQueryResult: OperationResult<
TokensPriceQuery,
{ datatokenIds: [string]; account: string }
> = await fetchData(
TokensPriceQuery,
{
datatokenIds: chainAssetLists[chainKey],
account: account?.toLowerCase()
},
queryContext
)
tokenQueryResult.data?.tokens.forEach((token) => {
const currentAsset = assetsExtended.find(
(asset) => asset.services[0].datatokenAddress.toLowerCase() === token.id
)
const accessDetails = getAccessDetailsFromTokenPrice(
token,
currentAsset?.services[0]?.timeout
for (const chainKey in chainAssetLists) {
const queryContext = getQueryContext(Number(chainKey))
const tokenQueryResult: OperationResult<
TokensPriceQuery,
{ datatokenIds: [string]; account: string }
> = await fetchData(
TokensPriceQuery,
{
datatokenIds: chainAssetLists[chainKey],
account: account?.toLowerCase()
},
queryContext
)
tokenQueryResult.data?.tokens.forEach((token) => {
const currentAsset = assetsExtended.find(
(asset) =>
asset.services[0].datatokenAddress.toLowerCase() === token.id
)
const accessDetails = getAccessDetailsFromTokenPrice(
token,
currentAsset?.services[0]?.timeout
)
currentAsset.accessDetails = accessDetails
})
currentAsset.accessDetails = accessDetails
})
}
return assetsExtended
} catch (error) {
LoggerInstance.error('Error getting access details: ', error.message)
}
return assetsExtended
}

View File

@ -170,6 +170,28 @@ export async function getAssetsFromDidList(
}
}
export async function getAssetsFromDtList(
dtList: string[],
chainIds: number[],
cancelToken: CancelToken
): Promise<Asset[]> {
try {
if (!(dtList.length > 0)) return
const baseParams = {
chainIds: chainIds,
filters: [getFilterTerm('services.datatokenAddress', dtList)],
ignorePurgatory: true
} as BaseQueryParams
const query = generateBaseQuery(baseParams)
const queryResult = await queryMetadata(query, cancelToken)
return queryResult?.results
} catch (error) {
LoggerInstance.error(error.message)
}
}
export async function retrieveDDOListByDIDs(
didList: string[],
chainIds: number[],

View File

@ -16,9 +16,6 @@ export function getOceanConfig(network: string | number): Config {
? undefined
: process.env.NEXT_PUBLIC_INFURA_PROJECT_ID
) as Config
// TODO: remove hack once address is fixed
if (network === 'rinkeby' || network === 4)
config.oceanTokenAddress = '0x8967bcf84170c91b0d24d4302c2376283b0b3a07'
return config as Config
}
@ -30,7 +27,7 @@ export function getDevelopmentConfig(): Config {
// metadataContractAddress: contractAddresses.development?.Metadata,
// oceanTokenAddress: contractAddresses.development?.Ocean,
// There is no subgraph in barge so we hardcode the Rinkeby one for now
subgraphUri: 'https://subgraphv4.rinkeby.oceanprotocol.com'
subgraphUri: 'https://v4.subgraph.rinkeby.oceanprotocol.com'
} as Config
}

View File

@ -5,7 +5,8 @@ import { getDummyWeb3 } from './web3'
import { TransactionReceipt } from 'web3-eth'
import Decimal from 'decimal.js'
import { AccessDetails } from 'src/@types/Price'
import { isValidNumber } from './numbers'
import { MAX_DECIMALS } from './constants'
/**
* This is used to calculate the price to buy one datatoken from a pool, that is different from spot price. You need to pass either a web3 object or a chainId. If you pass a chainId a dummy web3 object will be created
* @param {AccessDetails} accessDetails
@ -73,3 +74,40 @@ export async function buyDtFromPool(
return result
}
/**
* Calculate the base token liquidity based on shares info
* @param {string} shares
* @param {string} totalShares
* @param {string} baseTokenLiquidity
* @returns
*/
export function calculateUserLiquidity(
shares: string,
totalShares: string,
baseTokenLiquidity: string
): string {
const totalLiquidity =
isValidNumber(shares) &&
isValidNumber(totalShares) &&
isValidNumber(baseTokenLiquidity)
? new Decimal(shares)
.dividedBy(new Decimal(totalShares))
.mul(baseTokenLiquidity)
: new Decimal(0)
return totalLiquidity.toDecimalPlaces(MAX_DECIMALS).toString()
}
export function calculateUserTVL(
shares: string,
totalShares: string,
baseTokenLiquidity: string
): string {
const liquidity = calculateUserLiquidity(
shares,
totalShares,
baseTokenLiquidity
)
const tvl = new Decimal(liquidity).mul(2) // we multiply by 2 because of 50/50 weight
return tvl.toDecimalPlaces(MAX_DECIMALS).toString()
}

View File

@ -12,12 +12,12 @@ import {
PoolShares as PoolSharesList,
PoolShares_poolShares as PoolShare
} from '../@types/subgraph/PoolShares'
import {
OrdersData_orders as OrdersData,
OrdersData_orders_datatoken as OrdersDatatoken
} from '../@types/subgraph/OrdersData'
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
import { UserSalesQuery as UsersSalesList } from '../@types/subgraph/UserSalesQuery'
import { OpcFeesQuery as OpcFeesData } from '../@types/subgraph/OpcFeesQuery'
import { calculateUserTVL } from './pool'
import Decimal from 'decimal.js'
import { MAX_DECIMALS } from './constants'
export interface UserLiquidity {
price: string
@ -164,7 +164,7 @@ const UserTokenOrders = gql`
`
const UserSalesQuery = gql`
query UserSalesQuery($user: String!) {
query UserSalesQuery($user: ID!) {
users(where: { id: $user }) {
id
totalSales
@ -177,7 +177,7 @@ const TopSalesQuery = gql`
query TopSalesQuery {
users(
first: 20
orderBy: tokensOwned
orderBy: sharesOwned
orderDirection: desc
where: { tokenBalancesOwned_not: "0" }
) {
@ -206,14 +206,17 @@ export function getSubgraphUri(chainId: number): string {
}
export function getQueryContext(chainId: number): OperationContext {
const queryContext: OperationContext = {
url: `${getSubgraphUri(
Number(chainId)
)}/subgraphs/name/oceanprotocol/ocean-subgraph`,
requestPolicy: 'cache-and-network'
try {
const queryContext: OperationContext = {
url: `${getSubgraphUri(
Number(chainId)
)}/subgraphs/name/oceanprotocol/ocean-subgraph`,
requestPolicy: 'cache-and-network'
}
return queryContext
} catch (error) {
LoggerInstance.error('Get query context error: ', error.message)
}
return queryContext
}
export async function fetchData(
@ -223,12 +226,13 @@ export async function fetchData(
): Promise<any> {
try {
const client = getUrqlClientInstance()
const response = await client.query(query, variables, context).toPromise()
return response
} catch (error) {
console.error('Error fetchData: ', error.message)
throw Error(error.message)
LoggerInstance.error('Error fetchData: ', error.message)
}
return null
}
export async function fetchDataForMultipleChains(
@ -237,21 +241,20 @@ export async function fetchDataForMultipleChains(
chainIds: number[]
): Promise<any[]> {
let datas: any[] = []
for (const chainId of chainIds) {
const context: OperationContext = {
url: `${getSubgraphUri(
chainId
)}/subgraphs/name/oceanprotocol/ocean-subgraph`,
requestPolicy: 'network-only'
}
try {
try {
for (const chainId of chainIds) {
// console.log('fetch chainID', chainId)
const context: OperationContext = getQueryContext(chainId)
const response = await fetchData(query, variables, context)
datas = datas.concat(response.data)
} catch (error) {
console.error('Error fetchData: ', error.message)
// console.log('fetch response', response)
if (!response || response.error) continue
datas = datas.concat(response?.data)
// console.log('fetch datas', datas)
}
return datas
} catch (error) {
LoggerInstance.error('Error fetchDataForMultipleChains: ', error.message)
}
return datas
}
export async function getOpcFees(chainId: number) {
@ -268,7 +271,7 @@ export async function getOpcFees(chainId: number) {
)
opcFees = response?.data?.opc
} catch (error) {
console.error('Error fetchData: ', error.message)
LoggerInstance.error('Error getOpcFees: ', error.message)
throw Error(error.message)
}
return opcFees
@ -320,6 +323,7 @@ export async function getHighestLiquidityDatatokens(
): Promise<string[]> {
const dtList: string[] = []
let highestLiquidityAssets: HighestLiquidityAssetsPool[] = []
for (const chain of chainIds) {
const queryContext = getQueryContext(Number(chain))
const fetchedPools: OperationResult<HighestLiquidityGraphAssets, any> =
@ -339,22 +343,11 @@ export async function getHighestLiquidityDatatokens(
return dtList
}
export function calculateUserLiquidity(poolShare: PoolShare): number {
const ocean =
(poolShare.shares / poolShare.pool.totalShares) *
poolShare.pool.baseTokenLiquidity
const datatokens =
(poolShare.shares / poolShare.pool.totalShares) *
poolShare.pool.datatokenLiquidity
const totalLiquidity = ocean + datatokens * poolShare.pool.spotPrice
return totalLiquidity
}
export async function getAccountLiquidityInOwnAssets(
export async function getAccountTVLInOwnAssets(
accountId: string,
chainIds: number[],
pools: string[]
): Promise<UserLiquidity> {
): Promise<string> {
const queryVariables = {
user: accountId.toLowerCase(),
pools: pools
@ -364,22 +357,22 @@ export async function getAccountLiquidityInOwnAssets(
queryVariables,
chainIds
)
let totalLiquidity = 0
let totalOceanLiquidity = 0
let tvl = new Decimal(0)
// console.log('resss', results)
for (const result of results) {
// console.log('result.poolShares', result.poolShares)
for (const poolShare of result.poolShares) {
const userShare = poolShare.shares / poolShare.pool.totalShares
const userBalance = userShare * poolShare.pool.baseTokenLiquidity
totalOceanLiquidity += userBalance
const poolLiquidity = calculateUserLiquidity(poolShare)
totalLiquidity += poolLiquidity
const poolUserTvl = calculateUserTVL(
poolShare.shares,
poolShare.pool.totalShares,
poolShare.pool.baseTokenLiquidity
)
tvl = tvl.add(new Decimal(poolUserTvl))
// console.log('result.poolShares', tvl.toString())
}
}
return {
price: totalLiquidity.toString(),
oceanBalance: totalOceanLiquidity.toString()
}
return tvl.toDecimalPlaces(MAX_DECIMALS).toString()
}
export async function getPoolSharesData(
@ -388,17 +381,21 @@ export async function getPoolSharesData(
): Promise<PoolShare[]> {
const variables = { user: accountId?.toLowerCase() }
const data: PoolShare[] = []
const result = await fetchDataForMultipleChains(
userPoolSharesQuery,
variables,
chainIds
)
for (let i = 0; i < result.length; i++) {
result[i].poolShares.forEach((poolShare: PoolShare) => {
data.push(poolShare)
})
try {
const result = await fetchDataForMultipleChains(
userPoolSharesQuery,
variables,
chainIds
)
for (let i = 0; i < result.length; i++) {
result[i].poolShares.forEach((poolShare: PoolShare) => {
data.push(poolShare)
})
}
return data
} catch (error) {
LoggerInstance.error('Error getPoolSharesData: ', error.message)
}
return data
}
export async function getUserTokenOrders(
@ -422,7 +419,7 @@ export async function getUserTokenOrders(
return data
} catch (error) {
LoggerInstance.error(error.message)
LoggerInstance.error('Error getUserTokenOrders', error.message)
}
}
@ -445,7 +442,7 @@ export async function getUserSales(
}
return salesSum
} catch (error) {
LoggerInstance.log(error.message)
LoggerInstance.error('Error getUserSales', error.message)
}
}

View File

@ -36,7 +36,11 @@ export default function AssetComputeSelection({
{asset.symbol} | {asset.did}
</Dotdotdot>
</div>
<PriceUnit price={asset.price} small className={styles.price} />
<PriceUnit
price={asset.price}
size="small"
className={styles.price}
/>
</a>
</Link>
))

View File

@ -51,7 +51,7 @@ export default function AssetTeaser({
</div>
<footer className={styles.foot}>
<Price accessDetails={asset.accessDetails} small />
<Price accessDetails={asset.accessDetails} size="small" />
<NetworkName networkId={asset.chainId} className={styles.network} />
</footer>
</a>

View File

@ -110,7 +110,7 @@ export default function AssetSelection({
<PriceUnit
price={asset.price}
type={asset.price === '0' ? 'free' : undefined}
small
size="small"
className={styles.price}
/>
</div>

View File

@ -5,11 +5,10 @@ import AssetTitle from '@shared/AssetList/AssetListTitle'
import { useUserPreferences } from '@context/UserPreferences'
import { gql } from 'urql'
import { TransactionHistory_poolTransactions as TransactionHistoryPoolTransactions } from '../../../@types/subgraph/TransactionHistory'
import web3 from 'web3'
import { fetchDataForMultipleChains } from '@utils/subgraph'
import { useSiteMetadata } from '@hooks/useSiteMetadata'
import NetworkName from '@shared/NetworkName'
import { retrieveDDOListByDIDs } from '@utils/aquarius'
import { getAssetsFromDtList } from '@utils/aquarius'
import { CancelToken } from 'axios'
import Title from './Title'
import styles from './index.module.css'
@ -128,13 +127,14 @@ export default function PoolTransactions({
minimal?: boolean
accountId: string
}): ReactElement {
const [transactions, setTransactions] = useState<PoolTransaction[]>()
const [isLoading, setIsLoading] = useState<boolean>(false)
const { chainIds } = useUserPreferences()
const { appConfig } = useSiteMetadata()
const cancelToken = useCancelToken()
const [transactions, setTransactions] = useState<PoolTransaction[]>()
const [isLoading, setIsLoading] = useState<boolean>(false)
const [dataFetchInterval, setDataFetchInterval] = useState<NodeJS.Timeout>()
const [data, setData] = useState<PoolTransaction[]>()
const cancelToken = useCancelToken()
const getPoolTransactionData = useCallback(async () => {
const variables = {
@ -165,24 +165,18 @@ export default function PoolTransactions({
return
}
const poolTransactions: PoolTransaction[] = []
const didList: string[] = []
const dtList: string[] = []
for (let i = 0; i < data.length; i++) {
const { address } = data[i].datatoken
const did = web3.utils
.toChecksumAddress(address)
.replace('0x', 'did:op:')
didList.push(did)
dtList.push(data[i]?.datatoken?.address)
}
if (didList.length === 0) {
if (dtList.length === 0) {
setIsLoading(false)
return
}
const ddoList = await retrieveDDOListByDIDs(
didList,
chainIds,
cancelToken
)
const ddoList = await getAssetsFromDtList(dtList, chainIds, cancelToken)
for (let i = 0; i < data.length; i++) {
poolTransactions.push({
...data[i],

View File

@ -2,7 +2,7 @@
display: inline-block;
font-weight: var(--font-weight-bold);
font-family: var(--font-family-base);
font-size: var(--font-size-large);
color: var(--font-color-text);
line-height: 1.2;
}
@ -17,6 +17,10 @@
font-size: var(--font-size-base);
}
.price.large {
font-size: var(--font-size-large);
}
.price.small {
display: inline-block;
font-size: var(--font-size-base);
@ -26,6 +30,15 @@
font-size: var(--font-size-small);
}
.price.mini {
display: inline-block;
font-size: var(--font-size-small);
}
.price.mini .symbol {
font-size: var(--font-size-mini);
}
.price .badge {
vertical-align: middle;
margin-left: calc(var(--spacer) / 6);

View File

@ -1,13 +1,10 @@
import React, { ReactElement } from 'react'
import { formatCurrency } from '@coingecko/cryptoformat'
import classNames from 'classnames/bind'
import Conversion from './Conversion'
import styles from './PriceUnit.module.css'
import { useUserPreferences } from '@context/UserPreferences'
import Badge from '@shared/atoms/Badge'
const cx = classNames.bind(styles)
export function formatPrice(price: string, locale: string): string {
return formatCurrency(Number(price), '', locale, false, {
// Not exactly clear what `significant figures` are for this library,
@ -20,7 +17,7 @@ export function formatPrice(price: string, locale: string): string {
export default function PriceUnit({
price,
className,
small,
size = 'small',
conversion,
symbol,
type
@ -28,20 +25,14 @@ export default function PriceUnit({
price: string
type?: string
className?: string
small?: boolean
size?: 'small' | 'mini' | 'large'
conversion?: boolean
symbol?: string
}): ReactElement {
const { locale } = useUserPreferences()
const styleClasses = cx({
price: true,
small: small,
[className]: className
})
return (
<div className={styleClasses}>
<div className={`${styles.price} ${styles[size]} ${className}`}>
{type && type === 'free' ? (
<div> Free </div>
) : (

View File

@ -9,21 +9,21 @@ export default function Price({
accessDetails,
orderPriceAndFees,
className,
small,
size,
conversion
}: {
accessDetails: AccessDetails
orderPriceAndFees?: OrderPriceAndFees
className?: string
small?: boolean
conversion?: boolean
size?: 'small' | 'mini' | 'large'
}): ReactElement {
return accessDetails?.price || accessDetails?.type === 'free' ? (
<PriceUnit
price={`${orderPriceAndFees?.price || accessDetails?.price}`}
symbol={accessDetails.baseToken?.symbol}
className={className}
small={small}
size={size}
conversion={conversion}
type={accessDetails.type}
/>

View File

@ -39,7 +39,7 @@ function Row({
<PriceUnit
price={hasPreviousOrder || hasDatatoken ? '0' : `${price}`}
symbol={symbol}
small
size="small"
className={styles.price}
/>
<span className={styles.timeout}>
@ -68,7 +68,8 @@ export default function PriceOutput({
return (
<div className={styles.priceComponent}>
You will pay <PriceUnit price={`${totalPrice}`} symbol={symbol} small />
You will pay{' '}
<PriceUnit price={`${totalPrice}`} symbol={symbol} size="small" />
<Tooltip
content={
<div className={styles.calculation}>

View File

@ -389,7 +389,7 @@ export default function Compute({
<>
<div className={styles.info}>
<FileIcon file={file} isLoading={fileIsLoading} small />
<Price accessDetails={accessDetails} conversion />
<Price accessDetails={accessDetails} conversion size="large" />
</div>
{ddo.metadata.type === 'algorithm' ? (

View File

@ -258,6 +258,7 @@ export default function Download({
accessDetails={asset.accessDetails}
orderPriceAndFees={orderPriceAndFees}
conversion
size="large"
/>
{!isInPurgatory && <PurchaseButton />}
</div>

View File

@ -34,3 +34,7 @@
.noIcon {
opacity: 0;
}
.token.mini {
font-size: var(--font-size-mini);
}

View File

@ -6,20 +6,22 @@ import Logo from '@shared/atoms/Logo'
export default function Token({
symbol,
balance,
noIcon
noIcon,
size
}: {
symbol: string
balance: string
noIcon?: boolean
size?: 'small' | 'mini'
}): ReactElement {
return (
<div className={styles.token}>
<div className={`${styles.token} ${size ? styles[size] : ''}`}>
<figure
className={`${styles.icon} ${symbol} ${noIcon ? styles.noIcon : ''}`}
>
<Logo noWordmark />
</figure>
<PriceUnit price={balance} symbol={symbol} small />
<PriceUnit price={balance} symbol={symbol} size={size} />
</div>
)
}

View File

@ -12,31 +12,38 @@ export default function TokenList({
datatokenValue,
datatokenSymbol,
conversion,
highlight
highlight,
size = 'small'
}: {
title: string | ReactNode
title?: string | ReactNode
children?: ReactNode
baseTokenValue: string
baseTokenSymbol: string
datatokenValue?: string
datatokenSymbol?: string
conversion: Decimal
conversion?: Decimal
highlight?: boolean
size?: 'small' | 'mini'
}): ReactElement {
return (
<div className={`${styles.tokenlist} ${highlight ? styles.highlight : ''}`}>
<h3 className={styles.title}>{title}</h3>
{title && <h3 className={styles.title}>{title}</h3>}
<div className={styles.tokens}>
<Token symbol={baseTokenSymbol} balance={baseTokenValue} />
{datatokenValue && (
<Token symbol={datatokenSymbol} balance={datatokenValue} />
)}
{conversion.greaterThan(0) && (
<Token symbol={baseTokenSymbol} balance={baseTokenValue} size={size} />
{conversion?.greaterThan(0) && (
<Conversion
price={conversion.toString()}
className={styles.totalLiquidity}
/>
)}
{datatokenValue && (
<Token
symbol={datatokenSymbol}
balance={datatokenValue}
size={size}
/>
)}
{children}
</div>
</div>

View File

@ -16,6 +16,7 @@ import PoolTransactions from '@shared/PoolTransactions'
import Decimal from 'decimal.js'
import content from '../../../../../content/price.json'
import { usePool } from '@context/Pool'
import Token from './Token'
export default function Pool(): ReactElement {
const { accountId } = useWeb3()
@ -65,10 +66,16 @@ export default function Pool(): ReactElement {
) : (
<>
<div className={styles.dataToken}>
<PriceUnit price="1" symbol={poolInfo?.datatokenSymbol} /> ={' '}
<PriceUnit
price="1"
symbol={poolInfo?.datatokenSymbol}
size="large"
/>{' '}
={' '}
<PriceUnit
price={`${poolData?.spotPrice}`}
symbol={poolInfo?.baseTokenSymbol}
size="large"
/>
<Tooltip content={content.pool.tooltips.price} />
<div className={styles.dataTokenLinks}>
@ -90,7 +97,6 @@ export default function Pool(): ReactElement {
</ExplorerLink>
</div>
</div>
<TokenList
title={
<>
@ -113,11 +119,10 @@ export default function Pool(): ReactElement {
conversion={poolInfoUser?.liquidity}
highlight
/>
<TokenList
title={
<>
Owner Liquidity
Owner Value Locked
<span className={styles.titleInfo}>
{poolInfoOwner?.poolShare}% of pool
</span>
@ -127,7 +132,6 @@ export default function Pool(): ReactElement {
baseTokenSymbol={poolInfo?.baseTokenSymbol}
conversion={poolInfoOwner?.liquidity}
/>
<TokenList
title={
<>
@ -143,21 +147,41 @@ export default function Pool(): ReactElement {
<Graph poolSnapshots={poolSnapshots} />
</>
}
baseTokenValue={`${poolInfo?.totalLiquidityInOcean}`}
baseTokenSymbol={poolInfo?.baseTokenSymbol}
conversion={poolInfo?.totalLiquidityInOcean}
/>
<TokenList
size="mini"
baseTokenValue={`${poolData?.baseTokenLiquidity}`}
baseTokenSymbol={poolInfo?.baseTokenSymbol}
datatokenValue={`${poolData?.datatokenLiquidity}`}
datatokenSymbol={poolInfo?.datatokenSymbol}
conversion={poolInfo?.totalLiquidityInOcean}
>
{/* <Token symbol="% pool fee" balance={poolInfo?.poolFee} noIcon />
<Token symbol="% market fee" balance={poolInfo?.marketFee} noIcon />
<Token symbol="% OPF fee" balance={poolInfo?.opfFee} noIcon /> */}
<Token
symbol="% pool fee"
balance={poolInfo?.liquidityProviderSwapFee}
noIcon
size="mini"
/>
<Token
symbol="% market fee"
balance={poolInfo?.publishMarketSwapFee}
noIcon
size="mini"
/>
<Token
symbol="% OPF fee"
balance={poolInfo?.opcFee}
noIcon
size="mini"
/>
</TokenList>
<div className={styles.update}>
Fetching every {refreshInterval / 1000} sec.
</div>
<div className={stylesActions.actions}>
<Button
style="primary"
@ -178,7 +202,6 @@ export default function Pool(): ReactElement {
</Button>
)}
</div>
{accountId && (
<AssetActionHistoryTable title="Your Pool Transactions">
<PoolTransactions

View File

@ -1,35 +0,0 @@
import { isValidNumber } from '@utils/numbers'
import Decimal from 'decimal.js'
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
export async function getMaxPercentRemove(
poolAddress: string,
poolTokens: string
): Promise<string> {
// const amountMaxOcean = await ocean.pool.getOceanMaxRemoveLiquidity(
// poolAddress
// )
// const amountMaxPoolShares =
// await ocean.pool.getPoolSharesRequiredToRemoveOcean(
// poolAddress,
// amountMaxOcean
// )
// let amountMaxPercent =
// isValidNumber(amountMaxPoolShares) && isValidNumber(poolTokens)
// ? new Decimal(amountMaxPoolShares)
// .dividedBy(new Decimal(poolTokens))
// .mul(100)
// .floor()
// .toString()
// : '0'
// if (Number(amountMaxPercent) > 100) {
// amountMaxPercent = '100'
// }
// return amountMaxPercent
return '0'
}

View File

@ -14,7 +14,7 @@ function UserLiquidityLine({
return (
<div>
<span>{title}</span>
<PriceUnit price={amount} symbol={symbol} small />
<PriceUnit price={amount} symbol={symbol} size="small" />
</div>
)
}

View File

@ -34,7 +34,7 @@ export default function MarketStatsTooltip({
<PriceUnit
price={totalOceanLiquidity?.[chainId] || '0'}
symbol="OCEAN"
small
size="small"
/>
</li>
))}

View File

@ -21,8 +21,12 @@ export default function MarketStatsTotal({
/>{' '}
<abbr title="Total Value Locked">TVL</abbr> across{' '}
<strong>{total.pools}</strong> asset pools that contain{' '}
<PriceUnit price={`${total.totalOceanLiquidity}`} symbol="OCEAN" small />,
plus datatokens for each pool.
<PriceUnit
price={`${total.totalOceanLiquidity}`}
symbol="OCEAN"
size="small"
/>
, plus datatokens for each pool.
</>
)
}

View File

@ -36,7 +36,7 @@ const columns = [
{
name: 'Price',
selector: function getAssetRow(row: AssetExtended) {
return <Price accessDetails={row.accessDetails} small />
return <Price accessDetails={row.accessDetails} size="small" />
},
right: true
}

View File

@ -56,7 +56,10 @@ function SectionQueryResult({
const [loading, setLoading] = useState<boolean>()
const isMounted = useIsMounted()
const newCancelToken = useCancelToken()
useEffect(() => {
if (!query) return
async function init() {
if (chainIds.length === 0) {
const result: PagedAssets = {
@ -91,11 +94,13 @@ function SectionQueryResult({
return (
<section className={styles.section}>
<h3>{title}</h3>
<AssetList
assets={result?.results}
showPagination={false}
isLoading={loading}
isLoading={loading || !query}
/>
{action && action}
</section>
)
@ -130,28 +135,24 @@ export default function HomePage(): ReactElement {
<Bookmarks />
</section>
{queryAndDids && (
<SectionQueryResult
title="Highest Liquidity"
query={queryAndDids[0]}
queryData={queryAndDids[1]}
/>
)}
<SectionQueryResult
title="Highest Liquidity"
query={queryAndDids?.[0]}
queryData={queryAndDids?.[1]}
/>
{queryLatest && (
<SectionQueryResult
title="Recently Published"
query={queryLatest}
action={
<Button
style="text"
to="/search?sort=metadata.created&sortOrder=desc"
>
All data sets and algorithms
</Button>
}
/>
)}
<SectionQueryResult
title="Recently Published"
query={queryLatest}
action={
<Button
style="text"
to="/search?sort=metadata.created&sortOrder=desc"
>
All data sets and algorithms
</Button>
}
/>
</>
)
}

View File

@ -2,7 +2,7 @@ import React, { ReactElement } from 'react'
import { useUserPreferences } from '@context/UserPreferences'
import ExplorerLink from '@shared/ExplorerLink'
import NetworkName from '@shared/NetworkName'
import jellyfish from '@oceanprotocol/art/creatures/jellyfish/jellyfish-grid.svg'
import Jellyfish from '@oceanprotocol/art/creatures/jellyfish/jellyfish-grid.svg'
import Copy from '@shared/atoms/Copy'
import Blockies from '@shared/atoms/Blockies'
import styles from './Account.module.css'
@ -29,12 +29,7 @@ export default function Account({
) : accountId ? (
<Blockies accountId={accountId} className={styles.image} />
) : (
<img
src={jellyfish}
className={styles.image}
width="96"
height="96"
/>
<Jellyfish className={styles.image} />
)}
</figure>

View File

@ -1,28 +1,32 @@
import { LoggerInstance } from '@oceanprotocol/lib'
import React, { useEffect, useState, ReactElement } from 'react'
import { useUserPreferences } from '@context/UserPreferences'
import {
getAccountLiquidityInOwnAssets,
UserLiquidity,
calculateUserLiquidity
} from '@utils/subgraph'
import { getAccountTVLInOwnAssets, UserLiquidity } from '@utils/subgraph'
import Conversion from '@shared/Price/Conversion'
import NumberUnit from './NumberUnit'
import styles from './Stats.module.css'
import { useProfile } from '@context/Profile'
import { PoolShares_poolShares as PoolShare } from '../../../@types/subgraph/PoolShares'
import { getAccessDetailsForAssets } from '@utils/accessDetailsAndPricing'
import { calculateUserTVL } from '@utils/pool'
import Decimal from 'decimal.js'
import { MAX_DECIMALS } from '@utils/constants'
async function getPoolSharesLiquidity(
poolShares: PoolShare[]
): Promise<number> {
let totalLiquidity = 0
): Promise<string> {
let tvl = new Decimal(0)
for (const poolShare of poolShares) {
const poolLiquidity = calculateUserLiquidity(poolShare)
totalLiquidity += poolLiquidity
const poolUserTvl = calculateUserTVL(
poolShare.shares,
poolShare.pool.totalShares,
poolShare.pool.baseTokenLiquidity
)
tvl = tvl.add(new Decimal(poolUserTvl))
}
return totalLiquidity
return tvl.toDecimalPlaces(MAX_DECIMALS).toString()
}
export default function Stats({
@ -33,13 +37,13 @@ export default function Stats({
const { chainIds } = useUserPreferences()
const { poolShares, assets, assetsTotal, sales } = useProfile()
const [publisherLiquidity, setPublisherLiquidity] = useState<UserLiquidity>()
const [totalLiquidity, setTotalLiquidity] = useState(0)
const [publisherTvl, setPublisherTvl] = useState('0')
const [totalTvl, setTotalTvl] = useState('0')
useEffect(() => {
if (!accountId || chainIds.length === 0) {
setPublisherLiquidity({ price: '0', oceanBalance: '0' })
setTotalLiquidity(0)
setPublisherTvl('0')
setTotalTvl('0')
}
}, [accountId, chainIds])
@ -57,12 +61,12 @@ export default function Stats({
)
}
}
const userLiquidity = await getAccountLiquidityInOwnAssets(
const userTvl = await getAccountTVLInOwnAssets(
accountId,
chainIds,
accountPoolAdresses
)
setPublisherLiquidity(userLiquidity)
setPublisherTvl(userTvl)
} catch (error) {
LoggerInstance.error(error.message)
}
@ -75,10 +79,10 @@ export default function Stats({
async function getTotalLiquidity() {
try {
const totalLiquidity = await getPoolSharesLiquidity(poolShares)
setTotalLiquidity(totalLiquidity)
const totalTvl = await getPoolSharesLiquidity(poolShares)
setTotalTvl(totalTvl)
} catch (error) {
console.error('Error fetching pool shares: ', error.message)
LoggerInstance.error('Error fetching pool shares: ', error.message)
}
}
getTotalLiquidity()
@ -87,14 +91,12 @@ export default function Stats({
return (
<div className={styles.stats}>
<NumberUnit
label="Liquidity in Own Assets"
value={
<Conversion price={publisherLiquidity?.price} hideApproximateSymbol />
}
label="TVL in Own Assets"
value={<Conversion price={publisherTvl} hideApproximateSymbol />}
/>
<NumberUnit
label="Total Liquidity"
value={<Conversion price={`${totalLiquidity}`} hideApproximateSymbol />}
label="TVL"
value={<Conversion price={totalTvl} hideApproximateSymbol />}
/>
<NumberUnit label={`Sale${sales === 1 ? '' : 's'}`} value={sales} />
<NumberUnit label="Published" value={assetsTotal} />

View File

@ -1,60 +0,0 @@
.totalLiquidity {
margin-bottom: 0;
font-weight: var(--font-weight-base) !important;
font-size: var(--font-size-small);
padding-left: var(--font-size-base);
}
.totalLiquidity strong {
font-size: var(--font-size-base);
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;
}
}
.userLiquidity {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.userLiquidity [class*='Conversion-module--'] {
margin-bottom: calc(var(--spacer) / 8);
}
.userLiquidity [class*='Conversion-module--'] strong {
font-size: var(--font-size-base);
}
.userLiquidity [class*='Token-module--token'] {
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: calc(var(--spacer) / 8);
}
.userLiquidity [class*='Token-module--token'] div {
font-size: var(--font-size-small);
}
.userLiquidity [class*='Token-module--icon'] {
display: none;
}

View File

@ -1,229 +0,0 @@
import React, { ReactElement, useCallback, useEffect, useState } from 'react'
import Table from '@shared/atoms/Table'
import Conversion from '@shared/Price/Conversion'
import styles from './PoolShares.module.css'
import AssetTitle from '@shared/AssetList/AssetListTitle'
import { PoolShares_poolShares as PoolShare } from '../../../@types/subgraph/PoolShares'
import web3 from 'web3'
import Token from '../../Asset/AssetActions/Pool/Token'
import { calculateUserLiquidity } from '@utils/subgraph'
import NetworkName from '@shared/NetworkName'
import { retrieveDDOListByDIDs } from '@utils/aquarius'
import { CancelToken } from 'axios'
import { isValidNumber } from '@utils/numbers'
import Decimal from 'decimal.js'
import { useProfile } from '@context/Profile'
import { useCancelToken } from '@hooks/useCancelToken'
import { useIsMounted } from '@hooks/useIsMounted'
import { useUserPreferences } from '@context/UserPreferences'
import { Asset } from '@oceanprotocol/lib'
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
const REFETCH_INTERVAL = 20000
interface AssetPoolShare {
userLiquidity: number
poolShare: PoolShare
networkId: number
createTime: number
asset: Asset
}
function Liquidity({ row, type }: { row: AssetPoolShare; type: string }) {
let price = ``
let oceanTokenBalance = ''
let dataTokenBalance = ''
if (type === 'user') {
price = `${row.userLiquidity}`
const userShare = row.poolShare.shares / row.poolShare.pool.totalShares
oceanTokenBalance = (
userShare * row.poolShare.pool.baseTokenLiquidity
).toString()
dataTokenBalance = (
userShare * row.poolShare.pool.datatokenLiquidity
).toString()
}
if (type === 'pool') {
price =
isValidNumber(row.poolShare.pool.baseTokenLiquidity) &&
isValidNumber(row.poolShare.pool.datatokenLiquidity) &&
isValidNumber(row.poolShare.pool.spotPrice)
? new Decimal(row.poolShare.pool.datatokenLiquidity)
.mul(new Decimal(row.poolShare.pool.spotPrice))
.plus(row.poolShare.pool.baseTokenLiquidity)
.toString()
: '0'
oceanTokenBalance = row.poolShare.pool.baseTokenLiquidity.toString()
dataTokenBalance = row.poolShare.pool.datatokenLiquidity.toString()
}
return (
<div className={styles.userLiquidity}>
<Conversion
price={price}
className={styles.totalLiquidity}
hideApproximateSymbol
/>
<Token
symbol={row.poolShare.pool.baseToken.symbol}
balance={oceanTokenBalance}
noIcon
/>
<Token
symbol={row.poolShare.pool.datatoken.symbol}
balance={dataTokenBalance}
noIcon
/>
</div>
)
}
const columns = [
{
name: 'Data Set',
selector: function getAssetRow(row: AssetPoolShare) {
return <AssetTitle asset={row.asset} />
},
grow: 2
},
{
name: 'Network',
selector: function getNetwork(row: AssetPoolShare) {
return <NetworkName networkId={row.networkId} />
}
},
{
name: 'Datatoken',
selector: function getSymbol(row: AssetPoolShare) {
return <>{row.poolShare.pool.datatoken.symbol}</>
}
},
{
name: 'Liquidity',
selector: function getAssetRow(row: AssetPoolShare) {
return <Liquidity row={row} type="user" />
},
right: true
},
{
name: 'Pool Liquidity',
selector: function getAssetRow(row: AssetPoolShare) {
return <Liquidity row={row} type="pool" />
},
right: true
}
]
async function getPoolSharesAssets(
data: PoolShare[],
chainIds: number[],
cancelToken: CancelToken
) {
if (data.length < 1) return
const assetList: AssetPoolShare[] = []
const didList: string[] = []
for (let i = 0; i < data.length; i++) {
const did = web3.utils
.toChecksumAddress(data[i].pool.datatoken.address)
.replace('0x', 'did:op:')
didList.push(did)
}
const ddoList = await retrieveDDOListByDIDs(didList, chainIds, cancelToken)
for (let i = 0; i < data.length; i++) {
const userLiquidity = calculateUserLiquidity(data[i])
assetList.push({
poolShare: data[i],
userLiquidity: userLiquidity,
networkId: ddoList[i].chainId,
createTime: data[i].pool.createdTimestamp,
asset: ddoList[i]
})
}
const assets = assetList.sort((a, b) => b.createTime - a.createTime)
return assets
}
export default function PoolShares({
accountId
}: {
accountId: string
}): ReactElement {
const { poolShares, isPoolSharesLoading } = useProfile()
const [assets, setAssets] = useState<AssetPoolShare[]>()
const [loading, setLoading] = useState<boolean>(false)
const [dataFetchInterval, setDataFetchInterval] = useState<NodeJS.Timeout>()
const { chainIds } = useUserPreferences()
const newCancelToken = useCancelToken()
const isMounted = useIsMounted()
const fetchPoolSharesAssets = useCallback(
async (
chainIds: number[],
poolShares: PoolShare[],
cancelToken: CancelToken
) => {
try {
const assets = await getPoolSharesAssets(
poolShares,
chainIds,
cancelToken
)
setAssets(assets)
} catch (error) {
console.error('Error fetching pool shares: ', error.message)
} finally {
setLoading(false)
}
},
[]
)
// do not add chainIds,dataFetchInterval to effect dep
useEffect(() => {
const cancelToken = newCancelToken()
async function init() {
setLoading(true)
if (!poolShares || isPoolSharesLoading || !chainIds || !isMounted())
return
await fetchPoolSharesAssets(chainIds, poolShares, cancelToken)
setLoading(false)
if (dataFetchInterval) return
const interval = setInterval(async () => {
await fetchPoolSharesAssets(chainIds, poolShares, cancelToken)
}, REFETCH_INTERVAL)
setDataFetchInterval(interval)
}
init()
return () => {
clearInterval(dataFetchInterval)
}
}, [
fetchPoolSharesAssets,
isPoolSharesLoading,
newCancelToken,
poolShares,
isMounted
])
return accountId ? (
<Table
columns={columns}
className={styles.poolSharesTable}
data={assets}
pagination
paginationPerPage={5}
isLoading={loading}
sortField="userLiquidity"
sortAsc={false}
/>
) : (
<div>Please connect your Web3 wallet.</div>
)
}

View File

@ -0,0 +1,37 @@
.totalLiquidity {
margin-bottom: 0;
font-weight: var(--font-weight-base) !important;
font-size: var(--font-size-small);
padding-left: var(--font-size-base);
}
.totalLiquidity strong {
font-size: var(--font-size-base);
color: var(--font-color-text);
line-height: 1;
}
.userLiquidity {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.userLiquidity [class*='Conversion_conversion'] {
margin-bottom: calc(var(--spacer) / 8);
}
.userLiquidity [class*='Conversion_conversion'] strong {
font-size: var(--font-size-base);
}
.userLiquidity [class*='Token_token'] {
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: calc(var(--spacer) / 8);
}
.userLiquidity [class*='Token_token'] div {
font-size: var(--font-size-small);
}

View File

@ -0,0 +1,59 @@
import React from 'react'
import Conversion from '@shared/Price/Conversion'
import styles from './Liquidity.module.css'
import Token from '../../../Asset/AssetActions/Pool/Token'
import { isValidNumber } from '@utils/numbers'
import Decimal from 'decimal.js'
import { AssetPoolShare } from './index'
import { calculateUserTVL } from '@utils/pool'
export function Liquidity({
row,
type
}: {
row: AssetPoolShare
type: string
}) {
let price = '0'
let liquidity = '0'
if (type === 'user') {
price = new Decimal(row.userLiquidity).mul(2).toString()
// Liquidity in base token, calculated from pool share tokens.
liquidity = calculateUserTVL(
row.poolShare.shares,
row.poolShare.pool.totalShares,
row.poolShare.pool.baseTokenLiquidity
)
}
if (type === 'pool') {
price =
isValidNumber(row.poolShare.pool.baseTokenLiquidity) &&
isValidNumber(row.poolShare.pool.datatokenLiquidity) &&
isValidNumber(row.poolShare.pool.spotPrice)
? new Decimal(row.poolShare.pool.datatokenLiquidity)
.mul(new Decimal(row.poolShare.pool.spotPrice))
.plus(row.poolShare.pool.baseTokenLiquidity)
.toString()
: '0'
liquidity = new Decimal(row.poolShare.pool.baseTokenLiquidity)
.mul(2)
.toString()
}
return (
<div className={styles.userLiquidity}>
<Conversion
price={price}
className={styles.totalLiquidity}
hideApproximateSymbol
/>
<Token
symbol={row.poolShare.pool.baseToken.symbol}
balance={liquidity}
noIcon
/>
</div>
)
}

View File

@ -0,0 +1,49 @@
import { getAssetsFromDtList } from '@utils/aquarius'
import { calculateUserLiquidity } from '@utils/pool'
import { CancelToken } from 'axios'
import { PoolShares_poolShares as PoolShare } from '../../../../@types/subgraph/PoolShares'
import { AssetPoolShare } from '.'
import { Asset } from '@oceanprotocol/lib'
function getAsset(items: Asset[], datatoken: string): Asset {
for (let i = 0; i < items.length; i++) {
if (
items[i].datatokens[0].address.toLowerCase() === datatoken.toLowerCase()
)
return items[i]
}
return null
}
export async function getAssetsFromPoolShares(
data: PoolShare[],
chainIds: number[],
cancelToken: CancelToken
) {
if (data.length < 1) return []
const assetList: AssetPoolShare[] = []
const dtList: string[] = []
for (let i = 0; i < data.length; i++) {
dtList.push(data[i].pool.datatoken.address)
}
const ddoList = await getAssetsFromDtList(dtList, chainIds, cancelToken)
for (let i = 0; i < data.length; i++) {
const userLiquidity = calculateUserLiquidity(
data[i].shares,
data[i].pool.totalShares,
data[i].pool.baseTokenLiquidity
)
console.log(data[i].pool.datatoken.address, userLiquidity)
assetList.push({
poolShare: data[i],
userLiquidity,
networkId: getAsset(ddoList, data[i].pool.datatoken.address).chainId,
createTime: data[i].pool.createdTimestamp,
asset: getAsset(ddoList, data[i].pool.datatoken.address)
})
}
return assetList
}

View File

@ -0,0 +1,18 @@
.poolSharesTable [role='gridcell'] {
align-items: flex-start;
margin: calc(var(--spacer) / 2) 0;
}
.poolSharesTable [class*='AssetListTitle_title'] {
line-height: 0 !important;
}
.poolSharesTable [class*='Token_token'] div {
color: var(--color-secondary);
}
@media (min-width: 30rem) {
.poolSharesTable [class*='AssetListTitle_title'] {
line-height: 0 !important;
}
}

View File

@ -0,0 +1,116 @@
import React, { ReactElement, useCallback, useEffect, useState } from 'react'
import Table from '@shared/atoms/Table'
import styles from './index.module.css'
import AssetTitle from '@shared/AssetList/AssetListTitle'
import { PoolShares_poolShares as PoolShare } from '../../../../@types/subgraph/PoolShares'
import NetworkName from '@shared/NetworkName'
import Decimal from 'decimal.js'
import { useProfile } from '@context/Profile'
import { useCancelToken } from '@hooks/useCancelToken'
import { useIsMounted } from '@hooks/useIsMounted'
import { useUserPreferences } from '@context/UserPreferences'
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
import { Liquidity } from './Liquidity'
import { getAssetsFromPoolShares } from './_utils'
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
export interface AssetPoolShare {
userLiquidity: string
poolShare: PoolShare
networkId: number
createTime: number
asset: Asset
}
const columns = [
{
name: 'Data Set',
selector: function getAssetRow(row: AssetPoolShare) {
return <AssetTitle asset={row.asset} />
},
grow: 2
},
{
name: 'Network',
selector: function getNetwork(row: AssetPoolShare) {
return <NetworkName networkId={row.networkId} />
}
},
{
name: 'Your Value Locked',
selector: function getAssetRow(row: AssetPoolShare) {
return <Liquidity row={row} type="user" />
},
right: true
},
{
name: 'Total Value Locked',
selector: function getAssetRow(row: AssetPoolShare) {
return <Liquidity row={row} type="pool" />
},
right: true
}
]
export default function PoolShares({
accountId
}: {
accountId: string
}): ReactElement {
const { poolShares } = useProfile()
const { chainIds } = useUserPreferences()
const newCancelToken = useCancelToken()
const isMounted = useIsMounted()
const [assets, setAssets] = useState<AssetPoolShare[]>()
const [loading, setLoading] = useState<boolean>(false)
//
// Helper: fetch assets from pool shares data
//
const fetchPoolSharesAssets = useCallback(async () => {
if (!poolShares || !chainIds) return
try {
const assets = await getAssetsFromPoolShares(
poolShares,
chainIds,
newCancelToken()
)
setAssets(assets)
} catch (error) {
LoggerInstance.error('Error fetching pool shares: ', error.message)
} finally {
setLoading(false)
}
// We do not need to react to `chainIds` changes here, cause changing them
// triggers change of `poolShares` from `useProfile()` already.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [poolShares, newCancelToken])
//
// 1. Kick off data fetching
//
useEffect(() => {
if (!isMounted()) return
setLoading(true)
fetchPoolSharesAssets()
}, [fetchPoolSharesAssets, isMounted])
return accountId ? (
<Table
columns={columns}
className={styles.poolSharesTable}
data={assets}
pagination
paginationPerPage={5}
isLoading={loading}
sortField="userLiquidity"
sortAsc={false}
/>
) : (
<div>Please connect your Web3 wallet.</div>
)
}

View File

@ -49,7 +49,7 @@ export default function Price({
Expected first price:{' '}
<PriceUnit
price={Number(firstPrice) > 0 ? firstPrice : '-'}
small
size="small"
conversion
/>
</aside>