1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-06-16 17:33:26 +02:00

add total user liquidity, new Profile provider (#841)

* label renaming, add total user liquidity

* new useProfile provider

* centralize pool shares fetching

* add some assets fetching to profile provider

* move 3box profile fetching, check passed accountId

* cancel token fixes

* remove publisher on published assets list

* more cancel token fixes

* prevent asset name double fetching in pool shares

* prevent asset name double fetching in downloads

* prevent asset name double fetching in pool transactions

* more cancel token fixes

* refetch crash fix

* another pool shares refetch fix

* pool transactions data flow refactor

* Add total downloads, speed up downloads fetching (#849)

* add total downloads

* replace multiple retrieveDDO with one single request

* getAssetsFromDidList() helper

* fix mixed up timestamps

* data structure based on tokenOrders

* add logging

* add tooltip to downloads, small NumberUnit refactor

* safeguard against passed empty didList

* deal with plural/singular in labels
This commit is contained in:
Matthias Kretschmann 2021-09-13 16:39:32 +02:00 committed by GitHub
parent 5a336bd699
commit 032606e61c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 800 additions and 435 deletions

View File

@ -9,6 +9,10 @@
font-size: var(--font-size-small);
}
.content p {
margin: 0;
}
.icon {
width: 1em;
height: 1em;

View File

@ -5,6 +5,7 @@ import { useSpring, animated } from 'react-spring'
import styles from './Tooltip.module.css'
import { ReactComponent as Info } from '../../images/info.svg'
import { Placement } from 'tippy.js'
import Markdown from './Markdown'
const cx = classNames.bind(styles)

View File

@ -1,5 +1,4 @@
import { DDO } from '@oceanprotocol/lib'
import { useOcean } from '../../providers/Ocean'
import { Link } from 'gatsby'
import React, { ReactElement, useEffect, useState } from 'react'
import { getAssetsNames } from '../../utils/aquarius'
@ -43,7 +42,7 @@ export default function AssetListTitle({
return (
<h3 className={styles.title}>
<Link to={`/asset/${did || ddo.id}`}>{assetTitle}</Link>
<Link to={`/asset/${did || ddo?.id}`}>{assetTitle}</Link>
</h3>
)
}

View File

@ -13,11 +13,13 @@ import { BestPrice } from '../../models/BestPrice'
declare type AssetTeaserProps = {
ddo: DDO
price: BestPrice
noPublisher?: boolean
}
const AssetTeaser: React.FC<AssetTeaserProps> = ({
ddo,
price
price,
noPublisher
}: AssetTeaserProps) => {
const { attributes } = ddo.findServiceByType('metadata')
const { name, type } = attributes.main
@ -34,7 +36,9 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({
<Dotdotdot clamp={3}>
<h1 className={styles.title}>{name}</h1>
</Dotdotdot>
<Publisher account={owner} minimal className={styles.publisher} />
{!noPublisher && (
<Publisher account={owner} minimal className={styles.publisher} />
)}
</header>
<AssetType

View File

@ -5,10 +5,7 @@ import { Logger } from '@oceanprotocol/lib'
import Price from '../atoms/Price'
import Tooltip from '../atoms/Tooltip'
import AssetTitle from './AssetListTitle'
import {
queryMetadata,
transformChainIdsListToQuery
} from '../../utils/aquarius'
import { getAssetsFromDidList } from '../../utils/aquarius'
import { getAssetsBestPrices, AssetListPrices } from '../../utils/subgraph'
import axios, { CancelToken } from 'axios'
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
@ -18,31 +15,8 @@ async function getAssetsBookmarked(
chainIds: number[],
cancelToken: CancelToken
) {
const searchDids = JSON.stringify(bookmarks)
.replace(/,/g, ' ')
.replace(/"/g, '')
.replace(/(\[|\])/g, '')
// for whatever reason ddo.id is not searchable, so use ddo.dataToken instead
.replace(/(did:op:)/g, '0x')
const queryBookmarks = {
page: 1,
offset: 100,
query: {
query_string: {
query: `(${searchDids}) AND (${transformChainIdsListToQuery(
chainIds
)})`,
fields: ['dataToken'],
default_operator: 'OR'
}
},
sort: { created: -1 }
}
try {
const result = await queryMetadata(queryBookmarks, cancelToken)
const result = await getAssetsFromDidList(bookmarks, chainIds, cancelToken)
return result
} catch (error) {
Logger.error(error.message)
@ -88,7 +62,7 @@ export default function Bookmarks(): ReactElement {
const { chainIds } = useUserPreferences()
useEffect(() => {
if (!appConfig.metadataCacheUri || bookmarks === []) return
if (!appConfig?.metadataCacheUri || bookmarks === []) return
const source = axios.CancelToken.source()
@ -121,7 +95,7 @@ export default function Bookmarks(): ReactElement {
return () => {
source.cancel()
}
}, [bookmarks, chainIds])
}, [bookmarks, chainIds, appConfig?.metadataCacheUri])
return (
<Table

View File

@ -3,6 +3,7 @@
font-weight: var(--font-weight-bold);
font-size: var(--font-size-h4);
color: var(--font-color-heading);
margin-left: 0;
}
.number {
@ -46,3 +47,9 @@
background: var(--brand-white);
border: 0.1rem solid var(--brand-pink);
}
.tooltip svg {
width: 0.8em !important;
height: 0.8em !important;
margin-left: 0;
}

View File

@ -1,45 +1,38 @@
import React, { ReactElement } from 'react'
import Markdown from '../atoms/Markdown'
import Tooltip from '../atoms/Tooltip'
import styles from './NumberUnit.module.css'
interface NumberInnerProps {
interface NumberUnitProps {
label: string
value: number | string | Element | ReactElement
small?: boolean
icon?: Element | ReactElement
tooltip?: string
}
interface NumberUnitProps extends NumberInnerProps {
link?: string
linkTooltip?: string
}
const NumberInner = ({ small, label, value, icon }: NumberInnerProps) => (
<>
<div className={`${styles.number} ${small && styles.small}`}>
{icon && icon}
{value}
</div>
<span className={styles.label}>{label}</span>
</>
)
export default function NumberUnit({
link,
linkTooltip,
small,
label,
value,
icon
icon,
tooltip
}: NumberUnitProps): ReactElement {
return (
<div className={styles.unit}>
{link ? (
<a href={link} title={linkTooltip}>
<NumberInner small={small} label={label} value={value} icon={icon} />
</a>
) : (
<NumberInner small={small} label={label} value={value} icon={icon} />
)}
<div className={`${styles.number} ${small && styles.small}`}>
{icon && icon}
{value}
</div>
<span className={styles.label}>
{label}{' '}
{tooltip && (
<Tooltip
content={<Markdown text={tooltip} />}
className={styles.tooltip}
/>
)}
</span>
</div>
)
}

View File

@ -68,6 +68,7 @@ export default function Title({ row }: { row: PoolTransaction }): ReactElement {
useEffect(() => {
if (!locale || !row) return
async function init() {
const title = await getTitle(row, locale)
setTitle(title)

View File

@ -1,4 +1,4 @@
import React, { ReactElement, useEffect, useState } from 'react'
import React, { ReactElement, useCallback, useEffect, useState } from 'react'
import Time from '../../atoms/Time'
import Table from '../../atoms/Table'
import AssetTitle from '../AssetListTitle'
@ -10,9 +10,10 @@ import { fetchDataForMultipleChains } from '../../../utils/subgraph'
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
import NetworkName from '../../atoms/NetworkName'
import { retrieveDDO } from '../../../utils/aquarius'
import axios from 'axios'
import axios, { CancelToken } from 'axios'
import Title from './Title'
import styles from './index.module.css'
import { DDO, Logger } from '@oceanprotocol/lib'
const REFETCH_INTERVAL = 20000
@ -75,6 +76,7 @@ export interface Datatoken {
export interface PoolTransaction extends TransactionHistoryPoolTransactions {
networkId: number
ddo: DDO
}
const columns = [
@ -87,11 +89,7 @@ const columns = [
{
name: 'Data Set',
selector: function getAssetRow(row: PoolTransaction) {
const did = web3.utils
.toChecksumAddress(row.poolAddress.datatokenAddress)
.replace('0x', 'did:op:')
return <AssetTitle did={did} />
return <AssetTitle ddo={row.ddo} />
}
},
{
@ -131,14 +129,14 @@ export default function PoolTransactions({
minimal?: boolean
accountId: string
}): ReactElement {
const [logs, setLogs] = useState<PoolTransaction[]>()
const [isLoading, setIsLoading] = useState<boolean>(false)
const [transactions, setTransactions] = useState<PoolTransaction[]>()
const [isLoading, setIsLoading] = useState<boolean>(true)
const { chainIds } = useUserPreferences()
const { appConfig } = useSiteMetadata()
const [dataFetchInterval, setDataFetchInterval] = useState<NodeJS.Timeout>()
const [data, setData] = useState<PoolTransaction[]>()
async function fetchPoolTransactionData() {
const getPoolTransactionData = useCallback(async () => {
const variables = {
user: accountId?.toLowerCase(),
pool: poolAddress?.toLowerCase()
@ -159,71 +157,94 @@ export default function PoolTransactions({
if (JSON.stringify(data) !== JSON.stringify(transactions)) {
setData(transactions)
}
}
}, [accountId, chainIds, data, poolAddress, poolChainId])
function refetchPoolTransactions() {
if (!dataFetchInterval) {
setDataFetchInterval(
setInterval(function () {
fetchPoolTransactionData()
}, REFETCH_INTERVAL)
const getPoolTransactions = useCallback(
async (cancelToken: CancelToken) => {
if (!data) return
const poolTransactions: PoolTransaction[] = []
for (let i = 0; i < data.length; i++) {
const { datatokenAddress } = data[i].poolAddress
const did = web3.utils
.toChecksumAddress(datatokenAddress)
.replace('0x', 'did:op:')
const ddo = await retrieveDDO(did, cancelToken)
ddo &&
poolTransactions.push({
...data[i],
networkId: ddo?.chainId,
ddo
})
}
const sortedTransactions = poolTransactions.sort(
(a, b) => b.timestamp - a.timestamp
)
}
}
setTransactions(sortedTransactions)
},
[data]
)
//
// Get data, periodically
//
useEffect(() => {
if (!appConfig?.metadataCacheUri) return
async function getTransactions() {
try {
await getPoolTransactionData()
if (dataFetchInterval) return
const interval = setInterval(async () => {
await getPoolTransactionData()
}, REFETCH_INTERVAL)
setDataFetchInterval(interval)
} catch (error) {
Logger.error('Error fetching pool transactions: ', error.message)
}
}
getTransactions()
return () => {
clearInterval(dataFetchInterval)
}
}, [dataFetchInterval])
}, [getPoolTransactionData, dataFetchInterval, appConfig.metadataCacheUri])
//
// Transform to final transactions
//
useEffect(() => {
if (!appConfig.metadataCacheUri) return
const cancelTokenSource = axios.CancelToken.source()
async function getTransactions() {
const poolTransactions: PoolTransaction[] = []
const source = axios.CancelToken.source()
async function transformData() {
try {
setIsLoading(true)
if (!data) {
await fetchPoolTransactionData()
return
}
const poolTransactionsData = data.map((obj) => ({ ...obj }))
for (let i = 0; i < poolTransactionsData.length; i++) {
const did = web3.utils
.toChecksumAddress(
poolTransactionsData[i].poolAddress.datatokenAddress
)
.replace('0x', 'did:op:')
const ddo = await retrieveDDO(did, source.token)
poolTransactionsData[i].networkId = ddo.chainId
poolTransactions.push(poolTransactionsData[i])
}
const sortedTransactions = poolTransactions.sort(
(a, b) => b.timestamp - a.timestamp
)
setLogs(sortedTransactions)
refetchPoolTransactions()
await getPoolTransactions(cancelTokenSource.token)
} catch (error) {
console.error('Error fetching pool transactions: ', error.message)
Logger.error('Error fetching pool transactions: ', error.message)
} finally {
setIsLoading(false)
}
}
getTransactions()
}, [accountId, chainIds, appConfig.metadataCacheUri, poolAddress, data])
transformData()
return () => {
cancelTokenSource.cancel()
}
}, [getPoolTransactions])
return accountId ? (
<Table
columns={minimal ? columnsMinimal : columns}
data={logs}
data={transactions}
isLoading={isLoading}
noTableHead={minimal}
dense={minimal}
pagination={minimal ? logs?.length >= 4 : logs?.length >= 9}
pagination={
minimal ? transactions?.length >= 4 : transactions?.length >= 9
}
paginationPerPage={minimal ? 5 : 10}
/>
) : (

View File

@ -26,6 +26,7 @@ declare type AssetListProps = {
isLoading?: boolean
onPageChange?: React.Dispatch<React.SetStateAction<number>>
className?: string
noPublisher?: boolean
}
const AssetList: React.FC<AssetListProps> = ({
@ -35,7 +36,8 @@ const AssetList: React.FC<AssetListProps> = ({
totalPages,
isLoading,
onPageChange,
className
className,
noPublisher
}) => {
const { chainIds } = useUserPreferences()
const [assetsWithPrices, setAssetWithPrices] = useState<AssetListPrices[]>()
@ -71,6 +73,7 @@ const AssetList: React.FC<AssetListProps> = ({
ddo={assetWithPrice.ddo}
price={assetWithPrice.price}
key={assetWithPrice.ddo.id}
noPublisher={noPublisher}
/>
))
) : chainIds.length === 0 ? (

View File

@ -7,23 +7,26 @@ import jellyfish from '@oceanprotocol/art/creatures/jellyfish/jellyfish-grid.svg
import Copy from '../../../atoms/Copy'
import Blockies from '../../../atoms/Blockies'
import styles from './Account.module.css'
import { useProfile } from '../../../../providers/Profile'
export default function Account({
name,
image,
accountId
}: {
name: string
image: string
accountId: string
}): ReactElement {
const { chainIds } = useUserPreferences()
const { profile } = useProfile()
return (
<div className={styles.account}>
<figure className={styles.imageWrap}>
{image ? (
<img src={image} className={styles.image} width="96" height="96" />
{profile?.image ? (
<img
src={profile?.image}
className={styles.image}
width="96"
height="96"
/>
) : accountId ? (
<Blockies accountId={accountId} className={styles.image} />
) : (
@ -37,7 +40,9 @@ export default function Account({
</figure>
<div>
<h3 className={styles.name}>{name || accountTruncate(accountId)}</h3>
<h3 className={styles.name}>
{profile?.name || accountTruncate(accountId)}
</h3>
{accountId && (
<code className={styles.accountId}>
{accountId} <Copy text={accountId} />

View File

@ -1,18 +1,18 @@
import React, { ReactElement } from 'react'
import classNames from 'classnames/bind'
import { ProfileLink } from '../../../../models/Profile'
import { ReactComponent as External } from '../../../../images/external.svg'
import styles from './PublisherLinks.module.css'
import { useProfile } from '../../../../providers/Profile'
const cx = classNames.bind(styles)
export default function PublisherLinks({
links,
className
}: {
links: ProfileLink[]
className: string
}): ReactElement {
const { profile } = useProfile()
const styleClasses = cx({
links: true,
[className]: className
@ -21,7 +21,7 @@ export default function PublisherLinks({
return (
<div className={styleClasses}>
{' — '}
{links?.map((link: ProfileLink) => {
{profile?.links?.map((link) => {
const href =
link.name === 'Twitter'
? `https://twitter.com/${link.value}`

View File

@ -1,6 +1,6 @@
.stats {
display: grid;
gap: var(--spacer);
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
margin-top: calc(var(--spacer) / 2);
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
margin-top: var(--spacer);
}

View File

@ -1,4 +1,4 @@
import { DDO, Logger } from '@oceanprotocol/lib'
import { Logger } from '@oceanprotocol/lib'
import React, { useEffect, useState } from 'react'
import { ReactElement } from 'react-markdown'
import { useUserPreferences } from '../../../../providers/UserPreferences'
@ -6,16 +6,27 @@ import {
getAccountLiquidityInOwnAssets,
getAccountNumberOfOrders,
getAssetsBestPrices,
UserTVL
UserLiquidity,
calculateUserLiquidity
} from '../../../../utils/subgraph'
import Conversion from '../../../atoms/Price/Conversion'
import NumberUnit from '../../../molecules/NumberUnit'
import styles from './Stats.module.css'
import {
queryMetadata,
transformChainIdsListToQuery
} from '../../../../utils/aquarius'
import axios from 'axios'
import { useProfile } from '../../../../providers/Profile'
import { PoolShares_poolShares as PoolShare } from '../../../../@types/apollo/PoolShares'
async function getPoolSharesLiquidity(
poolShares: PoolShare[]
): Promise<number> {
let totalLiquidity = 0
for (const poolShare of poolShares) {
const poolLiquidity = calculateUserLiquidity(poolShare)
totalLiquidity += poolLiquidity
}
return totalLiquidity
}
export default function Stats({
accountId
@ -23,80 +34,91 @@ export default function Stats({
accountId: string
}): ReactElement {
const { chainIds } = useUserPreferences()
const { poolShares, assets, assetsTotal, downloadsTotal } = useProfile()
const [publishedAssets, setPublishedAssets] = useState<DDO[]>()
const [numberOfAssets, setNumberOfAssets] = useState(0)
const [sold, setSold] = useState(0)
const [tvl, setTvl] = useState<UserTVL>()
const [publisherLiquidity, setPublisherLiquidity] = useState<UserLiquidity>()
const [totalLiquidity, setTotalLiquidity] = useState(0)
useEffect(() => {
if (!accountId) {
setNumberOfAssets(0)
setSold(0)
setTvl({ price: '0', oceanBalance: '0' })
setPublisherLiquidity({ price: '0', oceanBalance: '0' })
setTotalLiquidity(0)
return
}
async function getPublished() {
const queryPublishedAssets = {
query: {
query_string: {
query: `(publicKey.owner:${accountId}) AND (${transformChainIdsListToQuery(
chainIds
)})`
}
}
}
async function getSales() {
if (!assets) return
try {
const source = axios.CancelToken.source()
const result = await queryMetadata(queryPublishedAssets, source.token)
setPublishedAssets(result.results)
setNumberOfAssets(result.totalResults)
const nrOrders = await getAccountNumberOfOrders(
result.results,
chainIds
)
const nrOrders = await getAccountNumberOfOrders(assets, chainIds)
setSold(nrOrders)
} catch (error) {
Logger.error(error.message)
}
}
getPublished()
}, [accountId, chainIds])
getSales()
}, [accountId, chainIds, assets])
useEffect(() => {
if (!publishedAssets) return
if (!assets || !accountId || !chainIds) return
async function getAccountTVL() {
async function getPublisherLiquidity() {
try {
const accountPoolAdresses: string[] = []
const assetsPrices = await getAssetsBestPrices(publishedAssets)
const assetsPrices = await getAssetsBestPrices(assets)
for (const priceInfo of assetsPrices) {
if (priceInfo.price.type === 'pool') {
accountPoolAdresses.push(priceInfo.price.address.toLowerCase())
}
}
const userTvl: UserTVL = await getAccountLiquidityInOwnAssets(
const userLiquidity = await getAccountLiquidityInOwnAssets(
accountId,
chainIds,
accountPoolAdresses
)
setTvl(userTvl)
setPublisherLiquidity(userLiquidity)
} catch (error) {
Logger.error(error.message)
}
}
getAccountTVL()
}, [publishedAssets, accountId, chainIds])
getPublisherLiquidity()
}, [assets, accountId, chainIds])
useEffect(() => {
if (!poolShares) return
async function getTotalLiquidity() {
try {
const totalLiquidity = await getPoolSharesLiquidity(poolShares)
setTotalLiquidity(totalLiquidity)
} catch (error) {
console.error('Error fetching pool shares: ', error.message)
}
}
getTotalLiquidity()
}, [poolShares])
return (
<div className={styles.stats}>
<NumberUnit
label="Total Value Locked"
value={<Conversion price={tvl?.price} hideApproximateSymbol />}
label="Liquidity in Own Assets"
value={
<Conversion price={publisherLiquidity?.price} hideApproximateSymbol />
}
/>
<NumberUnit
label="Total Liquidity"
value={<Conversion price={`${totalLiquidity}`} hideApproximateSymbol />}
/>
<NumberUnit label={`Sale${sold === 1 ? '' : 's'}`} value={sold} />
<NumberUnit label="Published" value={assetsTotal} />
<NumberUnit
label={`Download${downloadsTotal === 1 ? '' : 's'}`}
tooltip="Datatoken orders for assets with `access` service, as opposed to `compute`. As one order could allow multiple or infinite downloads this number does not reflect the actual download count of an asset file."
value={downloadsTotal}
/>
<NumberUnit label="Sold" value={sold} />
<NumberUnit label="Published" value={numberOfAssets} />
</div>
)
}

View File

@ -1,26 +1,16 @@
import React, { ReactElement, useEffect, useState } from 'react'
import get3BoxProfile from '../../../../utils/profile'
import { Profile } from '../../../../models/Profile'
import { accountTruncate } from '../../../../utils/web3'
import axios from 'axios'
import React, { ReactElement, useState } from 'react'
import PublisherLinks from './PublisherLinks'
import Markdown from '../../../atoms/Markdown'
import Stats from './Stats'
import Account from './Account'
import styles from './index.module.css'
import { useProfile } from '../../../../providers/Profile'
const isDescriptionTextClamped = () => {
const el = document.getElementById('description')
if (el) return el.scrollHeight > el.clientHeight
}
const clearedProfile: Profile = {
name: null,
image: null,
description: null,
links: null
}
const Link3Box = ({ accountId, text }: { accountId: string; text: string }) => {
return (
<a
@ -38,62 +28,22 @@ export default function AccountHeader({
}: {
accountId: string
}): ReactElement {
const [profile, setProfile] = useState<Profile>({
name: accountTruncate(accountId),
image: null,
description: null,
links: null
})
const { profile } = useProfile()
const [isShowMore, setIsShowMore] = useState(false)
const toogleShowMore = () => {
setIsShowMore(!isShowMore)
}
useEffect(() => {
if (!accountId) {
setProfile(clearedProfile)
return
}
const source = axios.CancelToken.source()
async function getInfoFrom3Box() {
const profile3Box = await get3BoxProfile(accountId, source.token)
if (profile3Box) {
const { name, emoji, description, image, links } = profile3Box
const newName = `${emoji || ''} ${name || accountTruncate(accountId)}`
const newProfile = {
name: newName,
image,
description,
links
}
setProfile(newProfile)
} else {
setProfile(clearedProfile)
}
}
getInfoFrom3Box()
return () => {
source.cancel()
}
}, [accountId])
return (
<div className={styles.grid}>
<div>
<Account
accountId={accountId}
image={profile.image}
name={profile.name}
/>
<Account accountId={accountId} />
<Stats accountId={accountId} />
</div>
<div>
<Markdown text={profile.description} className={styles.description} />
<Markdown text={profile?.description} className={styles.description} />
{isDescriptionTextClamped() ? (
<span className={styles.more} onClick={toogleShowMore}>
<Link3Box accountId={accountId} text="Read more on 3box" />
@ -101,11 +51,8 @@ export default function AccountHeader({
) : (
''
)}
{profile.links?.length > 0 && (
<PublisherLinks
links={profile.links}
className={styles.publisherLinks}
/>
{profile?.links?.length > 0 && (
<PublisherLinks className={styles.publisherLinks} />
)}
</div>
<div className={styles.meta}>

View File

@ -1,64 +1,33 @@
import React, { ReactElement, useEffect, useState } from 'react'
import React, { ReactElement } from 'react'
import Table from '../../../atoms/Table'
import { gql } from 'urql'
import Time from '../../../atoms/Time'
import web3 from 'web3'
import AssetTitle from '../../../molecules/AssetListTitle'
import axios from 'axios'
import { retrieveDDO } from '../../../../utils/aquarius'
import { Logger } from '@oceanprotocol/lib'
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
import { useUserPreferences } from '../../../../providers/UserPreferences'
import { fetchDataForMultipleChains } from '../../../../utils/subgraph'
import { OrdersData_tokenOrders as OrdersData } from '../../../../@types/apollo/OrdersData'
import NetworkName from '../../../atoms/NetworkName'
const getTokenOrders = gql`
query OrdersData($user: String!) {
tokenOrders(
orderBy: timestamp
orderDirection: desc
where: { consumer: $user }
) {
datatokenId {
address
symbol
}
timestamp
tx
}
}
`
interface DownloadedAssets {
did: string
dtSymbol: string
timestamp: number
networkId: number
}
import { useProfile } from '../../../../providers/Profile'
import { DownloadedAsset } from '../../../../utils/aquarius'
const columns = [
{
name: 'Data Set',
selector: function getAssetRow(row: DownloadedAssets) {
return <AssetTitle did={row.did} />
selector: function getAssetRow(row: DownloadedAsset) {
return <AssetTitle ddo={row.ddo} />
}
},
{
name: 'Network',
selector: function getNetwork(row: DownloadedAssets) {
selector: function getNetwork(row: DownloadedAsset) {
return <NetworkName networkId={row.networkId} />
}
},
{
name: 'Datatoken',
selector: function getTitleRow(row: DownloadedAssets) {
selector: function getTitleRow(row: DownloadedAsset) {
return row.dtSymbol
}
},
{
name: 'Time',
selector: function getTimeRow(row: DownloadedAssets) {
selector: function getTimeRow(row: DownloadedAsset) {
return <Time date={row.timestamp.toString()} relative isUnix />
}
}
@ -69,67 +38,14 @@ export default function ComputeDownloads({
}: {
accountId: string
}): ReactElement {
const { appConfig } = useSiteMetadata()
const [isLoading, setIsLoading] = useState(false)
const [orders, setOrders] = useState<DownloadedAssets[]>()
const { chainIds } = useUserPreferences()
useEffect(() => {
const variables = { user: accountId?.toLowerCase() }
async function filterAssets() {
const filteredOrders: DownloadedAssets[] = []
const source = axios.CancelToken.source()
try {
setIsLoading(true)
const response = await fetchDataForMultipleChains(
getTokenOrders,
variables,
chainIds
)
const data: OrdersData[] = []
for (let i = 0; i < response.length; i++) {
response[i].tokenOrders.forEach((tokenOrder: OrdersData) => {
data.push(tokenOrder)
})
}
for (let i = 0; i < data.length; i++) {
const did = web3.utils
.toChecksumAddress(data[i].datatokenId.address)
.replace('0x', 'did:op:')
const ddo = await retrieveDDO(did, source.token)
if (!ddo) continue
if (ddo.service[1].type === 'access') {
filteredOrders.push({
did: did,
networkId: ddo.chainId,
dtSymbol: data[i].datatokenId.symbol,
timestamp: data[i].timestamp
})
}
}
const sortedOrders = filteredOrders.sort(
(a, b) => b.timestamp - a.timestamp
)
setOrders(sortedOrders)
} catch (err) {
Logger.log(err.message)
} finally {
setIsLoading(false)
}
}
filterAssets()
}, [accountId, appConfig.metadataCacheUri, chainIds])
const { downloads, isDownloadsLoading } = useProfile()
return accountId ? (
<Table
columns={columns}
data={orders}
data={downloads}
paginationPerPage={10}
isLoading={isLoading}
isLoading={isDownloadsLoading}
/>
) : (
<div>Please connect your Web3 wallet.</div>

View File

@ -3,61 +3,31 @@ import Table from '../../../atoms/Table'
import Conversion from '../../../atoms/Price/Conversion'
import styles from './PoolShares.module.css'
import AssetTitle from '../../../molecules/AssetListTitle'
import { gql } from 'urql'
import {
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'
import { useUserPreferences } from '../../../../providers/UserPreferences'
import {
fetchDataForMultipleChains,
calculateUserLiquidity
} from '../../../../utils/subgraph'
import { calculateUserLiquidity } from '../../../../utils/subgraph'
import NetworkName from '../../../atoms/NetworkName'
import axios from 'axios'
import axios, { CancelToken } from 'axios'
import { retrieveDDO } from '../../../../utils/aquarius'
import { isValidNumber } from '../../../../utils/numberValidations'
import Decimal from 'decimal.js'
import { useProfile } from '../../../../providers/Profile'
import { DDO } from '@oceanprotocol/lib'
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
const REFETCH_INTERVAL = 20000
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 {
id
isDatatoken
symbol
}
oceanReserve
datatokenReserve
totalShares
consumePrice
spotPrice
createTime
}
}
}
`
interface Asset {
userLiquidity: number
poolShare: PoolShare
networkId: number
createTime: number
ddo: DDO
}
function findTokenByType(tokens: PoolSharePoolIdTokens[], type: string) {
@ -126,10 +96,7 @@ const columns = [
{
name: 'Data Set',
selector: function getAssetRow(row: Asset) {
const did = web3.utils
.toChecksumAddress(row.poolShare.poolId.datatokenAddress)
.replace('0x', 'did:op:')
return <AssetTitle did={did} />
return <AssetTitle ddo={row.ddo} />
},
grow: 2
},
@ -161,38 +128,27 @@ const columns = [
}
]
async function getPoolSharesData(accountId: string, chainIds: number[]) {
const variables = { user: accountId?.toLowerCase() }
const data: PoolShare[] = []
const result = await fetchDataForMultipleChains(
poolSharesQuery,
variables,
chainIds
)
for (let i = 0; i < result.length; i++) {
result[i].poolShares.forEach((poolShare: PoolShare) => {
data.push(poolShare)
})
}
return data
}
async function getPoolSharesAssets(data: PoolShare[]) {
async function getPoolSharesAssets(
data: PoolShare[],
cancelToken: CancelToken
) {
const assetList: Asset[] = []
const source = axios.CancelToken.source()
for (let i = 0; i < data.length; i++) {
const did = web3.utils
.toChecksumAddress(data[i].poolId.datatokenAddress)
.replace('0x', 'did:op:')
const ddo = await retrieveDDO(did, source.token)
const ddo = await retrieveDDO(did, cancelToken)
const userLiquidity = calculateUserLiquidity(data[i])
assetList.push({
poolShare: data[i],
userLiquidity: userLiquidity,
networkId: ddo?.chainId,
createTime: data[i].poolId.createTime
})
ddo &&
assetList.push({
poolShare: data[i],
userLiquidity: userLiquidity,
networkId: ddo?.chainId,
createTime: data[i].poolId.createTime,
ddo
})
}
const assets = assetList.sort((a, b) => b.createTime - a.createTime)
return assets
@ -203,33 +159,36 @@ export default function PoolShares({
}: {
accountId: string
}): ReactElement {
const { poolShares, isPoolSharesLoading } = useProfile()
const [assets, setAssets] = useState<Asset[]>()
const [loading, setLoading] = useState<boolean>(false)
const [dataFetchInterval, setDataFetchInterval] = useState<NodeJS.Timeout>()
const { chainIds } = useUserPreferences()
const fetchPoolSharesData = useCallback(async () => {
try {
const data = await getPoolSharesData(accountId, chainIds)
const newAssets = await getPoolSharesAssets(data)
const fetchPoolSharesAssets = useCallback(
async (cancelToken: CancelToken) => {
if (!poolShares || isPoolSharesLoading) return
if (JSON.stringify(assets) !== JSON.stringify(newAssets)) {
setAssets(newAssets)
try {
const assets = await getPoolSharesAssets(poolShares, cancelToken)
setAssets(assets)
} catch (error) {
console.error('Error fetching pool shares: ', error.message)
}
} catch (error) {
console.error('Error fetching pool shares: ', error.message)
}
}, [accountId, assets, chainIds])
},
[poolShares, isPoolSharesLoading]
)
useEffect(() => {
const cancelTokenSource = axios.CancelToken.source()
async function init() {
setLoading(true)
await fetchPoolSharesData()
await fetchPoolSharesAssets(cancelTokenSource.token)
setLoading(false)
if (dataFetchInterval) return
const interval = setInterval(async () => {
await fetchPoolSharesData()
await fetchPoolSharesAssets(cancelTokenSource.token)
}, REFETCH_INTERVAL)
setDataFetchInterval(interval)
}
@ -237,8 +196,9 @@ export default function PoolShares({
return () => {
clearInterval(dataFetchInterval)
cancelTokenSource.cancel()
}
}, [dataFetchInterval, fetchPoolSharesData])
}, [dataFetchInterval, fetchPoolSharesAssets])
return accountId ? (
<Table

View File

@ -2,15 +2,12 @@ import { Logger } from '@oceanprotocol/lib'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import React, { ReactElement, useEffect, useState } from 'react'
import AssetList from '../../../organisms/AssetList'
import axios from 'axios'
import {
queryMetadata,
transformChainIdsListToQuery
} from '../../../../utils/aquarius'
import { getPublishedAssets } from '../../../../utils/aquarius'
import Filters from '../../../templates/Search/Filters'
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
import { useUserPreferences } from '../../../../providers/UserPreferences'
import styles from './PublishedList.module.css'
import axios from 'axios'
export default function PublishedList({
accountId
@ -23,30 +20,23 @@ export default function PublishedList({
const [queryResult, setQueryResult] = useState<QueryResult>()
const [isLoading, setIsLoading] = useState(false)
const [page, setPage] = useState<number>(1)
const [service, setServiceType] = useState<string>()
const [service, setServiceType] = useState('dataset OR algorithm')
useEffect(() => {
async function getPublished() {
const serviceFiter =
service === undefined ? 'dataset OR algorithm' : `${service}`
if (!accountId) return
const queryPublishedAssets = {
page: page,
offset: 9,
query: {
query_string: {
query: `(publicKey.owner:${accountId}) AND (service.attributes.main.type:${serviceFiter}) AND (${transformChainIdsListToQuery(
chainIds
)})`
}
},
sort: { created: -1 }
}
try {
const source = axios.CancelToken.source()
if (!accountId) return
const cancelTokenSource = axios.CancelToken.source()
async function getPublished() {
try {
setIsLoading(true)
const result = await queryMetadata(queryPublishedAssets, source.token)
const result = await getPublishedAssets(
accountId,
chainIds,
cancelTokenSource.token,
page,
service
)
setQueryResult(result)
} catch (error) {
Logger.error(error.message)
@ -55,6 +45,10 @@ export default function PublishedList({
}
}
getPublished()
return () => {
cancelTokenSource.cancel()
}
}, [accountId, page, appConfig.metadataCacheUri, chainIds, service])
return accountId ? (
@ -75,6 +69,7 @@ export default function PublishedList({
setPage(newPage)
}}
className={styles.assets}
noPublisher
/>
</>
) : (

View File

@ -4,6 +4,7 @@ import { graphql, PageProps } from 'gatsby'
import ProfilePage from '../../components/pages/Profile'
import { accountTruncate } from '../../utils/web3'
import { useWeb3 } from '../../providers/Web3'
import ProfileProvider from '../../providers/Profile'
export default function PageGatsbyProfile(props: PageProps): ReactElement {
const { accountId } = useWeb3()
@ -18,7 +19,9 @@ export default function PageGatsbyProfile(props: PageProps): ReactElement {
return (
<Page uri={props.uri} title={accountTruncate(finalAccountId)} noPageHeader>
<ProfilePage accountId={finalAccountId} />
<ProfileProvider accountId={finalAccountId}>
<ProfilePage accountId={finalAccountId} />
</ProfileProvider>
</Page>
)
}

291
src/providers/Profile.tsx Normal file
View File

@ -0,0 +1,291 @@
import React, {
useContext,
useState,
useEffect,
createContext,
ReactElement,
useCallback,
ReactNode
} from 'react'
import { getPoolSharesData, getUserTokenOrders } from '../utils/subgraph'
import { useUserPreferences } from './UserPreferences'
import { PoolShares_poolShares as PoolShare } from '../@types/apollo/PoolShares'
import { DDO, Logger } from '@oceanprotocol/lib'
import {
DownloadedAsset,
getDownloadAssets,
getPublishedAssets
} from '../utils/aquarius'
import { useSiteMetadata } from '../hooks/useSiteMetadata'
import { Profile } from '../models/Profile'
import { accountTruncate } from '../utils/web3'
import axios, { CancelToken } from 'axios'
import ethereumAddress from 'ethereum-address'
import get3BoxProfile from '../utils/profile'
import web3 from 'web3'
interface ProfileProviderValue {
profile: Profile
poolShares: PoolShare[]
isPoolSharesLoading: boolean
assets: DDO[]
assetsTotal: number
isEthAddress: boolean
downloads: DownloadedAsset[]
downloadsTotal: number
isDownloadsLoading: boolean
}
const ProfileContext = createContext({} as ProfileProviderValue)
const refreshInterval = 10000 // 10 sec.
function ProfileProvider({
accountId,
children
}: {
accountId: string
children: ReactNode
}): ReactElement {
const { chainIds } = useUserPreferences()
const { appConfig } = useSiteMetadata()
const [isEthAddress, setIsEthAddress] = useState<boolean>()
//
// Do nothing in all following effects
// when accountId is no ETH address
//
useEffect(() => {
const isEthAddress = ethereumAddress.isAddress(accountId)
setIsEthAddress(isEthAddress)
}, [accountId])
//
// 3Box
//
const [profile, setProfile] = useState<Profile>({
name: accountTruncate(accountId),
image: null,
description: null,
links: null
})
useEffect(() => {
const clearedProfile: Profile = {
name: null,
image: null,
description: null,
links: null
}
if (!accountId || !isEthAddress) {
setProfile(clearedProfile)
return
}
const cancelTokenSource = axios.CancelToken.source()
async function getInfoFrom3Box() {
const profile3Box = await get3BoxProfile(
accountId,
cancelTokenSource.token
)
if (profile3Box) {
const { name, emoji, description, image, links } = profile3Box
const newName = `${emoji || ''} ${name || accountTruncate(accountId)}`
const newProfile = {
name: newName,
image,
description,
links
}
setProfile(newProfile)
Logger.log('[profile] Found and set 3box profile.', newProfile)
} else {
setProfile(clearedProfile)
Logger.log('[profile] No 3box profile found.')
}
}
getInfoFrom3Box()
return () => {
cancelTokenSource.cancel()
}
}, [accountId, isEthAddress])
//
// POOL SHARES
//
const [poolShares, setPoolShares] = useState<PoolShare[]>()
const [isPoolSharesLoading, setIsPoolSharesLoading] = useState<boolean>(false)
const [poolSharesInterval, setPoolSharesInterval] = useState<NodeJS.Timeout>()
const fetchPoolShares = useCallback(async () => {
if (!accountId || !chainIds || !isEthAddress) return
try {
setIsPoolSharesLoading(true)
const poolShares = await getPoolSharesData(accountId, chainIds)
setPoolShares(poolShares)
Logger.log(
`[profile] Fetched ${poolShares.length} pool shares.`,
poolShares
)
} catch (error) {
Logger.error('Error fetching pool shares: ', error.message)
} finally {
setIsPoolSharesLoading(false)
}
}, [accountId, chainIds, isEthAddress])
useEffect(() => {
async function init() {
await fetchPoolShares()
if (poolSharesInterval) return
const interval = setInterval(async () => {
await fetchPoolShares()
}, refreshInterval)
setPoolSharesInterval(interval)
}
init()
return () => {
clearInterval(poolSharesInterval)
}
}, [poolSharesInterval, fetchPoolShares])
//
// PUBLISHED ASSETS
//
const [assets, setAssets] = useState<DDO[]>()
const [assetsTotal, setAssetsTotal] = useState(0)
// const [assetsWithPrices, setAssetsWithPrices] = useState<AssetListPrices[]>()
useEffect(() => {
if (!accountId || !isEthAddress) return
const cancelTokenSource = axios.CancelToken.source()
async function getAllPublished() {
try {
const result = await getPublishedAssets(
accountId,
chainIds,
cancelTokenSource.token
)
setAssets(result.results)
setAssetsTotal(result.totalResults)
Logger.log(
`[profile] Fetched ${result.totalResults} assets.`,
result.results
)
// Hint: this would only make sense if we "search" in all subcomponents
// against this provider's state, meaning filtering via js rather then sending
// more queries to Aquarius.
// const assetsWithPrices = await getAssetsBestPrices(result.results)
// setAssetsWithPrices(assetsWithPrices)
} catch (error) {
Logger.error(error.message)
}
}
getAllPublished()
return () => {
cancelTokenSource.cancel()
}
}, [accountId, appConfig.metadataCacheUri, chainIds, isEthAddress])
//
// DOWNLOADS
//
const [downloads, setDownloads] = useState<DownloadedAsset[]>()
const [downloadsTotal, setDownloadsTotal] = useState(0)
const [isDownloadsLoading, setIsDownloadsLoading] = useState<boolean>()
const [downloadsInterval, setDownloadsInterval] = useState<NodeJS.Timeout>()
const fetchDownloads = useCallback(
async (cancelToken: CancelToken) => {
if (!accountId || !chainIds) return
const didList: string[] = []
const tokenOrders = await getUserTokenOrders(accountId, chainIds)
for (let i = 0; i < tokenOrders?.length; i++) {
const did = web3.utils
.toChecksumAddress(tokenOrders[i].datatokenId.address)
.replace('0x', 'did:op:')
didList.push(did)
}
const downloads = await getDownloadAssets(
didList,
tokenOrders,
chainIds,
cancelToken
)
setDownloads(downloads)
setDownloadsTotal(downloads.length)
Logger.log(
`[profile] Fetched ${downloads.length} download orders.`,
downloads
)
},
[accountId, chainIds]
)
useEffect(() => {
const cancelTokenSource = axios.CancelToken.source()
async function getDownloadAssets() {
if (!appConfig?.metadataCacheUri) return
try {
setIsDownloadsLoading(true)
await fetchDownloads(cancelTokenSource.token)
} catch (err) {
Logger.log(err.message)
} finally {
setIsDownloadsLoading(false)
}
}
getDownloadAssets()
if (downloadsInterval) return
const interval = setInterval(async () => {
await fetchDownloads(cancelTokenSource.token)
}, refreshInterval)
setDownloadsInterval(interval)
return () => {
cancelTokenSource.cancel()
clearInterval(downloadsInterval)
}
}, [fetchDownloads, appConfig.metadataCacheUri, downloadsInterval])
return (
<ProfileContext.Provider
value={{
profile,
poolShares,
isPoolSharesLoading,
assets,
assetsTotal,
isEthAddress,
downloads,
downloadsTotal,
isDownloadsLoading
}}
>
{children}
</ProfileContext.Provider>
)
}
// Helper hook to access the provider values
const useProfile = (): ProfileProviderValue => useContext(ProfileContext)
export { ProfileProvider, useProfile, ProfileProviderValue, ProfileContext }
export default ProfileProvider

View File

@ -12,7 +12,16 @@ import {
import { AssetSelectionAsset } from '../components/molecules/FormFields/AssetSelection'
import { PriceList, getAssetsPriceList } from './subgraph'
import axios, { CancelToken, AxiosResponse } from 'axios'
import { OrdersData_tokenOrders as OrdersData } from '../@types/apollo/OrdersData'
import { metadataCacheUri } from '../../app.config'
import web3 from '../../tests/unit/__mocks__/web3'
export interface DownloadedAsset {
dtSymbol: string
timestamp: number
networkId: number
ddo: DDO
}
function getQueryForAlgorithmDatasets(algorithmDid: string, chainId?: number) {
return {
@ -66,7 +75,8 @@ export async function queryMetadata(
try {
const response: AxiosResponse<any> = await axios.post(
`${metadataCacheUri}/api/v1/aquarius/assets/ddo/query`,
{ ...query, cancelToken }
{ ...query },
{ cancelToken }
)
if (!response || response.status !== 200 || !response.data) return
return transformQueryResult(response.data)
@ -108,10 +118,8 @@ export async function getAssetsNames(
try {
const response: AxiosResponse<Record<string, string>> = await axios.post(
`${metadataCacheUri}/api/v1/aquarius/assets/names`,
{
didList,
cancelToken
}
{ didList },
{ cancelToken }
)
if (!response || response.status !== 200 || !response.data) return
return response.data
@ -204,3 +212,122 @@ export async function getAlgorithmDatasetsForCompute(
)
return datasets
}
export async function getPublishedAssets(
accountId: string,
chainIds: number[],
cancelToken: CancelToken,
page?: number,
type?: string
): Promise<QueryResult> {
if (!accountId) return
page = page || 1
type = type || 'dataset OR algorithm'
const queryPublishedAssets = {
page,
offset: 9,
query: {
query_string: {
query: `(publicKey.owner:${accountId}) AND (service.attributes.main.type:${type}) AND (${transformChainIdsListToQuery(
chainIds
)})`
}
},
sort: { created: -1 }
}
try {
const result = await queryMetadata(queryPublishedAssets, cancelToken)
return result
} catch (error) {
if (axios.isCancel(error)) {
Logger.log(error.message)
} else {
Logger.error(error.message)
}
}
}
export async function getAssetsFromDidList(
didList: string[],
chainIds: number[],
cancelToken: CancelToken
): Promise<QueryResult> {
try {
// TODO: figure out cleaner way to transform string[] into csv
const searchDids = JSON.stringify(didList)
.replace(/,/g, ' ')
.replace(/"/g, '')
.replace(/(\[|\])/g, '')
// for whatever reason ddo.id is not searchable, so use ddo.dataToken instead
.replace(/(did:op:)/g, '0x')
// safeguard against passed empty didList, preventing 500 from Aquarius
if (!searchDids) return
const query = {
page: 1,
offset: 1000,
query: {
query_string: {
query: `(${searchDids}) AND (${transformChainIdsListToQuery(
chainIds
)})`,
fields: ['dataToken'],
default_operator: 'OR'
}
},
sort: { created: -1 }
}
const queryResult = await queryMetadata(query, cancelToken)
return queryResult
} catch (error) {
Logger.error(error.message)
}
}
export async function getDownloadAssets(
didList: string[],
tokenOrders: OrdersData[],
chainIds: number[],
cancelToken: CancelToken
): Promise<DownloadedAsset[]> {
const downloadedAssets: DownloadedAsset[] = []
try {
const queryResult = await getAssetsFromDidList(
didList,
chainIds,
cancelToken
)
const ddoList = queryResult?.results
for (let i = 0; i < tokenOrders?.length; i++) {
const ddo = ddoList.filter(
(ddo) =>
tokenOrders[i].datatokenId.address.toLowerCase() ===
ddo.dataToken.toLowerCase()
)[0]
// make sure we are only pushing download orders
if (ddo.service[1].type !== 'access') continue
downloadedAssets.push({
ddo,
networkId: ddo.chainId,
dtSymbol: tokenOrders[i].datatokenId.symbol,
timestamp: tokenOrders[i].timestamp
})
}
const sortedOrders = downloadedAssets.sort(
(a, b) => b.timestamp - a.timestamp
)
return sortedOrders
} catch (error) {
Logger.error(error.message)
}
}

View File

@ -1,5 +1,5 @@
import { gql, OperationResult, TypedDocumentNode, OperationContext } from 'urql'
import { DDO } from '@oceanprotocol/lib'
import { DDO, Logger } from '@oceanprotocol/lib'
import { getUrqlClientInstance } from '../providers/UrqlProvider'
import { getOceanConfig } from './ocean'
import web3 from 'web3'
@ -25,8 +25,9 @@ import {
PoolShares_poolShares as PoolShare
} from '../@types/apollo/PoolShares'
import { BestPrice } from '../models/BestPrice'
import { OrdersData_tokenOrders as OrdersData } from '../@types/apollo/OrdersData'
export interface UserTVL {
export interface UserLiquidity {
price: string
oceanBalance: string
}
@ -206,6 +207,52 @@ const UserSharesQuery = gql`
}
}
`
const userPoolSharesQuery = gql`
query PoolShares($user: String) {
poolShares(where: { userAddress: $user, balance_gt: 0.001 }, first: 1000) {
id
balance
userAddress {
id
}
poolId {
id
datatokenAddress
valueLocked
tokens {
id
isDatatoken
symbol
}
oceanReserve
datatokenReserve
totalShares
consumePrice
spotPrice
createTime
}
}
}
`
const UserTokenOrders = gql`
query OrdersData($user: String!) {
tokenOrders(
orderBy: timestamp
orderDirection: desc
where: { consumer: $user }
) {
datatokenId {
address
symbol
}
timestamp
tx
}
}
`
export function getSubgraphUri(chainId: number): string {
const config = getOceanConfig(chainId)
return config.subgraphUri
@ -604,7 +651,7 @@ export async function getAccountLiquidityInOwnAssets(
accountId: string,
chainIds: number[],
pools: string[]
): Promise<UserTVL> {
): Promise<UserLiquidity> {
const queryVariables = {
user: accountId.toLowerCase(),
pools: pools
@ -630,3 +677,48 @@ export async function getAccountLiquidityInOwnAssets(
oceanBalance: totalOceanLiquidity.toString()
}
}
export async function getPoolSharesData(
accountId: string,
chainIds: number[]
): 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)
})
}
return data
}
export async function getUserTokenOrders(
accountId: string,
chainIds: number[]
): Promise<OrdersData[]> {
const data: OrdersData[] = []
const variables = { user: accountId?.toLowerCase() }
try {
const tokenOrders = await fetchDataForMultipleChains(
UserTokenOrders,
variables,
chainIds
)
for (let i = 0; i < tokenOrders?.length; i++) {
tokenOrders[i].tokenOrders.forEach((tokenOrder: OrdersData) => {
data.push(tokenOrder)
})
}
return data
} catch (error) {
Logger.error(error.message)
}
}