Skip to content

Commit

Permalink
feat: transaction history table (#1973)
Browse files Browse the repository at this point in the history
* feat: transaction history table

* wire up download button

* use prod env temporarily

* fix amount formatting

* use asset name, link asset, right align
  • Loading branch information
JP authored Feb 22, 2024
1 parent 43b90a1 commit badf937
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 52 deletions.
30 changes: 15 additions & 15 deletions centrifuge-app/.env-config/.env.development
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
REACT_APP_COLLATOR_WSS_URL=wss://fullnode.development.cntrfg.com
REACT_APP_DEFAULT_NODE_URL=https://pod-development.k-f.dev
REACT_APP_COLLATOR_WSS_URL=wss://fullnode.parachain.centrifuge.io
REACT_APP_DEFAULT_NODE_URL=''
REACT_APP_DEFAULT_UNLIST_POOLS=false
REACT_APP_FAUCET_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/faucet-api-dev
REACT_APP_FAUCET_URL=
REACT_APP_IPFS_GATEWAY=https://centrifuge.mypinata.cloud/
REACT_APP_IS_DEMO=false
REACT_APP_IS_DEMO=
REACT_APP_NETWORK=centrifuge
REACT_APP_ONBOARDING_API_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/onboarding-api-dev
REACT_APP_PINNING_API_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/pinning-api-dev
REACT_APP_POOL_CREATION_TYPE=immediate
REACT_APP_RELAY_WSS_URL=wss://fullnode-relay.development.cntrfg.com
REACT_APP_SUBQUERY_URL=https://api.subquery.network/sq/centrifuge/pools-development
REACT_APP_ONBOARDING_API_URL=https://europe-central2-centrifuge-production-x.cloudfunctions.net/onboarding-api-production
REACT_APP_PINNING_API_URL=https://europe-central2-centrifuge-production-x.cloudfunctions.net/pinning-api-production
REACT_APP_POOL_CREATION_TYPE=propose
REACT_APP_RELAY_WSS_URL=wss://rpc.polkadot.io
REACT_APP_SUBQUERY_URL=https://api.subquery.network/sq/centrifuge/pools
REACT_APP_SUBSCAN_URL=https://centrifuge.subscan.io
REACT_APP_TINLAKE_NETWORK=goerli
REACT_APP_TINLAKE_NETWORK=mainnet
REACT_APP_INFURA_KEY=bf808e7d3d924fbeb74672d9341d0550
REACT_APP_ONFINALITY_KEY=0e1c049f-d876-4e77-a45f-b5afdf5739b2
REACT_APP_WHITELISTED_ACCOUNTS=
REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/prod/gn
REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-kovan-staging/latest.json
REACT_APP_ONFINALITY_KEY=7e8caebc-b052-402d-87a4-e990b67ed612
REACT_APP_WHITELISTED_ACCOUNTS=''
REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-mainnet-production/latest.json
REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kALJqPUHFzDR2VkoQYWefPQyzjGzKznNny2smXGQpSf3aMw19
REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a
REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kAJ27w29x7gHM75xajP2yXVLjVBaKmmUTxHwgRuCoAcWaoEiz
REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/prod/gn
188 changes: 183 additions & 5 deletions centrifuge-app/src/components/PoolOverview/TransactionHistory.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,187 @@
import { Card } from '@centrifuge/fabric'
import { AssetTransaction, CurrencyBalance } from '@centrifuge/centrifuge-js'
import { AnchorButton, IconDownload, IconExternalLink, Shelf, Stack, StatusChip, Text } from '@centrifuge/fabric'
import BN from 'bn.js'
import { nftMetadataSchema } from '../../schemas'
import { formatDate } from '../../utils/date'
import { formatBalance } from '../../utils/formatting'
import { getCSVDownloadUrl } from '../../utils/getCSVDownloadUrl'
import { useMetadataMulti } from '../../utils/useMetadata'
import { useAssetTransactions } from '../../utils/usePools'
import { DataTable, SortableTableHeader } from '../DataTable'
import { AnchorTextLink } from '../TextLink'

type Row = {
type: string
transactionDate: string
assetId: string
amount: CurrencyBalance | undefined
hash: string
assetName: string
}

const getTransactionTypeStatus = (type: string) => {
if (type === 'Principal payment' || type === 'Repaid') return 'warning'
if (type === 'Interest') return 'ok'
return 'default'
}

export const columns = [
{
align: 'left',
header: 'Type',
cell: ({ type }: Row) => <StatusChip status={getTransactionTypeStatus(type)}>{type}</StatusChip>,
},
{
align: 'left',
header: <SortableTableHeader label="Transaction date" />,
cell: ({ transactionDate }: Row) => (
<Text as="span" variant="body3">
{formatDate(transactionDate)}
</Text>
),
sortKey: 'transactionDate',
},
{
align: 'left',
header: 'Asset name',
cell: ({ assetId, assetName }: Row) => {
const [poolId, id] = assetId.split('-')
return (
<Text as="span" variant="body3">
<AnchorTextLink href={`/pools/${poolId}/assets/${id}`}>{assetName}</AnchorTextLink>
</Text>
)
},
},
{
align: 'right',
header: <SortableTableHeader label="Amount" />,
cell: ({ amount }: Row) => (
<Text as="span" variant="body3">
{amount ? formatBalance(amount, 'USD', 2, 2) : ''}
</Text>
),
sortKey: 'amount',
},
{
align: 'right',
header: 'View transaction',
cell: ({ hash }: Row) => {
return (
<Stack
as="a"
href={`${import.meta.env.REACT_APP_SUBSCAN_URL}/extrinsic/${hash}`}
target="_blank"
rel="noopener noreferrer"
aria-label="Transaction on Subscan.io"
>
<IconExternalLink size="iconSmall" color="textPrimary" />
</Stack>
)
},
},
]

export const TransactionHistory = ({ poolId, preview = true }: { poolId: string; preview?: boolean }) => {
const transactions = useAssetTransactions(poolId, new Date(0))

const assetMetadata = useMetadataMulti(
[...new Set(transactions?.map((transaction) => transaction.asset.metadata))] || [],
nftMetadataSchema
)

const getLabelAndAmount = (transaction: AssetTransaction) => {
if (transaction.type === 'BORROWED') {
return {
label: 'Purchase',
amount: transaction.amount,
}
}
if (transaction.type === 'REPAID' && !new BN(transaction.interestAmount || 0).isZero()) {
return {
label: 'Interest',
amount: transaction.interestAmount,
}
}

return {
label: 'Principal payment',
amount: transaction.principalAmount,
}
}

const csvData = transactions
?.filter(
(transaction) => transaction.type !== 'CREATED' && transaction.type !== 'CLOSED' && transaction.type !== 'PRICED'
)
.map((transaction) => {
const { label, amount } = getLabelAndAmount(transaction)
const [, id] = transaction.asset.id.split('-')
return {
Type: label,
'Transaction Date': `"${formatDate(transaction.timestamp, {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZoneName: 'short',
})}"`,
'Asset Name': assetMetadata[Number(id) - 1]?.data?.name || '-',
Amount: amount ? `"${formatBalance(amount, 'USD', 2, 2)}"` : '-',
Transaction: `${import.meta.env.REACT_APP_SUBSCAN_URL}/extrinsic/${transaction.hash}`,
}
})

const csvUrl = csvData?.length ? getCSVDownloadUrl(csvData) : ''

const tableData =
transactions
?.filter(
(transaction) =>
transaction.type !== 'CREATED' && transaction.type !== 'CLOSED' && transaction.type !== 'PRICED'
)
.sort((a, b) => (a.timestamp > b.timestamp ? -1 : 1))
.slice(0, preview ? 8 : Infinity)
.map((transaction) => {
const [, id] = transaction.asset.id.split('-')
const { label, amount } = getLabelAndAmount(transaction)
return {
type: label,
transactionDate: transaction.timestamp,
assetId: transaction.asset.id,
assetName: assetMetadata[Number(id) - 1]?.data?.name,
amount,
hash: transaction.hash,
}
}) || []

export const TransactionHistory = () => {
return (
<Card width="100%" height="100%">
Transaction History
</Card>
<Stack gap={2}>
<Shelf justifyContent="space-between">
<Text fontSize="18px" fontWeight="500">
Transaction history
</Text>
{transactions?.length && (
<AnchorButton
href={csvUrl}
download={`pool-transaction-history-${poolId}.csv`}
variant="secondary"
icon={IconDownload}
small
target="_blank"
>
Download
</AnchorButton>
)}
</Shelf>
<DataTable data={tableData} columns={columns} />
{transactions?.length! > 8 && preview && (
<Text variant="body2" color="textSecondary">
<AnchorTextLink href={`/pools/${poolId}/transactions`}>View all</AnchorTextLink>
</Text>
)}
</Stack>
)
}
6 changes: 3 additions & 3 deletions centrifuge-app/src/components/Report/AssetTransactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { getCSVDownloadUrl } from '../../utils/getCSVDownloadUrl'
import { useAssetTransactions } from '../../utils/usePools'
import { DataTable } from '../DataTable'
import { Spinner } from '../Spinner'
import type { TableDataRow } from './index'
import { ReportContext } from './ReportContext'
import { UserFeedback } from './UserFeedback'
import type { TableDataRow } from './index'
import { formatAssetTransactionType } from './utils'

export function AssetTransactions({ pool }: { pool: Pool }) {
Expand All @@ -26,7 +26,7 @@ export function AssetTransactions({ pool }: { pool: Pool }) {
return transactions?.map((tx) => ({
name: '',
value: [
tx.assetId.split('-').at(-1)!,
tx.asset.id.split('-').at(-1)!,
tx.epochId.split('-').at(-1)!,
formatDate(tx.timestamp.toString()),
formatAssetTransactionType(tx.type),
Expand Down Expand Up @@ -77,4 +77,4 @@ export function AssetTransactions({ pool }: { pool: Pool }) {
) : (
<UserFeedback reportType="Borrower transactions" />
)
}
}
6 changes: 4 additions & 2 deletions centrifuge-app/src/components/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import {
TransactionToasts,
WalletProvider,
} from '@centrifuge/centrifuge-react'
import { FabricProvider, GlobalStyle as FabricGlobalStyle } from '@centrifuge/fabric'
import { GlobalStyle as FabricGlobalStyle, FabricProvider } from '@centrifuge/fabric'
import * as React from 'react'
import { HelmetProvider } from 'react-helmet-async'
import { QueryClient, QueryClientProvider } from 'react-query'
import { BrowserRouter as Router, LinkProps, matchPath, Redirect, Route, RouteProps, Switch } from 'react-router-dom'
import { LinkProps, Redirect, Route, RouteProps, BrowserRouter as Router, Switch, matchPath } from 'react-router-dom'
import { config, evmChains } from '../config'
import PoolsPage from '../pages/Pools'
import { pinToApi } from '../utils/pinToApi'
Expand Down Expand Up @@ -131,6 +131,7 @@ const TransactionHistoryPage = React.lazy(() => import('../pages/Portfolio/Trans
const TokenOverviewPage = React.lazy(() => import('../pages/Tokens'))
const PrimePage = React.lazy(() => import('../pages/Prime'))
const PrimeDetailPage = React.lazy(() => import('../pages/Prime/Detail'))
const PoolTransactionsPage = React.lazy(() => import('../pages/PoolTransactions'))

const routes: RouteProps[] = [
{ path: '/nfts/collection/:cid/object/mint', component: MintNFTPage },
Expand All @@ -144,6 +145,7 @@ const routes: RouteProps[] = [
{ path: '/issuer/:pid', component: IssuerPoolPage },
{ path: '/pools/:pid/assets/:aid', component: LoanPage },
{ path: '/pools/tokens', component: TokenOverviewPage },
{ path: '/pools/:pid/transactions', component: PoolTransactionsPage },
{ path: '/pools/:pid', component: PoolDetailPage },
{ path: '/pools', component: PoolsPage },
{ path: '/history/:address', component: TransactionHistoryPage },
Expand Down
2 changes: 1 addition & 1 deletion centrifuge-app/src/pages/Loan/PricingValues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function PricingValues({ loan, pool }: Props) {
const days = getAge(new Date(pricing.oracle.timestamp).toISOString())

const borrowerAssetTransactions = assetTransactions?.filter(
(assetTransaction) => assetTransaction.loanId === `${loan.poolId}-${loan.id}`
(assetTransaction) => assetTransaction.asset.id === `${loan.poolId}-${loan.id}`
)
const latestPrice = getLatestPrice(pricing.oracle.value, borrowerAssetTransactions, pool.currency.decimals)

Expand Down
6 changes: 4 additions & 2 deletions centrifuge-app/src/pages/Pool/Overview/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CurrencyBalance, Price } from '@centrifuge/centrifuge-js'
import { Box, Button, Grid, TextWithPlaceholder } from '@centrifuge/fabric'
import { Box, Button, Card, Grid, TextWithPlaceholder } from '@centrifuge/fabric'
import Decimal from 'decimal.js-light'
import * as React from 'react'
import { useParams } from 'react-router'
Expand Down Expand Up @@ -163,7 +163,9 @@ export function PoolDetailOverview() {
<PoolOverviewSection>
<React.Suspense fallback={<Spinner />}>
<Box height={447}>
<TransactionHistory />
<Card p={3}>
<TransactionHistory poolId={poolId} />
</Card>
</Box>
</React.Suspense>
</PoolOverviewSection>
Expand Down
29 changes: 29 additions & 0 deletions centrifuge-app/src/pages/PoolTransactions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Box, Stack, Text } from '@centrifuge/fabric'
import { useParams } from 'react-router'
import { LayoutBase } from '../components/LayoutBase'
import { LayoutSection } from '../components/LayoutBase/LayoutSection'
import { TransactionHistory } from '../components/PoolOverview/TransactionHistory'
import { usePool, usePoolMetadata } from '../utils/usePools'

const PoolTransactions = () => {
const { pid: poolId } = useParams<{ pid: string }>()
const pool = usePool(poolId)
const { data: metadata } = usePoolMetadata(pool)

return (
<LayoutBase>
<LayoutSection py={5}>
<Stack gap={4}>
<Text as="h1" variant="heading1">
{metadata?.pool?.name}
</Text>
<Box>
<TransactionHistory poolId={poolId} preview={false} />
</Box>
</Stack>
</LayoutSection>
</LayoutBase>
)
}

export default PoolTransactions
2 changes: 1 addition & 1 deletion centrifuge-app/src/utils/usePools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export function useBorrowerAssetTransactions(poolId: string, assetId: string, fr

return assetTransactions.pipe(
map((transactions: AssetTransaction[]) =>
transactions.filter((transaction) => transaction.assetId.split('-')[1] === assetId)
transactions.filter((transaction) => transaction.asset.id.split('-')[1] === assetId)
)
)
},
Expand Down
Loading

0 comments on commit badf937

Please sign in to comment.