From 827124f973a4788bcc1f918e417fd60b22b5649c Mon Sep 17 00:00:00 2001 From: Jeroen <1748621+hieronx@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:21:43 +0100 Subject: [PATCH 1/3] fix: subquery asset renamings (#1970) * Subquery asset renames * Update prod endpoint --- centrifuge-app/.env-config/.env.production | 2 +- .../Portfolio/TransactionTypeChip.tsx | 4 ++-- .../src/components/Portfolio/Transactions.tsx | 4 ++-- .../components/Report/AssetTransactions.tsx | 2 +- .../src/components/Report/utils.tsx | 12 +++++----- .../src/pages/Loan/HoldingsValues.tsx | 4 ++-- .../src/pages/Loan/PricingValues.tsx | 2 +- .../src/pages/Loan/TransactionTable.tsx | 12 +++++----- centrifuge-app/src/utils/getLatestPrice.ts | 4 ++-- centrifuge-app/src/utils/usePools.ts | 12 +++++----- centrifuge-js/src/modules/pools.ts | 22 +++++++++---------- centrifuge-js/src/types/subquery.ts | 10 ++++----- 12 files changed, 45 insertions(+), 45 deletions(-) diff --git a/centrifuge-app/.env-config/.env.production b/centrifuge-app/.env-config/.env.production index 0f028d5aa4..6635e88113 100644 --- a/centrifuge-app/.env-config/.env.production +++ b/centrifuge-app/.env-config/.env.production @@ -9,7 +9,7 @@ REACT_APP_ONBOARDING_API_URL=https://europe-central2-centrifuge-production-x.clo 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-centrifuge +REACT_APP_SUBQUERY_URL=https://api.subquery.network/sq/centrifuge/pools REACT_APP_SUBSCAN_URL=https://centrifuge.subscan.io REACT_APP_TINLAKE_NETWORK=mainnet REACT_APP_INFURA_KEY=bf808e7d3d924fbeb74672d9341d0550 diff --git a/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx index ad57d28fcc..b44e886c39 100644 --- a/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx +++ b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx @@ -1,10 +1,10 @@ -import { BorrowerTransactionType, InvestorTransactionType } from '@centrifuge/centrifuge-js' +import { AssetTransactionType, InvestorTransactionType } from '@centrifuge/centrifuge-js' import { StatusChip } from '@centrifuge/fabric' import * as React from 'react' import { formatTransactionsType } from '../Report/utils' type TransactionTypeProps = { - type: InvestorTransactionType | BorrowerTransactionType + type: InvestorTransactionType | AssetTransactionType trancheTokenSymbol: string poolCurrencySymbol: string currencyAmount: number | null diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index cb790ba3ef..e5dc3170be 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -1,4 +1,4 @@ -import { BorrowerTransactionType, InvestorTransactionType, Pool, Token, TokenBalance } from '@centrifuge/centrifuge-js' +import { AssetTransactionType, InvestorTransactionType, Pool, Token, TokenBalance } from '@centrifuge/centrifuge-js' import { formatBalance } from '@centrifuge/centrifuge-react' import { AnchorButton, @@ -30,7 +30,7 @@ type TransactionsProps = { } type Row = { - action: InvestorTransactionType | BorrowerTransactionType + action: InvestorTransactionType | AssetTransactionType date: number tranche?: Token tranchePrice: string diff --git a/centrifuge-app/src/components/Report/AssetTransactions.tsx b/centrifuge-app/src/components/Report/AssetTransactions.tsx index 8f1c9565fc..83cc9a11e9 100644 --- a/centrifuge-app/src/components/Report/AssetTransactions.tsx +++ b/centrifuge-app/src/components/Report/AssetTransactions.tsx @@ -26,7 +26,7 @@ export function AssetTransactions({ pool }: { pool: Pool }) { return transactions?.map((tx) => ({ name: '', value: [ - tx.loanId.split('-').at(-1)!, + tx.assetId.split('-').at(-1)!, tx.epochId.split('-').at(-1)!, formatDate(tx.timestamp.toString()), formatAssetTransactionType(tx.type), diff --git a/centrifuge-app/src/components/Report/utils.tsx b/centrifuge-app/src/components/Report/utils.tsx index 8ccefab7ff..637bf067e7 100644 --- a/centrifuge-app/src/components/Report/utils.tsx +++ b/centrifuge-app/src/components/Report/utils.tsx @@ -1,4 +1,4 @@ -import { BorrowerTransactionType, InvestorTransactionType } from '@centrifuge/centrifuge-js/dist/types/subquery' +import { AssetTransactionType, InvestorTransactionType } from '@centrifuge/centrifuge-js/dist/types/subquery' import { Text } from '@centrifuge/fabric' import { copyToClipboard } from '../../utils/copyToClipboard' import { truncate } from '../../utils/web3' @@ -48,7 +48,7 @@ export function formatInvestorTransactionsType({ } const assetTransactionTypes: { - [key in BorrowerTransactionType]: string + [key in AssetTransactionType]: string } = { CREATED: 'Created', PRICED: 'Priced', @@ -57,9 +57,9 @@ const assetTransactionTypes: { CLOSED: 'Closed', } -export function formatAssetTransactionType(type: BorrowerTransactionType) { +export function formatAssetTransactionType(type: AssetTransactionType) { if (!assetTransactionTypes[type]) { - console.warn(`Type '${type}' is not assignable to type 'BorrowerTransactionType'`) + console.warn(`Type '${type}' is not assignable to type 'AssetTransactionType'`) return type } @@ -72,7 +72,7 @@ export function formatTransactionsType({ poolCurrencySymbol, currencyAmount, }: { - type: InvestorTransactionType | BorrowerTransactionType + type: InvestorTransactionType | AssetTransactionType trancheTokenSymbol: string poolCurrencySymbol: string currencyAmount: number | null @@ -89,7 +89,7 @@ export function formatTransactionsType({ }) } -function isAssetType(type: InvestorTransactionType | BorrowerTransactionType): type is BorrowerTransactionType { +function isAssetType(type: InvestorTransactionType | AssetTransactionType): type is AssetTransactionType { return ['CREATED', 'PRICED', 'BORROWED', 'REPAID', 'CLOSED'].includes(type) } diff --git a/centrifuge-app/src/pages/Loan/HoldingsValues.tsx b/centrifuge-app/src/pages/Loan/HoldingsValues.tsx index 2c46e9680b..3b1234e80b 100644 --- a/centrifuge-app/src/pages/Loan/HoldingsValues.tsx +++ b/centrifuge-app/src/pages/Loan/HoldingsValues.tsx @@ -1,4 +1,4 @@ -import { BorrowerTransaction, CurrencyBalance, ExternalPricingInfo, Pool } from '@centrifuge/centrifuge-js' +import { AssetTransaction, CurrencyBalance, ExternalPricingInfo, Pool } from '@centrifuge/centrifuge-js' import Decimal from 'decimal.js-light' import { LabelValueStack } from '../../components/LabelValueStack' import { Dec } from '../../utils/Decimal' @@ -6,7 +6,7 @@ import { formatBalance } from '../../utils/formatting' type Props = { pool: Pool - transactions?: BorrowerTransaction[] | null + transactions?: AssetTransaction[] | null currentFace: Decimal | null pricing: ExternalPricingInfo } diff --git a/centrifuge-app/src/pages/Loan/PricingValues.tsx b/centrifuge-app/src/pages/Loan/PricingValues.tsx index fc7eb18217..345156f792 100644 --- a/centrifuge-app/src/pages/Loan/PricingValues.tsx +++ b/centrifuge-app/src/pages/Loan/PricingValues.tsx @@ -27,7 +27,7 @@ export function PricingValues({ loan, pool }: Props) { const days = getAge(new Date(pricing.oracle.timestamp).toISOString()) const borrowerAssetTransactions = assetTransactions?.filter( - (borrowerTransaction) => borrowerTransaction.loanId === `${loan.poolId}-${loan.id}` + (assetTransaction) => assetTransaction.loanId === `${loan.poolId}-${loan.id}` ) const latestPrice = getLatestPrice(pricing.oracle.value, borrowerAssetTransactions, pool.currency.decimals) diff --git a/centrifuge-app/src/pages/Loan/TransactionTable.tsx b/centrifuge-app/src/pages/Loan/TransactionTable.tsx index a3a7e59d4f..0c6f7c0aa1 100644 --- a/centrifuge-app/src/pages/Loan/TransactionTable.tsx +++ b/centrifuge-app/src/pages/Loan/TransactionTable.tsx @@ -1,5 +1,5 @@ -import { BorrowerTransaction, CurrencyBalance, ExternalPricingInfo, PricingInfo } from '@centrifuge/centrifuge-js' -import { BorrowerTransactionType } from '@centrifuge/centrifuge-js/dist/types/subquery' +import { AssetTransaction, CurrencyBalance, ExternalPricingInfo, PricingInfo } from '@centrifuge/centrifuge-js' +import { AssetTransactionType } from '@centrifuge/centrifuge-js/dist/types/subquery' import { StatusChip, Tooltip } from '@centrifuge/fabric' import BN from 'bn.js' import { useMemo } from 'react' @@ -9,7 +9,7 @@ import { Dec } from '../../utils/Decimal' import { formatBalance } from '../../utils/formatting' type Props = { - transactions: BorrowerTransaction[] + transactions: AssetTransaction[] currency: string decimals: number loanType: 'external' | 'internal' @@ -76,13 +76,13 @@ export const TransactionTable = ({ transactions, currency, loanType, decimals, p })) }, [transactions, decimals, pricing]) - const getStatusChipType = (type: BorrowerTransactionType) => { + const getStatusChipType = (type: AssetTransactionType) => { if (type === 'BORROWED' || type === 'CREATED' || type === 'PRICED') return 'info' if (type === 'REPAID') return 'ok' return 'default' } - const getStatusText = (type: BorrowerTransactionType) => { + const getStatusText = (type: AssetTransactionType) => { if (loanType === 'external' && type === 'BORROWED') return 'Purchase' if (loanType === 'external' && type === 'REPAID') return 'Sale' @@ -99,7 +99,7 @@ export const TransactionTable = ({ transactions, currency, loanType, decimals, p { align: 'left', header: 'Type', - cell: (row: { type: BorrowerTransactionType }) => ( + cell: (row: { type: AssetTransactionType }) => ( {getStatusText(row.type)} ), }, diff --git a/centrifuge-app/src/utils/getLatestPrice.ts b/centrifuge-app/src/utils/getLatestPrice.ts index 8fc2aa54cf..dbb102c8f1 100644 --- a/centrifuge-app/src/utils/getLatestPrice.ts +++ b/centrifuge-app/src/utils/getLatestPrice.ts @@ -1,8 +1,8 @@ -import { BorrowerTransaction, CurrencyBalance } from '@centrifuge/centrifuge-js' +import { AssetTransaction, CurrencyBalance } from '@centrifuge/centrifuge-js' export const getLatestPrice = ( oracleValue: CurrencyBalance, - borrowerAssetTransactions: BorrowerTransaction[] | undefined, + borrowerAssetTransactions: AssetTransaction[] | undefined, decimals: number ) => { if (!borrowerAssetTransactions) return null diff --git a/centrifuge-app/src/utils/usePools.ts b/centrifuge-app/src/utils/usePools.ts index 4fc14a1b87..f54997748b 100644 --- a/centrifuge-app/src/utils/usePools.ts +++ b/centrifuge-app/src/utils/usePools.ts @@ -1,4 +1,4 @@ -import Centrifuge, { addressToHex, BorrowerTransaction, Loan, Pool, PoolMetadata } from '@centrifuge/centrifuge-js' +import Centrifuge, { addressToHex, AssetTransaction, Loan, Pool, PoolMetadata } from '@centrifuge/centrifuge-js' import { useCentrifugeApi, useCentrifugeConsts, useCentrifugeQuery, useWallet } from '@centrifuge/centrifuge-react' import BN from 'bn.js' import { useEffect, useMemo } from 'react' @@ -83,7 +83,7 @@ export function useInvestorTransactions(poolId: string, trancheId?: string, from export function useAssetTransactions(poolId: string, from?: Date, to?: Date) { const [result] = useCentrifugeQuery( ['assetTransactions', poolId, from, to], - (cent) => cent.pools.getBorrowerTransactions([poolId, from, to]), + (cent) => cent.pools.getAssetTransactions([poolId, from, to]), { enabled: !poolId.startsWith('0x'), } @@ -113,11 +113,11 @@ export function useBorrowerAssetTransactions(poolId: string, assetId: string, fr const [result] = useCentrifugeQuery( ['borrowerAssetTransactions', poolId, assetId, from, to], (cent) => { - const borrowerTransactions = cent.pools.getBorrowerTransactions([poolId, from, to]) + const assetTransactions = cent.pools.getAssetTransactions([poolId, from, to]) - return borrowerTransactions.pipe( - map((transactions: BorrowerTransaction[]) => - transactions.filter((transaction) => transaction.loanId.split('-')[1] === assetId) + return assetTransactions.pipe( + map((transactions: AssetTransaction[]) => + transactions.filter((transaction) => transaction.assetId.split('-')[1] === assetId) ) ) }, diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index 54a3af8cc6..e9bf8c6fe9 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -10,9 +10,9 @@ import { SolverResult, calculateOptimalSolution } from '..' import { Centrifuge } from '../Centrifuge' import { Account, TransactionOptions } from '../types' import { - BorrowerTransactionType, + AssetTransactionType, InvestorTransactionType, - SubqueryBorrowerTransaction, + SubqueryAssetTransaction, SubqueryCurrencyBalances, SubqueryInvestorTransaction, SubqueryPoolSnapshot, @@ -725,14 +725,14 @@ type InvestorTransaction = { evmAddress?: string } -export type BorrowerTransaction = { +export type AssetTransaction = { id: string timestamp: string poolId: string accountId: string epochId: string loanId: string - type: BorrowerTransactionType + type: AssetTransactionType amount: CurrencyBalance | undefined settlementPrice: string | null quantity: string | null @@ -2507,21 +2507,21 @@ export function getPoolsModule(inst: Centrifuge) { ) } - function getBorrowerTransactions(args: [poolId: string, from?: Date, to?: Date]) { + function getAssetTransactions(args: [poolId: string, from?: Date, to?: Date]) { const [poolId, from, to] = args const $query = inst.getSubqueryObservable<{ - borrowerTransactions: { nodes: SubqueryBorrowerTransaction[] } + assetTransactions: { nodes: SubqueryAssetTransaction[] } }>( `query($poolId: String!, $from: Datetime!, $to: Datetime!) { - borrowerTransactions( + assetTransactions( orderBy: TIMESTAMP_ASC, filter: { poolId: { equalTo: $poolId }, timestamp: { greaterThan: $from, lessThan: $to }, }) { nodes { - loanId + assetId epochId type timestamp @@ -2543,11 +2543,11 @@ export function getPoolsModule(inst: Centrifuge) { return $query.pipe( switchMap(() => combineLatest([$query, getPoolCurrency([poolId])])), map(([data, currency]) => { - return data!.borrowerTransactions.nodes.map((tx) => ({ + return data!.assetTransactions.nodes.map((tx) => ({ ...tx, amount: tx.amount ? new CurrencyBalance(tx.amount, currency.decimals) : undefined, timestamp: new Date(`${tx.timestamp}+00:00`), - })) as unknown as BorrowerTransaction[] + })) as unknown as AssetTransaction[] }) ) } @@ -3446,7 +3446,7 @@ export function getPoolsModule(inst: Centrifuge) { getDailyPoolStates, getMonthlyPoolStates, getInvestorTransactions, - getBorrowerTransactions, + getAssetTransactions, getNativeCurrency, getCurrencies, getDailyTrancheStates, diff --git a/centrifuge-js/src/types/subquery.ts b/centrifuge-js/src/types/subquery.ts index 9bdb29437e..4cc7707737 100644 --- a/centrifuge-js/src/types/subquery.ts +++ b/centrifuge-js/src/types/subquery.ts @@ -74,17 +74,17 @@ export type SubqueryInvestorTransaction = { transactionFee?: number | null } -export type BorrowerTransactionType = 'CREATED' | 'PRICED' | 'BORROWED' | 'REPAID' | 'CLOSED' +export type AssetTransactionType = 'CREATED' | 'PRICED' | 'BORROWED' | 'REPAID' | 'CLOSED' -export type SubqueryBorrowerTransaction = { - __typename?: 'BorrowerTransaction' +export type SubqueryAssetTransaction = { + __typename?: 'AssetTransaction' id: string timestamp: string poolId: string accountId: string epochId: string - loanId: string - type: BorrowerTransactionType + assetId: string + type: AssetTransactionType amount?: number | null settlementPrice: string | null quantity: string | null From ec7859cd883c93efdfd7ad67375e051b4a0bb3d7 Mon Sep 17 00:00:00 2001 From: Sophia Date: Wed, 21 Feb 2024 19:52:27 -0500 Subject: [PATCH 2/3] Settlement account display (#1974) * Add summary component * Fix prettier * Add logos to summary * Add pinned row option to DataTable * Add reserve to asset table * Update chip and icon for assets * Fix type error * Use outstanding debt to calculate offchain cash sum * Update centrifuge-app/src/components/LoanList.tsx Co-authored-by: JP --------- Co-authored-by: JP --- .../src/assets/images/currency-dollar.svg | 3 + centrifuge-app/src/components/DataTable.tsx | 23 ++++ centrifuge-app/src/components/LoanLabel.tsx | 5 +- centrifuge-app/src/components/LoanList.tsx | 111 ++++++++++++++++-- centrifuge-app/src/components/Tooltips.tsx | 12 ++ centrifuge-app/src/pages/Loan/index.tsx | 4 +- .../src/pages/Pool/Assets/index.tsx | 60 +++++++--- fabric/src/components/Thumbnail/index.tsx | 8 +- package.json | 4 +- yarn.lock | 25 ++-- 10 files changed, 209 insertions(+), 46 deletions(-) create mode 100644 centrifuge-app/src/assets/images/currency-dollar.svg diff --git a/centrifuge-app/src/assets/images/currency-dollar.svg b/centrifuge-app/src/assets/images/currency-dollar.svg new file mode 100644 index 0000000000..cbee5b983c --- /dev/null +++ b/centrifuge-app/src/assets/images/currency-dollar.svg @@ -0,0 +1,3 @@ + + + diff --git a/centrifuge-app/src/components/DataTable.tsx b/centrifuge-app/src/components/DataTable.tsx index 622a60e341..e3a91d63cd 100644 --- a/centrifuge-app/src/components/DataTable.tsx +++ b/centrifuge-app/src/components/DataTable.tsx @@ -30,12 +30,19 @@ type GroupedProps = { export type DataTableProps = { data: Array + /** + * pinnedData is not included in sorting and will be pinned to the top of the table in the order provided + * */ + pinnedData?: Array columns: Column[] keyField?: string onRowClicked?: (row: T) => string | LinkProps['to'] defaultSortKey?: string defaultSortOrder?: OrderBy hoverable?: boolean + /** + * summary row is not included in sorting + */ summary?: T pageSize?: number page?: number @@ -88,6 +95,7 @@ const sorter = >(data: Array, order: OrderBy, s export const DataTable = >({ data, + pinnedData, columns, keyField, onRowClicked, @@ -140,6 +148,21 @@ export const DataTable = >({ ))} )} + {pinnedData?.map((row, i) => ( + onRowClicked(row))} + key={keyField ? row[keyField] : i} + tabIndex={onRowClicked ? 0 : undefined} + > + {columns.map((col, index) => ( + + {col.cell(row, i)} + + ))} + + ))} {sortedAndPaginatedData?.map((row, i) => ( = ({ loan }) => { : null const isExternalAssetRepaid = currentFace?.isZero() && loan.status === 'Active' + const isCashAsset = 'valuationMethod' in loan.pricing && loan.pricing?.valuationMethod === 'cash' const [status, text] = getLoanLabelStatus(loan, isExternalAssetRepaid) + if (!status || isCashAsset) return null return {text} } diff --git a/centrifuge-app/src/components/LoanList.tsx b/centrifuge-app/src/components/LoanList.tsx index f504812a4c..a640c7e8d6 100644 --- a/centrifuge-app/src/components/LoanList.tsx +++ b/centrifuge-app/src/components/LoanList.tsx @@ -1,4 +1,4 @@ -import { Loan, TinlakeLoan } from '@centrifuge/centrifuge-js' +import { CurrencyBalance, Loan, Rate, TinlakeLoan } from '@centrifuge/centrifuge-js' import { Box, IconChevronRight, @@ -11,8 +11,11 @@ import { Thumbnail, usePagination, } from '@centrifuge/fabric' +import get from 'lodash/get' import * as React from 'react' import { useParams, useRouteMatch } from 'react-router' +import currencyDollar from '../assets/images/currency-dollar.svg' +import usdcLogo from '../assets/images/usdc-logo.svg' import { formatNftAttribute } from '../pages/Loan/utils' import { nftMetadataSchema } from '../schemas' import { LoanTemplate, LoanTemplateAttribute } from '../types' @@ -27,10 +30,13 @@ import { Column, DataTable, FilterableTableHeader, SortableTableHeader } from '. import { LoadBoundary } from './LoadBoundary' import LoanLabel, { getLoanLabelStatus } from './LoanLabel' import { prefetchRoute } from './Root' +import { Tooltips } from './Tooltips' type Row = (Loan | TinlakeLoan) & { idSortKey: number originationDateSortKey: string + maturityDate: string | null + status: 'Created' | 'Active' | 'Closed' | '' } type Props = { @@ -64,10 +70,25 @@ export function LoanList({ loans }: Props) { const templateIds = poolMetadata?.loanTemplates?.map((s) => s.id) ?? [] const templateId = templateIds.at(-1) const { data: templateMetadata } = useMetadata(templateId) - const loansWithLabelStatus = loans.map((loan) => ({ - ...loan, - labelStatus: getLoanStatus(loan), - })) + const loansWithLabelStatus = React.useMemo(() => { + return loans + .map((loan) => ({ + ...loan, + labelStatus: getLoanStatus(loan), + })) + .sort((a, b) => { + const aValuation = get(a, 'pricing.valuationMethod') + const bValuation = get(b, 'pricing.valuationMethod') + const aId = get(a, 'id') as string + const bId = get(b, 'id') as string + + if (aValuation === 'cash' && bValuation !== 'cash') return -1 + if (aValuation !== 'cash' && bValuation === 'cash') return 1 + if (aValuation === 'cash' && bValuation === 'cash') return aId.localeCompare(bId) + + return aId.localeCompare(bId) + }) + }, [loans]) const filters = useFilters({ data: loansWithLabelStatus, }) @@ -113,7 +134,7 @@ export function LoanList({ loans }: Props) { return l.originationDate && (l.poolId.startsWith('0x') || l.status === 'Active') ? // @ts-expect-error formatDate(l.originationDate) - : '' + : '-' }, sortKey: 'originationDateSortKey', }, @@ -121,7 +142,7 @@ export function LoanList({ loans }: Props) { { align: 'left', header: , - cell: (l: Row) => (l.pricing.maturityDate ? formatDate(l.pricing.maturityDate) : ''), + cell: (l: Row) => (l?.maturityDate ? formatDate(l.maturityDate) : '-'), sortKey: 'maturityDate', }, { @@ -144,7 +165,7 @@ export function LoanList({ loans }: Props) { }, { header: '', - cell: () => , + cell: (l: Row) => (l.status ? : ''), width: '52px', }, ].filter(Boolean) as Column[] @@ -161,10 +182,33 @@ export function LoanList({ loans }: Props) { !loan?.totalBorrowed?.isZero() ? loan.originationDate : '', - maturityDate: loan.pricing.maturityDate, + maturityDate: + 'valuationMethod' in loan.pricing && loan.pricing.valuationMethod === 'cash' ? null : loan.pricing.maturityDate, ...loan, })) + const pinnedData: Row[] = [ + { + id: 'reserve', + // @ts-expect-error + status: '', + poolId: pool.id, + pricing: { + valuationMethod: 'discountedCashFlow', + maxBorrowAmount: 'upToTotalBorrowed', + value: CurrencyBalance.fromFloat(0, 18), + maturityDate: '', + maturityExtensionDays: 0, + advanceRate: Rate.fromFloat(0), + interestRate: Rate.fromFloat(0), + }, + asset: { collectionId: '', nftId: '' }, + totalBorrowed: CurrencyBalance.fromFloat(0, 18), + totalRepaid: CurrencyBalance.fromFloat(0, 18), + outstandingDebt: CurrencyBalance.fromFloat(0, 18), + }, + ] + const pagination = usePagination({ data: rows, pageSize: 20 }) return ( @@ -175,9 +219,11 @@ export function LoanList({ loans }: Props) { `${basePath}/${poolId}/assets/${row.id}`} + onRowClicked={(row) => + row.status ? `${basePath}/${poolId}/assets/${row.id}` : `${basePath}/${poolId}/assets` + } pageSize={20} page={pagination.page} /> @@ -216,14 +262,49 @@ function AssetName({ loan }: { loan: Row }) { const isTinlakePool = loan.poolId.startsWith('0x') const nft = useCentNFT(loan.asset.collectionId, loan.asset.nftId, false, isTinlakePool) const { data: metadata, isLoading } = useMetadata(nft?.metadataUri, nftMetadataSchema) + if (loan.id === 'reserve') { + return ( + + + + + + Onchain reserve} /> + + + ) + } + + if (loan.status === 'Active' && 'valuationMethod' in loan.pricing && loan.pricing.valuationMethod === 'cash') { + return ( + + + + + + Bank account} /> + + + ) + } + return ( - + {metadata?.name} @@ -261,6 +342,10 @@ function Amount({ loan }: { loan: Row }) { return formatBalance(l.outstandingDebt, pool?.currency.symbol) + // @ts-expect-error + case '': + return formatBalance(pool.reserve.total, pool?.currency.symbol) + default: return '' } diff --git a/centrifuge-app/src/components/Tooltips.tsx b/centrifuge-app/src/components/Tooltips.tsx index a67329aefa..3398bbf1f9 100644 --- a/centrifuge-app/src/components/Tooltips.tsx +++ b/centrifuge-app/src/components/Tooltips.tsx @@ -258,6 +258,18 @@ export const tooltipText = { label: 'T-Bill APR', body: 'Based on 3- to 6-month T-bills returns. See pool details for further information.', }, + totalNav: { + label: "Total NAV", + body: "The total Net Asset Value (NAV) reflects the combined present value of assets, cash held in the onchain reserve of the pool, and cash in the bank account designated as offchain cash." + } , + onchainReserve: { + label: "Onchain reserve", + body: "The onchain reserve represents the amount of available liquidity in the pool available for asset originations and redemptions." + }, + offchainCash: { + label: "Offchain cash", + body: "Offchain cash represents funds held in a traditional bank account or custody account." + } } export type TooltipsProps = { diff --git a/centrifuge-app/src/pages/Loan/index.tsx b/centrifuge-app/src/pages/Loan/index.tsx index b2236eb9fc..c41934fc5e 100644 --- a/centrifuge-app/src/pages/Loan/index.tsx +++ b/centrifuge-app/src/pages/Loan/index.tsx @@ -185,7 +185,9 @@ function Loan() { : nftMetadata?.properties[key], })) || [] : []), - ...(loan.pricing.maturityDate + ...(loan.pricing.maturityDate && + 'valuationMethod' in loan.pricing && + loan.pricing.valuationMethod !== 'cash' ? [ { label: 'Maturity date', diff --git a/centrifuge-app/src/pages/Pool/Assets/index.tsx b/centrifuge-app/src/pages/Pool/Assets/index.tsx index 3892d71cb3..5133cee25c 100644 --- a/centrifuge-app/src/pages/Pool/Assets/index.tsx +++ b/centrifuge-app/src/pages/Pool/Assets/index.tsx @@ -1,7 +1,9 @@ -import { ActiveLoan } from '@centrifuge/centrifuge-js' +import { ActiveLoan, Loan } from '@centrifuge/centrifuge-js' import { Box, Shelf, Text } from '@centrifuge/fabric' import * as React from 'react' import { useParams } from 'react-router' +import currencyDollar from '../../../assets/images/currency-dollar.svg' +import usdcLogo from '../../../assets/images/usdc-logo.svg' import { LayoutBase } from '../../../components/LayoutBase' import { LoadBoundary } from '../../../components/LoadBoundary' import { LoanList } from '../../../components/LoanList' @@ -13,7 +15,7 @@ import { Dec } from '../../../utils/Decimal' import { formatBalance } from '../../../utils/formatting' import { useLoans } from '../../../utils/useLoans' import { useSuitableAccounts } from '../../../utils/usePermissions' -import { useAverageAmount, usePool } from '../../../utils/usePools' +import { usePool } from '../../../utils/usePools' import { PoolDetailHeader } from '../Header' export function PoolDetailAssetsTab() { @@ -31,7 +33,7 @@ export function PoolDetailAssets() { const { pid: poolId } = useParams<{ pid: string }>() const pool = usePool(poolId) const loans = useLoans(poolId) - const averageAmount = useAverageAmount(poolId) + const isTinlakePool = poolId.startsWith('0x') if (!pool) return null @@ -47,26 +49,52 @@ export function PoolDetailAssets() { const ongoingAssets = (loans && [...loans].filter((loan) => loan.status === 'Active' && !loan.outstandingDebt.isZero())) as ActiveLoan[] - const isExternal = 'valuationMethod' in loans[0].pricing && loans[0].pricing.valuationMethod === 'oracle' - - const avgAmount = isExternal - ? averageAmount - : ongoingAssets - .reduce((curr, prev) => curr.add(prev.outstandingDebt.toDecimal() || Dec(0)), Dec(0)) - .dividedBy(ongoingAssets.length) - .toDecimalPlaces(2) + const offchainAssets = !isTinlakePool + ? loans.filter((loan) => (loan as Loan).pricing.valuationMethod === 'cash') + : null + const offchainReserve = offchainAssets?.reduce( + (curr, prev) => curr.add(prev.status === 'Active' ? prev.outstandingDebt.toDecimal() : Dec(0)), + Dec(0) + ) - const assetValue = formatBalance(pool.nav.latest.toDecimal().toNumber(), pool.currency.symbol) + const overdueAssets = loans.filter( + (loan) => + loan.status === 'Active' && + loan.outstandingDebt.gtn(0) && + new Date(loan.pricing.maturityDate).getTime() < Date.now() + ) const pageSummaryData: { label: React.ReactNode; value: React.ReactNode }[] = [ { - label: , - value: assetValue, + label: , + value: formatBalance(pool.nav.latest.toDecimal(), pool.currency.symbol), + }, + { + label: ( + + + + + ), + value: formatBalance(pool.reserve.total || 0, pool.currency.symbol), + }, + { + label: ( + + + + + ), + value: formatBalance(offchainReserve, 'USD'), + }, + { + label: 'Total assets', + value: loans.length, }, { label: , value: ongoingAssets.length || 0 }, { - label: , - value: formatBalance(avgAmount, pool.currency.symbol), + label: 'Overdue assets', + value: 0 ? 'statusCritical' : 'inherit'}>{overdueAssets.length}, }, ] diff --git a/fabric/src/components/Thumbnail/index.tsx b/fabric/src/components/Thumbnail/index.tsx index eda0060078..34ebed6a8f 100644 --- a/fabric/src/components/Thumbnail/index.tsx +++ b/fabric/src/components/Thumbnail/index.tsx @@ -54,8 +54,8 @@ const StyledThumbnail = styled(Text)>` background: 'transparent', '&::before': { content: '""', - width: '80%', - height: '80%', + width: '50%', + height: '50%', position: 'absolute', left: 0, top: 0, @@ -64,9 +64,9 @@ const StyledThumbnail = styled(Text)>` margin: 'auto', zIndex: 0, transform: 'rotate(45deg)', - background: theme.colors.backgroundThumbnail, + background: theme.colors.statusInfo, color: theme.colors.textInverted, - borderRadius: '4px', + borderRadius: '2px', }, }) case 'token': diff --git a/package.json b/package.json index 518808112d..ff369b2f70 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "esbuild": "^0.16.17", "esbuild-node-externals": "^1.6.0", "husky": "^6.0.0", - "prettier": "^2.3.1", - "prettier-plugin-organize-imports": "1.1.1", + "prettier": "^2.4.1", + "prettier-plugin-organize-imports": "3.2.4", "pretty-quick": "^3.1.1", "ts-node": "9.0.0", "typescript": "~5.3.3" diff --git a/yarn.lock b/yarn.lock index 076f458433..4c18bbd492 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22478,17 +22478,24 @@ fsevents@~2.3.3: languageName: node linkType: hard -"prettier-plugin-organize-imports@npm:1.1.1": - version: 1.1.1 - resolution: "prettier-plugin-organize-imports@npm:1.1.1" +"prettier-plugin-organize-imports@npm:3.2.4": + version: 3.2.4 + resolution: "prettier-plugin-organize-imports@npm:3.2.4" peerDependencies: - prettier: ">=1.10" - typescript: ">=2.8.2" - checksum: 030b9534ad40741643ca5747ddf85290c68f67b5b96a4c389670d2ac8b39c09df24f7aa821a2f5d5c4a95a99dd699276082bae5b4aeea7817d1e3596434dad10 + "@volar/vue-language-plugin-pug": ^1.0.4 + "@volar/vue-typescript": ^1.0.4 + prettier: ">=2.0" + typescript: ">=2.9" + peerDependenciesMeta: + "@volar/vue-language-plugin-pug": + optional: true + "@volar/vue-typescript": + optional: true + checksum: 57ae97d7e403445e650ae92b7da586761d1d88a47e46b3ea274baeb96782165bebd0132db9c652081e185c41b50701ba1d30d615ad1c9000300cc0c67eb12b7a languageName: node linkType: hard -"prettier@npm:^2.1.2, prettier@npm:^2.3.1, prettier@npm:^2.4.1, prettier@npm:^2.8.0": +"prettier@npm:^2.1.2, prettier@npm:^2.4.1, prettier@npm:^2.8.0": version: 2.8.8 resolution: "prettier@npm:2.8.8" bin: @@ -23905,8 +23912,8 @@ fsevents@~2.3.3: esbuild: ^0.16.17 esbuild-node-externals: ^1.6.0 husky: ^6.0.0 - prettier: ^2.3.1 - prettier-plugin-organize-imports: 1.1.1 + prettier: ^2.4.1 + prettier-plugin-organize-imports: 3.2.4 pretty-quick: ^3.1.1 rxjs: ^7.8.0 ts-node: 9.0.0 From da6762d6920348e6ecff55bb0b6dbe03796e52fa Mon Sep 17 00:00:00 2001 From: Guillermo Perez Date: Thu, 22 Feb 2024 15:38:02 +0100 Subject: [PATCH 3/3] fix demo pod url --- centrifuge-app/.env-config/.env.demo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrifuge-app/.env-config/.env.demo b/centrifuge-app/.env-config/.env.demo index b51d48cbf6..34f76d2ff5 100644 --- a/centrifuge-app/.env-config/.env.demo +++ b/centrifuge-app/.env-config/.env.demo @@ -1,5 +1,5 @@ REACT_APP_COLLATOR_WSS_URL=wss://fullnode.demo.k-f.dev -REACT_APP_DEFAULT_NODE_URL=pod-demo.k-f.dev +REACT_APP_DEFAULT_NODE_URL=https://pod-demo.k-f.dev REACT_APP_DEFAULT_UNLIST_POOLS=true REACT_APP_FAUCET_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/faucet-api-demo REACT_APP_IPFS_GATEWAY=https://centrifuge.mypinata.cloud/