mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01: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:
parent
5a336bd699
commit
032606e61c
@ -9,6 +9,10 @@
|
|||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
|
@ -5,6 +5,7 @@ import { useSpring, animated } from 'react-spring'
|
|||||||
import styles from './Tooltip.module.css'
|
import styles from './Tooltip.module.css'
|
||||||
import { ReactComponent as Info } from '../../images/info.svg'
|
import { ReactComponent as Info } from '../../images/info.svg'
|
||||||
import { Placement } from 'tippy.js'
|
import { Placement } from 'tippy.js'
|
||||||
|
import Markdown from './Markdown'
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
const cx = classNames.bind(styles)
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { DDO } from '@oceanprotocol/lib'
|
import { DDO } from '@oceanprotocol/lib'
|
||||||
import { useOcean } from '../../providers/Ocean'
|
|
||||||
import { Link } from 'gatsby'
|
import { Link } from 'gatsby'
|
||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import { getAssetsNames } from '../../utils/aquarius'
|
import { getAssetsNames } from '../../utils/aquarius'
|
||||||
@ -43,7 +42,7 @@ export default function AssetListTitle({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<h3 className={styles.title}>
|
<h3 className={styles.title}>
|
||||||
<Link to={`/asset/${did || ddo.id}`}>{assetTitle}</Link>
|
<Link to={`/asset/${did || ddo?.id}`}>{assetTitle}</Link>
|
||||||
</h3>
|
</h3>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,13 @@ import { BestPrice } from '../../models/BestPrice'
|
|||||||
declare type AssetTeaserProps = {
|
declare type AssetTeaserProps = {
|
||||||
ddo: DDO
|
ddo: DDO
|
||||||
price: BestPrice
|
price: BestPrice
|
||||||
|
noPublisher?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const AssetTeaser: React.FC<AssetTeaserProps> = ({
|
const AssetTeaser: React.FC<AssetTeaserProps> = ({
|
||||||
ddo,
|
ddo,
|
||||||
price
|
price,
|
||||||
|
noPublisher
|
||||||
}: AssetTeaserProps) => {
|
}: AssetTeaserProps) => {
|
||||||
const { attributes } = ddo.findServiceByType('metadata')
|
const { attributes } = ddo.findServiceByType('metadata')
|
||||||
const { name, type } = attributes.main
|
const { name, type } = attributes.main
|
||||||
@ -34,7 +36,9 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({
|
|||||||
<Dotdotdot clamp={3}>
|
<Dotdotdot clamp={3}>
|
||||||
<h1 className={styles.title}>{name}</h1>
|
<h1 className={styles.title}>{name}</h1>
|
||||||
</Dotdotdot>
|
</Dotdotdot>
|
||||||
<Publisher account={owner} minimal className={styles.publisher} />
|
{!noPublisher && (
|
||||||
|
<Publisher account={owner} minimal className={styles.publisher} />
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<AssetType
|
<AssetType
|
||||||
|
@ -5,10 +5,7 @@ import { Logger } from '@oceanprotocol/lib'
|
|||||||
import Price from '../atoms/Price'
|
import Price from '../atoms/Price'
|
||||||
import Tooltip from '../atoms/Tooltip'
|
import Tooltip from '../atoms/Tooltip'
|
||||||
import AssetTitle from './AssetListTitle'
|
import AssetTitle from './AssetListTitle'
|
||||||
import {
|
import { getAssetsFromDidList } from '../../utils/aquarius'
|
||||||
queryMetadata,
|
|
||||||
transformChainIdsListToQuery
|
|
||||||
} from '../../utils/aquarius'
|
|
||||||
import { getAssetsBestPrices, AssetListPrices } from '../../utils/subgraph'
|
import { getAssetsBestPrices, AssetListPrices } from '../../utils/subgraph'
|
||||||
import axios, { CancelToken } from 'axios'
|
import axios, { CancelToken } from 'axios'
|
||||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||||
@ -18,31 +15,8 @@ async function getAssetsBookmarked(
|
|||||||
chainIds: number[],
|
chainIds: number[],
|
||||||
cancelToken: CancelToken
|
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 {
|
try {
|
||||||
const result = await queryMetadata(queryBookmarks, cancelToken)
|
const result = await getAssetsFromDidList(bookmarks, chainIds, cancelToken)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(error.message)
|
Logger.error(error.message)
|
||||||
@ -88,7 +62,7 @@ export default function Bookmarks(): ReactElement {
|
|||||||
const { chainIds } = useUserPreferences()
|
const { chainIds } = useUserPreferences()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!appConfig.metadataCacheUri || bookmarks === []) return
|
if (!appConfig?.metadataCacheUri || bookmarks === []) return
|
||||||
|
|
||||||
const source = axios.CancelToken.source()
|
const source = axios.CancelToken.source()
|
||||||
|
|
||||||
@ -121,7 +95,7 @@ export default function Bookmarks(): ReactElement {
|
|||||||
return () => {
|
return () => {
|
||||||
source.cancel()
|
source.cancel()
|
||||||
}
|
}
|
||||||
}, [bookmarks, chainIds])
|
}, [bookmarks, chainIds, appConfig?.metadataCacheUri])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
font-size: var(--font-size-h4);
|
font-size: var(--font-size-h4);
|
||||||
color: var(--font-color-heading);
|
color: var(--font-color-heading);
|
||||||
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.number {
|
.number {
|
||||||
@ -46,3 +47,9 @@
|
|||||||
background: var(--brand-white);
|
background: var(--brand-white);
|
||||||
border: 0.1rem solid var(--brand-pink);
|
border: 0.1rem solid var(--brand-pink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip svg {
|
||||||
|
width: 0.8em !important;
|
||||||
|
height: 0.8em !important;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
@ -1,45 +1,38 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
|
import Markdown from '../atoms/Markdown'
|
||||||
|
import Tooltip from '../atoms/Tooltip'
|
||||||
import styles from './NumberUnit.module.css'
|
import styles from './NumberUnit.module.css'
|
||||||
|
|
||||||
interface NumberInnerProps {
|
interface NumberUnitProps {
|
||||||
label: string
|
label: string
|
||||||
value: number | string | Element | ReactElement
|
value: number | string | Element | ReactElement
|
||||||
small?: boolean
|
small?: boolean
|
||||||
icon?: Element | ReactElement
|
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({
|
export default function NumberUnit({
|
||||||
link,
|
|
||||||
linkTooltip,
|
|
||||||
small,
|
small,
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
icon
|
icon,
|
||||||
|
tooltip
|
||||||
}: NumberUnitProps): ReactElement {
|
}: NumberUnitProps): ReactElement {
|
||||||
return (
|
return (
|
||||||
<div className={styles.unit}>
|
<div className={styles.unit}>
|
||||||
{link ? (
|
<div className={`${styles.number} ${small && styles.small}`}>
|
||||||
<a href={link} title={linkTooltip}>
|
{icon && icon}
|
||||||
<NumberInner small={small} label={label} value={value} icon={icon} />
|
{value}
|
||||||
</a>
|
</div>
|
||||||
) : (
|
<span className={styles.label}>
|
||||||
<NumberInner small={small} label={label} value={value} icon={icon} />
|
{label}{' '}
|
||||||
)}
|
{tooltip && (
|
||||||
|
<Tooltip
|
||||||
|
content={<Markdown text={tooltip} />}
|
||||||
|
className={styles.tooltip}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,7 @@ export default function Title({ row }: { row: PoolTransaction }): ReactElement {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!locale || !row) return
|
if (!locale || !row) return
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const title = await getTitle(row, locale)
|
const title = await getTitle(row, locale)
|
||||||
setTitle(title)
|
setTitle(title)
|
||||||
|
@ -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 Time from '../../atoms/Time'
|
||||||
import Table from '../../atoms/Table'
|
import Table from '../../atoms/Table'
|
||||||
import AssetTitle from '../AssetListTitle'
|
import AssetTitle from '../AssetListTitle'
|
||||||
@ -10,9 +10,10 @@ import { fetchDataForMultipleChains } from '../../../utils/subgraph'
|
|||||||
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||||
import NetworkName from '../../atoms/NetworkName'
|
import NetworkName from '../../atoms/NetworkName'
|
||||||
import { retrieveDDO } from '../../../utils/aquarius'
|
import { retrieveDDO } from '../../../utils/aquarius'
|
||||||
import axios from 'axios'
|
import axios, { CancelToken } from 'axios'
|
||||||
import Title from './Title'
|
import Title from './Title'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
import { DDO, Logger } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
const REFETCH_INTERVAL = 20000
|
const REFETCH_INTERVAL = 20000
|
||||||
|
|
||||||
@ -75,6 +76,7 @@ export interface Datatoken {
|
|||||||
|
|
||||||
export interface PoolTransaction extends TransactionHistoryPoolTransactions {
|
export interface PoolTransaction extends TransactionHistoryPoolTransactions {
|
||||||
networkId: number
|
networkId: number
|
||||||
|
ddo: DDO
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@ -87,11 +89,7 @@ const columns = [
|
|||||||
{
|
{
|
||||||
name: 'Data Set',
|
name: 'Data Set',
|
||||||
selector: function getAssetRow(row: PoolTransaction) {
|
selector: function getAssetRow(row: PoolTransaction) {
|
||||||
const did = web3.utils
|
return <AssetTitle ddo={row.ddo} />
|
||||||
.toChecksumAddress(row.poolAddress.datatokenAddress)
|
|
||||||
.replace('0x', 'did:op:')
|
|
||||||
|
|
||||||
return <AssetTitle did={did} />
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -131,14 +129,14 @@ export default function PoolTransactions({
|
|||||||
minimal?: boolean
|
minimal?: boolean
|
||||||
accountId: string
|
accountId: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const [logs, setLogs] = useState<PoolTransaction[]>()
|
const [transactions, setTransactions] = useState<PoolTransaction[]>()
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
const [isLoading, setIsLoading] = useState<boolean>(true)
|
||||||
const { chainIds } = useUserPreferences()
|
const { chainIds } = useUserPreferences()
|
||||||
const { appConfig } = useSiteMetadata()
|
const { appConfig } = useSiteMetadata()
|
||||||
const [dataFetchInterval, setDataFetchInterval] = useState<NodeJS.Timeout>()
|
const [dataFetchInterval, setDataFetchInterval] = useState<NodeJS.Timeout>()
|
||||||
const [data, setData] = useState<PoolTransaction[]>()
|
const [data, setData] = useState<PoolTransaction[]>()
|
||||||
|
|
||||||
async function fetchPoolTransactionData() {
|
const getPoolTransactionData = useCallback(async () => {
|
||||||
const variables = {
|
const variables = {
|
||||||
user: accountId?.toLowerCase(),
|
user: accountId?.toLowerCase(),
|
||||||
pool: poolAddress?.toLowerCase()
|
pool: poolAddress?.toLowerCase()
|
||||||
@ -159,71 +157,94 @@ export default function PoolTransactions({
|
|||||||
if (JSON.stringify(data) !== JSON.stringify(transactions)) {
|
if (JSON.stringify(data) !== JSON.stringify(transactions)) {
|
||||||
setData(transactions)
|
setData(transactions)
|
||||||
}
|
}
|
||||||
}
|
}, [accountId, chainIds, data, poolAddress, poolChainId])
|
||||||
|
|
||||||
function refetchPoolTransactions() {
|
const getPoolTransactions = useCallback(
|
||||||
if (!dataFetchInterval) {
|
async (cancelToken: CancelToken) => {
|
||||||
setDataFetchInterval(
|
if (!data) return
|
||||||
setInterval(function () {
|
|
||||||
fetchPoolTransactionData()
|
const poolTransactions: PoolTransaction[] = []
|
||||||
}, REFETCH_INTERVAL)
|
|
||||||
|
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(() => {
|
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 () => {
|
return () => {
|
||||||
clearInterval(dataFetchInterval)
|
clearInterval(dataFetchInterval)
|
||||||
}
|
}
|
||||||
}, [dataFetchInterval])
|
}, [getPoolTransactionData, dataFetchInterval, appConfig.metadataCacheUri])
|
||||||
|
|
||||||
|
//
|
||||||
|
// Transform to final transactions
|
||||||
|
//
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!appConfig.metadataCacheUri) return
|
const cancelTokenSource = axios.CancelToken.source()
|
||||||
|
|
||||||
async function getTransactions() {
|
async function transformData() {
|
||||||
const poolTransactions: PoolTransaction[] = []
|
|
||||||
const source = axios.CancelToken.source()
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
|
await getPoolTransactions(cancelTokenSource.token)
|
||||||
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()
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching pool transactions: ', error.message)
|
Logger.error('Error fetching pool transactions: ', error.message)
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getTransactions()
|
transformData()
|
||||||
}, [accountId, chainIds, appConfig.metadataCacheUri, poolAddress, data])
|
|
||||||
|
return () => {
|
||||||
|
cancelTokenSource.cancel()
|
||||||
|
}
|
||||||
|
}, [getPoolTransactions])
|
||||||
|
|
||||||
return accountId ? (
|
return accountId ? (
|
||||||
<Table
|
<Table
|
||||||
columns={minimal ? columnsMinimal : columns}
|
columns={minimal ? columnsMinimal : columns}
|
||||||
data={logs}
|
data={transactions}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
noTableHead={minimal}
|
noTableHead={minimal}
|
||||||
dense={minimal}
|
dense={minimal}
|
||||||
pagination={minimal ? logs?.length >= 4 : logs?.length >= 9}
|
pagination={
|
||||||
|
minimal ? transactions?.length >= 4 : transactions?.length >= 9
|
||||||
|
}
|
||||||
paginationPerPage={minimal ? 5 : 10}
|
paginationPerPage={minimal ? 5 : 10}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@ -26,6 +26,7 @@ declare type AssetListProps = {
|
|||||||
isLoading?: boolean
|
isLoading?: boolean
|
||||||
onPageChange?: React.Dispatch<React.SetStateAction<number>>
|
onPageChange?: React.Dispatch<React.SetStateAction<number>>
|
||||||
className?: string
|
className?: string
|
||||||
|
noPublisher?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const AssetList: React.FC<AssetListProps> = ({
|
const AssetList: React.FC<AssetListProps> = ({
|
||||||
@ -35,7 +36,8 @@ const AssetList: React.FC<AssetListProps> = ({
|
|||||||
totalPages,
|
totalPages,
|
||||||
isLoading,
|
isLoading,
|
||||||
onPageChange,
|
onPageChange,
|
||||||
className
|
className,
|
||||||
|
noPublisher
|
||||||
}) => {
|
}) => {
|
||||||
const { chainIds } = useUserPreferences()
|
const { chainIds } = useUserPreferences()
|
||||||
const [assetsWithPrices, setAssetWithPrices] = useState<AssetListPrices[]>()
|
const [assetsWithPrices, setAssetWithPrices] = useState<AssetListPrices[]>()
|
||||||
@ -71,6 +73,7 @@ const AssetList: React.FC<AssetListProps> = ({
|
|||||||
ddo={assetWithPrice.ddo}
|
ddo={assetWithPrice.ddo}
|
||||||
price={assetWithPrice.price}
|
price={assetWithPrice.price}
|
||||||
key={assetWithPrice.ddo.id}
|
key={assetWithPrice.ddo.id}
|
||||||
|
noPublisher={noPublisher}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : chainIds.length === 0 ? (
|
) : chainIds.length === 0 ? (
|
||||||
|
@ -7,23 +7,26 @@ import jellyfish from '@oceanprotocol/art/creatures/jellyfish/jellyfish-grid.svg
|
|||||||
import Copy from '../../../atoms/Copy'
|
import Copy from '../../../atoms/Copy'
|
||||||
import Blockies from '../../../atoms/Blockies'
|
import Blockies from '../../../atoms/Blockies'
|
||||||
import styles from './Account.module.css'
|
import styles from './Account.module.css'
|
||||||
|
import { useProfile } from '../../../../providers/Profile'
|
||||||
|
|
||||||
export default function Account({
|
export default function Account({
|
||||||
name,
|
|
||||||
image,
|
|
||||||
accountId
|
accountId
|
||||||
}: {
|
}: {
|
||||||
name: string
|
|
||||||
image: string
|
|
||||||
accountId: string
|
accountId: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { chainIds } = useUserPreferences()
|
const { chainIds } = useUserPreferences()
|
||||||
|
const { profile } = useProfile()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.account}>
|
<div className={styles.account}>
|
||||||
<figure className={styles.imageWrap}>
|
<figure className={styles.imageWrap}>
|
||||||
{image ? (
|
{profile?.image ? (
|
||||||
<img src={image} className={styles.image} width="96" height="96" />
|
<img
|
||||||
|
src={profile?.image}
|
||||||
|
className={styles.image}
|
||||||
|
width="96"
|
||||||
|
height="96"
|
||||||
|
/>
|
||||||
) : accountId ? (
|
) : accountId ? (
|
||||||
<Blockies accountId={accountId} className={styles.image} />
|
<Blockies accountId={accountId} className={styles.image} />
|
||||||
) : (
|
) : (
|
||||||
@ -37,7 +40,9 @@ export default function Account({
|
|||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className={styles.name}>{name || accountTruncate(accountId)}</h3>
|
<h3 className={styles.name}>
|
||||||
|
{profile?.name || accountTruncate(accountId)}
|
||||||
|
</h3>
|
||||||
{accountId && (
|
{accountId && (
|
||||||
<code className={styles.accountId}>
|
<code className={styles.accountId}>
|
||||||
{accountId} <Copy text={accountId} />
|
{accountId} <Copy text={accountId} />
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import classNames from 'classnames/bind'
|
import classNames from 'classnames/bind'
|
||||||
import { ProfileLink } from '../../../../models/Profile'
|
|
||||||
import { ReactComponent as External } from '../../../../images/external.svg'
|
import { ReactComponent as External } from '../../../../images/external.svg'
|
||||||
import styles from './PublisherLinks.module.css'
|
import styles from './PublisherLinks.module.css'
|
||||||
|
import { useProfile } from '../../../../providers/Profile'
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
const cx = classNames.bind(styles)
|
||||||
|
|
||||||
export default function PublisherLinks({
|
export default function PublisherLinks({
|
||||||
links,
|
|
||||||
className
|
className
|
||||||
}: {
|
}: {
|
||||||
links: ProfileLink[]
|
|
||||||
className: string
|
className: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
|
const { profile } = useProfile()
|
||||||
|
|
||||||
const styleClasses = cx({
|
const styleClasses = cx({
|
||||||
links: true,
|
links: true,
|
||||||
[className]: className
|
[className]: className
|
||||||
@ -21,7 +21,7 @@ export default function PublisherLinks({
|
|||||||
return (
|
return (
|
||||||
<div className={styleClasses}>
|
<div className={styleClasses}>
|
||||||
{' — '}
|
{' — '}
|
||||||
{links?.map((link: ProfileLink) => {
|
{profile?.links?.map((link) => {
|
||||||
const href =
|
const href =
|
||||||
link.name === 'Twitter'
|
link.name === 'Twitter'
|
||||||
? `https://twitter.com/${link.value}`
|
? `https://twitter.com/${link.value}`
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.stats {
|
.stats {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--spacer);
|
gap: var(--spacer);
|
||||||
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
|
||||||
margin-top: calc(var(--spacer) / 2);
|
margin-top: var(--spacer);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DDO, Logger } from '@oceanprotocol/lib'
|
import { Logger } from '@oceanprotocol/lib'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { ReactElement } from 'react-markdown'
|
import { ReactElement } from 'react-markdown'
|
||||||
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
||||||
@ -6,16 +6,27 @@ import {
|
|||||||
getAccountLiquidityInOwnAssets,
|
getAccountLiquidityInOwnAssets,
|
||||||
getAccountNumberOfOrders,
|
getAccountNumberOfOrders,
|
||||||
getAssetsBestPrices,
|
getAssetsBestPrices,
|
||||||
UserTVL
|
UserLiquidity,
|
||||||
|
calculateUserLiquidity
|
||||||
} from '../../../../utils/subgraph'
|
} from '../../../../utils/subgraph'
|
||||||
import Conversion from '../../../atoms/Price/Conversion'
|
import Conversion from '../../../atoms/Price/Conversion'
|
||||||
import NumberUnit from '../../../molecules/NumberUnit'
|
import NumberUnit from '../../../molecules/NumberUnit'
|
||||||
import styles from './Stats.module.css'
|
import styles from './Stats.module.css'
|
||||||
import {
|
import { useProfile } from '../../../../providers/Profile'
|
||||||
queryMetadata,
|
import { PoolShares_poolShares as PoolShare } from '../../../../@types/apollo/PoolShares'
|
||||||
transformChainIdsListToQuery
|
|
||||||
} from '../../../../utils/aquarius'
|
async function getPoolSharesLiquidity(
|
||||||
import axios from 'axios'
|
poolShares: PoolShare[]
|
||||||
|
): Promise<number> {
|
||||||
|
let totalLiquidity = 0
|
||||||
|
|
||||||
|
for (const poolShare of poolShares) {
|
||||||
|
const poolLiquidity = calculateUserLiquidity(poolShare)
|
||||||
|
totalLiquidity += poolLiquidity
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalLiquidity
|
||||||
|
}
|
||||||
|
|
||||||
export default function Stats({
|
export default function Stats({
|
||||||
accountId
|
accountId
|
||||||
@ -23,80 +34,91 @@ export default function Stats({
|
|||||||
accountId: string
|
accountId: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { chainIds } = useUserPreferences()
|
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 [sold, setSold] = useState(0)
|
||||||
const [tvl, setTvl] = useState<UserTVL>()
|
const [publisherLiquidity, setPublisherLiquidity] = useState<UserLiquidity>()
|
||||||
|
const [totalLiquidity, setTotalLiquidity] = useState(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!accountId) {
|
if (!accountId) {
|
||||||
setNumberOfAssets(0)
|
|
||||||
setSold(0)
|
setSold(0)
|
||||||
setTvl({ price: '0', oceanBalance: '0' })
|
setPublisherLiquidity({ price: '0', oceanBalance: '0' })
|
||||||
|
setTotalLiquidity(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPublished() {
|
async function getSales() {
|
||||||
const queryPublishedAssets = {
|
if (!assets) return
|
||||||
query: {
|
|
||||||
query_string: {
|
|
||||||
query: `(publicKey.owner:${accountId}) AND (${transformChainIdsListToQuery(
|
|
||||||
chainIds
|
|
||||||
)})`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const source = axios.CancelToken.source()
|
const nrOrders = await getAccountNumberOfOrders(assets, chainIds)
|
||||||
const result = await queryMetadata(queryPublishedAssets, source.token)
|
|
||||||
setPublishedAssets(result.results)
|
|
||||||
setNumberOfAssets(result.totalResults)
|
|
||||||
const nrOrders = await getAccountNumberOfOrders(
|
|
||||||
result.results,
|
|
||||||
chainIds
|
|
||||||
)
|
|
||||||
setSold(nrOrders)
|
setSold(nrOrders)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(error.message)
|
Logger.error(error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getPublished()
|
getSales()
|
||||||
}, [accountId, chainIds])
|
}, [accountId, chainIds, assets])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!publishedAssets) return
|
if (!assets || !accountId || !chainIds) return
|
||||||
|
|
||||||
async function getAccountTVL() {
|
async function getPublisherLiquidity() {
|
||||||
try {
|
try {
|
||||||
const accountPoolAdresses: string[] = []
|
const accountPoolAdresses: string[] = []
|
||||||
const assetsPrices = await getAssetsBestPrices(publishedAssets)
|
const assetsPrices = await getAssetsBestPrices(assets)
|
||||||
for (const priceInfo of assetsPrices) {
|
for (const priceInfo of assetsPrices) {
|
||||||
if (priceInfo.price.type === 'pool') {
|
if (priceInfo.price.type === 'pool') {
|
||||||
accountPoolAdresses.push(priceInfo.price.address.toLowerCase())
|
accountPoolAdresses.push(priceInfo.price.address.toLowerCase())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const userTvl: UserTVL = await getAccountLiquidityInOwnAssets(
|
const userLiquidity = await getAccountLiquidityInOwnAssets(
|
||||||
accountId,
|
accountId,
|
||||||
chainIds,
|
chainIds,
|
||||||
accountPoolAdresses
|
accountPoolAdresses
|
||||||
)
|
)
|
||||||
setTvl(userTvl)
|
setPublisherLiquidity(userLiquidity)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(error.message)
|
Logger.error(error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getAccountTVL()
|
getPublisherLiquidity()
|
||||||
}, [publishedAssets, accountId, chainIds])
|
}, [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 (
|
return (
|
||||||
<div className={styles.stats}>
|
<div className={styles.stats}>
|
||||||
<NumberUnit
|
<NumberUnit
|
||||||
label="Total Value Locked"
|
label="Liquidity in Own Assets"
|
||||||
value={<Conversion price={tvl?.price} hideApproximateSymbol />}
|
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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,16 @@
|
|||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useState } from 'react'
|
||||||
import get3BoxProfile from '../../../../utils/profile'
|
|
||||||
import { Profile } from '../../../../models/Profile'
|
|
||||||
import { accountTruncate } from '../../../../utils/web3'
|
|
||||||
import axios from 'axios'
|
|
||||||
import PublisherLinks from './PublisherLinks'
|
import PublisherLinks from './PublisherLinks'
|
||||||
import Markdown from '../../../atoms/Markdown'
|
import Markdown from '../../../atoms/Markdown'
|
||||||
import Stats from './Stats'
|
import Stats from './Stats'
|
||||||
import Account from './Account'
|
import Account from './Account'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
import { useProfile } from '../../../../providers/Profile'
|
||||||
|
|
||||||
const isDescriptionTextClamped = () => {
|
const isDescriptionTextClamped = () => {
|
||||||
const el = document.getElementById('description')
|
const el = document.getElementById('description')
|
||||||
if (el) return el.scrollHeight > el.clientHeight
|
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 }) => {
|
const Link3Box = ({ accountId, text }: { accountId: string; text: string }) => {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
@ -38,62 +28,22 @@ export default function AccountHeader({
|
|||||||
}: {
|
}: {
|
||||||
accountId: string
|
accountId: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const [profile, setProfile] = useState<Profile>({
|
const { profile } = useProfile()
|
||||||
name: accountTruncate(accountId),
|
|
||||||
image: null,
|
|
||||||
description: null,
|
|
||||||
links: null
|
|
||||||
})
|
|
||||||
const [isShowMore, setIsShowMore] = useState(false)
|
const [isShowMore, setIsShowMore] = useState(false)
|
||||||
|
|
||||||
const toogleShowMore = () => {
|
const toogleShowMore = () => {
|
||||||
setIsShowMore(!isShowMore)
|
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 (
|
return (
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
<div>
|
<div>
|
||||||
<Account
|
<Account accountId={accountId} />
|
||||||
accountId={accountId}
|
|
||||||
image={profile.image}
|
|
||||||
name={profile.name}
|
|
||||||
/>
|
|
||||||
<Stats accountId={accountId} />
|
<Stats accountId={accountId} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Markdown text={profile.description} className={styles.description} />
|
<Markdown text={profile?.description} className={styles.description} />
|
||||||
{isDescriptionTextClamped() ? (
|
{isDescriptionTextClamped() ? (
|
||||||
<span className={styles.more} onClick={toogleShowMore}>
|
<span className={styles.more} onClick={toogleShowMore}>
|
||||||
<Link3Box accountId={accountId} text="Read more on 3box" />
|
<Link3Box accountId={accountId} text="Read more on 3box" />
|
||||||
@ -101,11 +51,8 @@ export default function AccountHeader({
|
|||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
)}
|
)}
|
||||||
{profile.links?.length > 0 && (
|
{profile?.links?.length > 0 && (
|
||||||
<PublisherLinks
|
<PublisherLinks className={styles.publisherLinks} />
|
||||||
links={profile.links}
|
|
||||||
className={styles.publisherLinks}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.meta}>
|
<div className={styles.meta}>
|
||||||
|
@ -1,64 +1,33 @@
|
|||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import Table from '../../../atoms/Table'
|
import Table from '../../../atoms/Table'
|
||||||
import { gql } from 'urql'
|
|
||||||
import Time from '../../../atoms/Time'
|
import Time from '../../../atoms/Time'
|
||||||
import web3 from 'web3'
|
|
||||||
import AssetTitle from '../../../molecules/AssetListTitle'
|
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'
|
import NetworkName from '../../../atoms/NetworkName'
|
||||||
|
import { useProfile } from '../../../../providers/Profile'
|
||||||
const getTokenOrders = gql`
|
import { DownloadedAsset } from '../../../../utils/aquarius'
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
name: 'Data Set',
|
name: 'Data Set',
|
||||||
selector: function getAssetRow(row: DownloadedAssets) {
|
selector: function getAssetRow(row: DownloadedAsset) {
|
||||||
return <AssetTitle did={row.did} />
|
return <AssetTitle ddo={row.ddo} />
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Network',
|
name: 'Network',
|
||||||
selector: function getNetwork(row: DownloadedAssets) {
|
selector: function getNetwork(row: DownloadedAsset) {
|
||||||
return <NetworkName networkId={row.networkId} />
|
return <NetworkName networkId={row.networkId} />
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Datatoken',
|
name: 'Datatoken',
|
||||||
selector: function getTitleRow(row: DownloadedAssets) {
|
selector: function getTitleRow(row: DownloadedAsset) {
|
||||||
return row.dtSymbol
|
return row.dtSymbol
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Time',
|
name: 'Time',
|
||||||
selector: function getTimeRow(row: DownloadedAssets) {
|
selector: function getTimeRow(row: DownloadedAsset) {
|
||||||
return <Time date={row.timestamp.toString()} relative isUnix />
|
return <Time date={row.timestamp.toString()} relative isUnix />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,67 +38,14 @@ export default function ComputeDownloads({
|
|||||||
}: {
|
}: {
|
||||||
accountId: string
|
accountId: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { appConfig } = useSiteMetadata()
|
const { downloads, isDownloadsLoading } = useProfile()
|
||||||
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])
|
|
||||||
|
|
||||||
return accountId ? (
|
return accountId ? (
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={orders}
|
data={downloads}
|
||||||
paginationPerPage={10}
|
paginationPerPage={10}
|
||||||
isLoading={isLoading}
|
isLoading={isDownloadsLoading}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div>Please connect your Web3 wallet.</div>
|
<div>Please connect your Web3 wallet.</div>
|
||||||
|
@ -3,61 +3,31 @@ import Table from '../../../atoms/Table'
|
|||||||
import Conversion from '../../../atoms/Price/Conversion'
|
import Conversion from '../../../atoms/Price/Conversion'
|
||||||
import styles from './PoolShares.module.css'
|
import styles from './PoolShares.module.css'
|
||||||
import AssetTitle from '../../../molecules/AssetListTitle'
|
import AssetTitle from '../../../molecules/AssetListTitle'
|
||||||
import { gql } from 'urql'
|
|
||||||
import {
|
import {
|
||||||
PoolShares_poolShares as PoolShare,
|
PoolShares_poolShares as PoolShare,
|
||||||
PoolShares_poolShares_poolId_tokens as PoolSharePoolIdTokens
|
PoolShares_poolShares_poolId_tokens as PoolSharePoolIdTokens
|
||||||
} from '../../../../@types/apollo/PoolShares'
|
} from '../../../../@types/apollo/PoolShares'
|
||||||
import web3 from 'web3'
|
import web3 from 'web3'
|
||||||
import Token from '../../../organisms/AssetActions/Pool/Token'
|
import Token from '../../../organisms/AssetActions/Pool/Token'
|
||||||
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
import { calculateUserLiquidity } from '../../../../utils/subgraph'
|
||||||
import {
|
|
||||||
fetchDataForMultipleChains,
|
|
||||||
calculateUserLiquidity
|
|
||||||
} from '../../../../utils/subgraph'
|
|
||||||
import NetworkName from '../../../atoms/NetworkName'
|
import NetworkName from '../../../atoms/NetworkName'
|
||||||
import axios from 'axios'
|
import axios, { CancelToken } from 'axios'
|
||||||
import { retrieveDDO } from '../../../../utils/aquarius'
|
import { retrieveDDO } from '../../../../utils/aquarius'
|
||||||
import { isValidNumber } from '../../../../utils/numberValidations'
|
import { isValidNumber } from '../../../../utils/numberValidations'
|
||||||
import Decimal from 'decimal.js'
|
import Decimal from 'decimal.js'
|
||||||
|
import { useProfile } from '../../../../providers/Profile'
|
||||||
|
import { DDO } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||||
|
|
||||||
const REFETCH_INTERVAL = 20000
|
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 {
|
interface Asset {
|
||||||
userLiquidity: number
|
userLiquidity: number
|
||||||
poolShare: PoolShare
|
poolShare: PoolShare
|
||||||
networkId: number
|
networkId: number
|
||||||
createTime: number
|
createTime: number
|
||||||
|
ddo: DDO
|
||||||
}
|
}
|
||||||
|
|
||||||
function findTokenByType(tokens: PoolSharePoolIdTokens[], type: string) {
|
function findTokenByType(tokens: PoolSharePoolIdTokens[], type: string) {
|
||||||
@ -126,10 +96,7 @@ const columns = [
|
|||||||
{
|
{
|
||||||
name: 'Data Set',
|
name: 'Data Set',
|
||||||
selector: function getAssetRow(row: Asset) {
|
selector: function getAssetRow(row: Asset) {
|
||||||
const did = web3.utils
|
return <AssetTitle ddo={row.ddo} />
|
||||||
.toChecksumAddress(row.poolShare.poolId.datatokenAddress)
|
|
||||||
.replace('0x', 'did:op:')
|
|
||||||
return <AssetTitle did={did} />
|
|
||||||
},
|
},
|
||||||
grow: 2
|
grow: 2
|
||||||
},
|
},
|
||||||
@ -161,38 +128,27 @@ const columns = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
async function getPoolSharesData(accountId: string, chainIds: number[]) {
|
async function getPoolSharesAssets(
|
||||||
const variables = { user: accountId?.toLowerCase() }
|
data: PoolShare[],
|
||||||
const data: PoolShare[] = []
|
cancelToken: CancelToken
|
||||||
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[]) {
|
|
||||||
const assetList: Asset[] = []
|
const assetList: Asset[] = []
|
||||||
const source = axios.CancelToken.source()
|
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
const did = web3.utils
|
const did = web3.utils
|
||||||
.toChecksumAddress(data[i].poolId.datatokenAddress)
|
.toChecksumAddress(data[i].poolId.datatokenAddress)
|
||||||
.replace('0x', 'did:op:')
|
.replace('0x', 'did:op:')
|
||||||
const ddo = await retrieveDDO(did, source.token)
|
const ddo = await retrieveDDO(did, cancelToken)
|
||||||
const userLiquidity = calculateUserLiquidity(data[i])
|
const userLiquidity = calculateUserLiquidity(data[i])
|
||||||
assetList.push({
|
|
||||||
poolShare: data[i],
|
ddo &&
|
||||||
userLiquidity: userLiquidity,
|
assetList.push({
|
||||||
networkId: ddo?.chainId,
|
poolShare: data[i],
|
||||||
createTime: data[i].poolId.createTime
|
userLiquidity: userLiquidity,
|
||||||
})
|
networkId: ddo?.chainId,
|
||||||
|
createTime: data[i].poolId.createTime,
|
||||||
|
ddo
|
||||||
|
})
|
||||||
}
|
}
|
||||||
const assets = assetList.sort((a, b) => b.createTime - a.createTime)
|
const assets = assetList.sort((a, b) => b.createTime - a.createTime)
|
||||||
return assets
|
return assets
|
||||||
@ -203,33 +159,36 @@ export default function PoolShares({
|
|||||||
}: {
|
}: {
|
||||||
accountId: string
|
accountId: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
|
const { poolShares, isPoolSharesLoading } = useProfile()
|
||||||
const [assets, setAssets] = useState<Asset[]>()
|
const [assets, setAssets] = useState<Asset[]>()
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
const [dataFetchInterval, setDataFetchInterval] = useState<NodeJS.Timeout>()
|
const [dataFetchInterval, setDataFetchInterval] = useState<NodeJS.Timeout>()
|
||||||
const { chainIds } = useUserPreferences()
|
|
||||||
|
|
||||||
const fetchPoolSharesData = useCallback(async () => {
|
const fetchPoolSharesAssets = useCallback(
|
||||||
try {
|
async (cancelToken: CancelToken) => {
|
||||||
const data = await getPoolSharesData(accountId, chainIds)
|
if (!poolShares || isPoolSharesLoading) return
|
||||||
const newAssets = await getPoolSharesAssets(data)
|
|
||||||
|
|
||||||
if (JSON.stringify(assets) !== JSON.stringify(newAssets)) {
|
try {
|
||||||
setAssets(newAssets)
|
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)
|
[poolShares, isPoolSharesLoading]
|
||||||
}
|
)
|
||||||
}, [accountId, assets, chainIds])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const cancelTokenSource = axios.CancelToken.source()
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
await fetchPoolSharesData()
|
await fetchPoolSharesAssets(cancelTokenSource.token)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|
||||||
if (dataFetchInterval) return
|
if (dataFetchInterval) return
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
await fetchPoolSharesData()
|
await fetchPoolSharesAssets(cancelTokenSource.token)
|
||||||
}, REFETCH_INTERVAL)
|
}, REFETCH_INTERVAL)
|
||||||
setDataFetchInterval(interval)
|
setDataFetchInterval(interval)
|
||||||
}
|
}
|
||||||
@ -237,8 +196,9 @@ export default function PoolShares({
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(dataFetchInterval)
|
clearInterval(dataFetchInterval)
|
||||||
|
cancelTokenSource.cancel()
|
||||||
}
|
}
|
||||||
}, [dataFetchInterval, fetchPoolSharesData])
|
}, [dataFetchInterval, fetchPoolSharesAssets])
|
||||||
|
|
||||||
return accountId ? (
|
return accountId ? (
|
||||||
<Table
|
<Table
|
||||||
|
@ -2,15 +2,12 @@ import { Logger } from '@oceanprotocol/lib'
|
|||||||
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import AssetList from '../../../organisms/AssetList'
|
import AssetList from '../../../organisms/AssetList'
|
||||||
import axios from 'axios'
|
import { getPublishedAssets } from '../../../../utils/aquarius'
|
||||||
import {
|
|
||||||
queryMetadata,
|
|
||||||
transformChainIdsListToQuery
|
|
||||||
} from '../../../../utils/aquarius'
|
|
||||||
import Filters from '../../../templates/Search/Filters'
|
import Filters from '../../../templates/Search/Filters'
|
||||||
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
|
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
|
||||||
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
||||||
import styles from './PublishedList.module.css'
|
import styles from './PublishedList.module.css'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
export default function PublishedList({
|
export default function PublishedList({
|
||||||
accountId
|
accountId
|
||||||
@ -23,30 +20,23 @@ export default function PublishedList({
|
|||||||
const [queryResult, setQueryResult] = useState<QueryResult>()
|
const [queryResult, setQueryResult] = useState<QueryResult>()
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [page, setPage] = useState<number>(1)
|
const [page, setPage] = useState<number>(1)
|
||||||
const [service, setServiceType] = useState<string>()
|
const [service, setServiceType] = useState('dataset OR algorithm')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getPublished() {
|
if (!accountId) return
|
||||||
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()
|
|
||||||
|
|
||||||
|
const cancelTokenSource = axios.CancelToken.source()
|
||||||
|
|
||||||
|
async function getPublished() {
|
||||||
|
try {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const result = await queryMetadata(queryPublishedAssets, source.token)
|
const result = await getPublishedAssets(
|
||||||
|
accountId,
|
||||||
|
chainIds,
|
||||||
|
cancelTokenSource.token,
|
||||||
|
page,
|
||||||
|
service
|
||||||
|
)
|
||||||
setQueryResult(result)
|
setQueryResult(result)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(error.message)
|
Logger.error(error.message)
|
||||||
@ -55,6 +45,10 @@ export default function PublishedList({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
getPublished()
|
getPublished()
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelTokenSource.cancel()
|
||||||
|
}
|
||||||
}, [accountId, page, appConfig.metadataCacheUri, chainIds, service])
|
}, [accountId, page, appConfig.metadataCacheUri, chainIds, service])
|
||||||
|
|
||||||
return accountId ? (
|
return accountId ? (
|
||||||
@ -75,6 +69,7 @@ export default function PublishedList({
|
|||||||
setPage(newPage)
|
setPage(newPage)
|
||||||
}}
|
}}
|
||||||
className={styles.assets}
|
className={styles.assets}
|
||||||
|
noPublisher
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
@ -4,6 +4,7 @@ import { graphql, PageProps } from 'gatsby'
|
|||||||
import ProfilePage from '../../components/pages/Profile'
|
import ProfilePage from '../../components/pages/Profile'
|
||||||
import { accountTruncate } from '../../utils/web3'
|
import { accountTruncate } from '../../utils/web3'
|
||||||
import { useWeb3 } from '../../providers/Web3'
|
import { useWeb3 } from '../../providers/Web3'
|
||||||
|
import ProfileProvider from '../../providers/Profile'
|
||||||
|
|
||||||
export default function PageGatsbyProfile(props: PageProps): ReactElement {
|
export default function PageGatsbyProfile(props: PageProps): ReactElement {
|
||||||
const { accountId } = useWeb3()
|
const { accountId } = useWeb3()
|
||||||
@ -18,7 +19,9 @@ export default function PageGatsbyProfile(props: PageProps): ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Page uri={props.uri} title={accountTruncate(finalAccountId)} noPageHeader>
|
<Page uri={props.uri} title={accountTruncate(finalAccountId)} noPageHeader>
|
||||||
<ProfilePage accountId={finalAccountId} />
|
<ProfileProvider accountId={finalAccountId}>
|
||||||
|
<ProfilePage accountId={finalAccountId} />
|
||||||
|
</ProfileProvider>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
291
src/providers/Profile.tsx
Normal file
291
src/providers/Profile.tsx
Normal 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
|
@ -12,7 +12,16 @@ import {
|
|||||||
import { AssetSelectionAsset } from '../components/molecules/FormFields/AssetSelection'
|
import { AssetSelectionAsset } from '../components/molecules/FormFields/AssetSelection'
|
||||||
import { PriceList, getAssetsPriceList } from './subgraph'
|
import { PriceList, getAssetsPriceList } from './subgraph'
|
||||||
import axios, { CancelToken, AxiosResponse } from 'axios'
|
import axios, { CancelToken, AxiosResponse } from 'axios'
|
||||||
|
import { OrdersData_tokenOrders as OrdersData } from '../@types/apollo/OrdersData'
|
||||||
import { metadataCacheUri } from '../../app.config'
|
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) {
|
function getQueryForAlgorithmDatasets(algorithmDid: string, chainId?: number) {
|
||||||
return {
|
return {
|
||||||
@ -66,7 +75,8 @@ export async function queryMetadata(
|
|||||||
try {
|
try {
|
||||||
const response: AxiosResponse<any> = await axios.post(
|
const response: AxiosResponse<any> = await axios.post(
|
||||||
`${metadataCacheUri}/api/v1/aquarius/assets/ddo/query`,
|
`${metadataCacheUri}/api/v1/aquarius/assets/ddo/query`,
|
||||||
{ ...query, cancelToken }
|
{ ...query },
|
||||||
|
{ cancelToken }
|
||||||
)
|
)
|
||||||
if (!response || response.status !== 200 || !response.data) return
|
if (!response || response.status !== 200 || !response.data) return
|
||||||
return transformQueryResult(response.data)
|
return transformQueryResult(response.data)
|
||||||
@ -108,10 +118,8 @@ export async function getAssetsNames(
|
|||||||
try {
|
try {
|
||||||
const response: AxiosResponse<Record<string, string>> = await axios.post(
|
const response: AxiosResponse<Record<string, string>> = await axios.post(
|
||||||
`${metadataCacheUri}/api/v1/aquarius/assets/names`,
|
`${metadataCacheUri}/api/v1/aquarius/assets/names`,
|
||||||
{
|
{ didList },
|
||||||
didList,
|
{ cancelToken }
|
||||||
cancelToken
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
if (!response || response.status !== 200 || !response.data) return
|
if (!response || response.status !== 200 || !response.data) return
|
||||||
return response.data
|
return response.data
|
||||||
@ -204,3 +212,122 @@ export async function getAlgorithmDatasetsForCompute(
|
|||||||
)
|
)
|
||||||
return datasets
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { gql, OperationResult, TypedDocumentNode, OperationContext } from 'urql'
|
import { gql, OperationResult, TypedDocumentNode, OperationContext } from 'urql'
|
||||||
import { DDO } from '@oceanprotocol/lib'
|
import { DDO, Logger } from '@oceanprotocol/lib'
|
||||||
import { getUrqlClientInstance } from '../providers/UrqlProvider'
|
import { getUrqlClientInstance } from '../providers/UrqlProvider'
|
||||||
import { getOceanConfig } from './ocean'
|
import { getOceanConfig } from './ocean'
|
||||||
import web3 from 'web3'
|
import web3 from 'web3'
|
||||||
@ -25,8 +25,9 @@ import {
|
|||||||
PoolShares_poolShares as PoolShare
|
PoolShares_poolShares as PoolShare
|
||||||
} from '../@types/apollo/PoolShares'
|
} from '../@types/apollo/PoolShares'
|
||||||
import { BestPrice } from '../models/BestPrice'
|
import { BestPrice } from '../models/BestPrice'
|
||||||
|
import { OrdersData_tokenOrders as OrdersData } from '../@types/apollo/OrdersData'
|
||||||
|
|
||||||
export interface UserTVL {
|
export interface UserLiquidity {
|
||||||
price: string
|
price: string
|
||||||
oceanBalance: 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 {
|
export function getSubgraphUri(chainId: number): string {
|
||||||
const config = getOceanConfig(chainId)
|
const config = getOceanConfig(chainId)
|
||||||
return config.subgraphUri
|
return config.subgraphUri
|
||||||
@ -604,7 +651,7 @@ export async function getAccountLiquidityInOwnAssets(
|
|||||||
accountId: string,
|
accountId: string,
|
||||||
chainIds: number[],
|
chainIds: number[],
|
||||||
pools: string[]
|
pools: string[]
|
||||||
): Promise<UserTVL> {
|
): Promise<UserLiquidity> {
|
||||||
const queryVariables = {
|
const queryVariables = {
|
||||||
user: accountId.toLowerCase(),
|
user: accountId.toLowerCase(),
|
||||||
pools: pools
|
pools: pools
|
||||||
@ -630,3 +677,48 @@ export async function getAccountLiquidityInOwnAssets(
|
|||||||
oceanBalance: totalOceanLiquidity.toString()
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user