From 79715df43546f282b6b4733dea8d10ef6cf8189a Mon Sep 17 00:00:00 2001 From: mihaisc Date: Fri, 22 Jan 2021 19:05:02 +0200 Subject: [PATCH] Pool tx history (#307) * graphql Signed-off-by: mihaisc * ignore generated Signed-off-by: mihaisc * delete generated Signed-off-by: mihaisc * fix travis Signed-off-by: mihaisc * fix travis Signed-off-by: mihaisc * fix fetch Signed-off-by: mihaisc * fix travis Signed-off-by: mihaisc * fix fetch Signed-off-by: mihaisc * update readme Signed-off-by: mihaisc * pool creator liquidit& statistics Signed-off-by: mihaisc * graph with the graph Signed-off-by: mihaisc * cleanup Signed-off-by: mihaisc * fix query Signed-off-by: mihaisc * update poll interval Signed-off-by: mihaisc * update graph url Signed-off-by: mihaisc * ocean bump Signed-off-by: mihaisc * run apollo codegen before starting gatsby * put back graph loading state * typing fix * graph tweak, add error state * readme update * remove unused functions, move graph provider Signed-off-by: mihaisc * fix package-lock * fix graph when switching tabs * generate apollo files into one folder * fix loading Signed-off-by: mihaisc * graph query Signed-off-by: mihaisc * tx query Signed-off-by: mihaisc * fix titles Signed-off-by: mihaisc * fix text issues Signed-off-by: mihaisc * fix query Signed-off-by: mihaisc * local pagination Signed-off-by: mihaisc * return refreshInterval to 10 sec Signed-off-by: mihaisc * add Signed-off-by: mihaisc * fix data set column Signed-off-by: mihaisc Co-authored-by: Matthias Kretschmann --- package.json | 3 +- src/components/App.tsx | 3 +- src/components/molecules/AssetListTitle.tsx | 8 +- .../molecules/PoolTransactions.module.css | 4 + src/components/molecules/PoolTransactions.tsx | 216 +++++++++++------- .../AssetActions/Pool/Transactions.tsx | 13 +- .../organisms/AssetActions/Pool/index.tsx | 2 +- src/providers/Asset.tsx | 2 +- 8 files changed, 152 insertions(+), 99 deletions(-) diff --git a/package.json b/package.json index 8268406d9..096c68933 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,8 @@ "slugify": "^1.4.6", "swr": "^0.3.11", "use-dark-mode": "^2.3.1", - "yup": "^0.32.8" + "web3": "^1.3.1", + "yup": "^0.32.6" }, "devDependencies": { "@babel/core": "^7.12.10", diff --git a/src/components/App.tsx b/src/components/App.tsx index fdd5ccdc0..830a679e7 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -15,6 +15,7 @@ import { NormalizedCacheObject } from '@apollo/client' import fetch from 'cross-fetch' +import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper' const contentQuery = graphql` query AppQuery { purgatory: allFile(filter: { relativePath: { eq: "purgatory.json" } }) { @@ -54,7 +55,7 @@ export default function App({ const newClient = new ApolloClient({ link: new HttpLink({ uri: `${ - (config as any).subgraphUri + (config as ConfigHelperConfig).subgraphUri }/subgraphs/name/oceanprotocol/ocean-subgraph`, fetch }), diff --git a/src/components/molecules/AssetListTitle.tsx b/src/components/molecules/AssetListTitle.tsx index 7c4084e5a..3eec652ed 100644 --- a/src/components/molecules/AssetListTitle.tsx +++ b/src/components/molecules/AssetListTitle.tsx @@ -19,8 +19,7 @@ export default function AssetListTitle({ const [assetTitle, setAssetTitle] = useState(title) useEffect(() => { - if (assetTitle || !config?.metadataCacheUri) return - + if (title || !config?.metadataCacheUri) return if (ddo) { const { attributes } = ddo.findServiceByType('metadata') setAssetTitle(attributes.main.name) @@ -31,6 +30,8 @@ export default function AssetListTitle({ async function getDDO() { const ddo = await retrieveDDO(did, config.metadataCacheUri, source.token) + + if (!ddo) return const { attributes } = ddo.findServiceByType('metadata') setAssetTitle(attributes.main.name) } @@ -38,9 +39,10 @@ export default function AssetListTitle({ !ddo && did && getDDO() return () => { + console.log('canceled?') source.cancel() } - }, [assetTitle, config?.metadataCacheUri, ddo, did]) + }, [assetTitle, config?.metadataCacheUri, ddo, did, title]) return (

diff --git a/src/components/molecules/PoolTransactions.module.css b/src/components/molecules/PoolTransactions.module.css index 197568bba..12bb497f5 100644 --- a/src/components/molecules/PoolTransactions.module.css +++ b/src/components/molecules/PoolTransactions.module.css @@ -1,3 +1,7 @@ .time { color: var(--color-secondary); } + +.titleText { + white-space: pre; +} diff --git a/src/components/molecules/PoolTransactions.tsx b/src/components/molecules/PoolTransactions.tsx index e3d72fd9a..afdda9e69 100644 --- a/src/components/molecules/PoolTransactions.tsx +++ b/src/components/molecules/PoolTransactions.tsx @@ -1,4 +1,3 @@ -import { PoolTransaction } from '@oceanprotocol/lib/dist/node/balancer/OceanPool' import { useOcean } from '@oceanprotocol/react' import React, { ReactElement, useEffect, useState } from 'react' import EtherscanLink from '../atoms/EtherscanLink' @@ -9,41 +8,61 @@ import styles from './PoolTransactions.module.css' import { useUserPreferences } from '../../providers/UserPreferences' import { Ocean } from '@oceanprotocol/lib' import { formatPrice } from '../atoms/Price/PriceUnit' -import { gql } from '@apollo/client' +import { gql, useQuery } from '@apollo/client' +import { + TransactionHistory, + TransactionHistoryPoolTransactions +} from '../../@types/apollo/TransactionHistory' -const txHistoryQuery = gql` - query Pool($id: ID!, $user: String!) { - pool(id: $id) { - transactions(orderBy: timestamp, where: { userAddressStr: $user }) { - tx - timestamp - spotPrice - event - sharesTransferAmount - tokens { - type - value - tokenAddress - poolToken { - tokenId { - symbol - name - } - tokenAddress - } - } +import web3 from 'web3' + +const txHistoryQueryByPool = gql` + query TransactionHistoryByPool($user: String, $pool: String) { + poolTransactions( + orderBy: timestamp + orderDirection: desc + where: { userAddress: $user, poolAddress: $pool } + first: 1000 + ) { + tx + event + timestamp + poolAddress { + datatokenAddress + } + tokens { + value + type + tokenAddress } } } ` - -async function getSymbol( - ocean: Ocean, - tokenAddress: string, - oceanTokenAddress: string -) { +const txHistoryQuery = gql` + query TransactionHistory($user: String) { + poolTransactions( + orderBy: timestamp + orderDirection: desc + where: { userAddress: $user } + first: 1000 + ) { + tx + event + timestamp + poolAddress { + datatokenAddress + } + tokens { + value + type + tokenAddress + } + } + } +` +async function getSymbol(ocean: Ocean, tokenAddress: string) { const symbol = - oceanTokenAddress === tokenAddress + ocean.pool.oceanAddress.toLowerCase() === tokenAddress.toLowerCase() ? 'OCEAN' : await ocean.datatokens.getSymbol(tokenAddress) @@ -52,52 +71,81 @@ async function getSymbol( async function getTitle( ocean: Ocean, - row: PoolTransaction, - locale: string, - oceanTokenAddress: string + row: TransactionHistoryPoolTransactions, + locale: string ) { - const addRemoveSymbol = await getSymbol( - ocean, - row.tokenIn || row.tokenOut, - oceanTokenAddress - ) + let title = '' - const title = - row.type === 'join' - ? `Add ${formatPrice(row.tokenAmountIn, locale)} ${addRemoveSymbol}` - : row.type === 'exit' - ? `Remove ${formatPrice(row.tokenAmountOut, locale)} ${addRemoveSymbol}` - : `Swap ${formatPrice(row.tokenAmountIn, locale)} ${await getSymbol( - ocean, - row.tokenIn, - oceanTokenAddress - )} for ${formatPrice(row.tokenAmountOut, locale)} ${await getSymbol( - ocean, - row.tokenOut, - oceanTokenAddress - )}` + switch (row.event) { + case 'swap': { + const inToken = row.tokens.filter((x) => x.type === 'in')[0] + const inTokenSymbol = await getSymbol(ocean, inToken.tokenAddress) + const outToken = row.tokens.filter((x) => x.type === 'out')[0] + const outTokenSymbol = await getSymbol(ocean, outToken.tokenAddress) + title += `Swap ${formatPrice( + Math.abs(inToken.value).toString(), + locale + )}${inTokenSymbol} for ${formatPrice( + Math.abs(outToken.value).toString(), + locale + )}${outTokenSymbol}` + break + } + case 'setup': { + const firstToken = row.tokens.filter( + (x) => + x.tokenAddress.toLowerCase() === ocean.pool.oceanAddress.toLowerCase() + )[0] + const firstTokenSymbol = await getSymbol(ocean, firstToken.tokenAddress) + const secondToken = row.tokens.filter( + (x) => + x.tokenAddress.toLowerCase() !== ocean.pool.oceanAddress.toLowerCase() + )[0] + const secondTokenSymbol = await getSymbol(ocean, secondToken.tokenAddress) + title += `Create pool with ${formatPrice( + Math.abs(firstToken.value).toString(), + locale + )}${firstTokenSymbol} and ${formatPrice( + Math.abs(secondToken.value).toString(), + locale + )}${secondTokenSymbol}` + break + } + case 'join': + case 'exit': { + for (let i = 0; i < row.tokens.length; i++) { + const tokenSymbol = await getSymbol(ocean, row.tokens[i].tokenAddress) + if (i > 0) title += '\n' + title += `${row.event === 'join' ? 'Add' : 'Remove'} ${formatPrice( + Math.abs(row.tokens[i].value).toString(), + locale + )}${tokenSymbol}` + } + break + } + } return title } -function Title({ row }: { row: PoolTransaction }) { - const { ocean, networkId, config } = useOcean() +function Title({ row }: { row: TransactionHistoryPoolTransactions }) { + const { ocean, networkId } = useOcean() const [title, setTitle] = useState() const { locale } = useUserPreferences() useEffect(() => { - if (!ocean || !locale || !row || !config?.oceanTokenAddress) return + if (!ocean || !locale || !row) return async function init() { - const title = await getTitle(ocean, row, locale, config.oceanTokenAddress) + const title = await getTitle(ocean, row, locale) setTitle(title) } init() - }, [ocean, row, locale, config?.oceanTokenAddress]) + }, [ocean, row, locale]) return title ? ( - - {title} + + {title} ) : null } @@ -106,21 +154,24 @@ function getColumns(minimal?: boolean) { return [ { name: 'Title', - selector: function getTitleRow(row: PoolTransaction) { + selector: function getTitleRow(row: TransactionHistoryPoolTransactions) { return } }, { name: 'Data Set', - selector: function getAssetRow(row: PoolTransaction) { - const did = row.dtAddress.replace('0x', 'did:op:') + selector: function getAssetRow(row: TransactionHistoryPoolTransactions) { + const did = web3.utils + .toChecksumAddress(row.poolAddress.datatokenAddress) + .replace('0x', 'did:op:') + return <AssetTitle did={did} /> }, omit: minimal }, { name: 'Time', - selector: function getTimeRow(row: PoolTransaction) { + selector: function getTimeRow(row: TransactionHistoryPoolTransactions) { return ( <Time className={styles.time} @@ -142,35 +193,30 @@ export default function PoolTransactions({ poolAddress?: string minimal?: boolean }): ReactElement { - const { ocean, accountId } = useOcean() - const [logs, setLogs] = useState<PoolTransaction[]>() - const [isLoading, setIsLoading] = useState(false) + const { accountId } = useOcean() + const [logs, setLogs] = useState<TransactionHistoryPoolTransactions[]>() + + const { data, loading } = useQuery<TransactionHistory>( + poolAddress ? txHistoryQueryByPool : txHistoryQuery, + { + variables: { + user: accountId?.toLowerCase(), + pool: poolAddress?.toLowerCase() + }, + pollInterval: 20000 + } + ) useEffect(() => { - async function getLogs() { - if (!ocean || !accountId) return - - setIsLoading(true) - const logs = poolAddress - ? await ocean.pool.getPoolLogs(poolAddress, 0, accountId) - : await ocean.pool.getAllPoolLogs(accountId) - // sort logs by date, newest first - const logsSorted = logs.sort((a, b) => { - if (a.timestamp > b.timestamp) return -1 - if (a.timestamp < b.timestamp) return 1 - return 0 - }) - setLogs(logsSorted) - setIsLoading(false) - } - getLogs() - }, [ocean, accountId, poolAddress]) + if (!data) return + setLogs(data.poolTransactions) + }, [data, loading]) return ( <Table columns={getColumns(minimal)} data={logs} - isLoading={isLoading} + isLoading={loading} noTableHead={minimal} dense={minimal} pagination={minimal ? logs?.length >= 4 : logs?.length >= 9} diff --git a/src/components/organisms/AssetActions/Pool/Transactions.tsx b/src/components/organisms/AssetActions/Pool/Transactions.tsx index bc3c2370a..b4973822f 100644 --- a/src/components/organisms/AssetActions/Pool/Transactions.tsx +++ b/src/components/organisms/AssetActions/Pool/Transactions.tsx @@ -3,14 +3,11 @@ import Button from '../../../atoms/Button' import PoolTransactions from '../../../molecules/PoolTransactions' import styles from './Transactions.module.css' import { ReactComponent as Caret } from '../../../../images/caret.svg' +import { useAsset } from '../../../../providers/Asset' -export default function Transactions({ - poolAddress -}: { - poolAddress: string -}): ReactElement { +export default function Transactions(): ReactElement { const [open, setOpen] = useState(false) - + const { ddo } = useAsset() function handleClick() { setOpen(!open) } @@ -31,7 +28,9 @@ export default function Transactions({ {open ? 'Hide' : 'Show'} <Caret /> </Button> </h3> - {open === true && <PoolTransactions poolAddress={poolAddress} minimal />} + {open === true && ( + <PoolTransactions poolAddress={ddo.price?.address} minimal /> + )} </div> ) } diff --git a/src/components/organisms/AssetActions/Pool/index.tsx b/src/components/organisms/AssetActions/Pool/index.tsx index 85ecd6e80..7712b8edd 100644 --- a/src/components/organisms/AssetActions/Pool/index.tsx +++ b/src/components/organisms/AssetActions/Pool/index.tsx @@ -343,7 +343,7 @@ export default function Pool(): ReactElement { )} </div> - {accountId && <Transactions poolAddress={price?.address} />} + {accountId && <Transactions />} </> )} </> diff --git a/src/providers/Asset.tsx b/src/providers/Asset.tsx index 5288c5afb..aa8629465 100644 --- a/src/providers/Asset.tsx +++ b/src/providers/Asset.tsx @@ -68,7 +68,7 @@ function AssetProvider({ ddo.price.address ) setPrice(newPrice) - Logger.log(`Refreshed asset price: ${newPrice?.value}`) + Logger.log(`Refreshed asset price: ${newPrice?.value}`, newPrice) }, [ocean, config, ddo, networkId, status]) const fetchDdo = async (token?: CancelToken) => {