Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new sdk to current app #2579

Merged
merged 14 commits into from
Feb 12, 2025
2 changes: 1 addition & 1 deletion centrifuge-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"@centrifuge/centrifuge-js": "workspace:*",
"@centrifuge/centrifuge-react": "workspace:*",
"@centrifuge/fabric": "workspace:*",
"@centrifuge/sdk": "^0.0.0-alpha.0",
"@centrifuge/sdk": "latest",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's pin down the version so we don't accidentally include updates that we're not ready for yet

"@makerdao/multicall": "^0.12.0",
"@polkadot/react-identicon": "~3.1.4",
"@styled-system/css": "^5.1.5",
Expand Down
23 changes: 12 additions & 11 deletions centrifuge-app/src/components/Charts/SimpleBarChart.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { CurrencyBalance, CurrencyMetadata } from '@centrifuge/centrifuge-js'
import { Shelf, Text } from '@centrifuge/fabric'
import { CurrencyMetadata } from '@centrifuge/centrifuge-js'
import { Box, Shelf, Text } from '@centrifuge/fabric'
import { Bar, BarChart, CartesianGrid, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'
import { useTheme } from 'styled-components'
import { formatDate } from '../../../src/utils/date'
import { formatBalance, formatBalanceAbbreviated } from '../../../src/utils/formatting'
import { formatBalance, formatBalanceAbbreviated } from '../../../src/utils/formatting-sdk'
import { LoadBoundary } from '../LoadBoundary'
import { CustomTick } from './PoolPerformanceChart'
import { TooltipContainer, TooltipTitle } from './Tooltip'
Expand Down Expand Up @@ -62,10 +62,7 @@ export const SimpleBarChart = ({ currency, data, groupBy }: SimpleBarChartProps)
angle={45}
/>
<YAxis
tickFormatter={(tick: number) => {
const balance = new CurrencyBalance(tick, currency?.decimals || 0)
return formatBalanceAbbreviated(balance, '', 0)
}}
tickFormatter={(tick: any) => formatBalanceAbbreviated(tick, 0)}
tick={{ fontSize: 10, color: theme.colors.textPrimary }}
tickLine={false}
axisLine={false}
Expand All @@ -79,10 +76,14 @@ export const SimpleBarChart = ({ currency, data, groupBy }: SimpleBarChartProps)
if (payload && payload?.length > 0) {
return (
<TooltipContainer>
<TooltipTitle>{formatDate(payload[0].payload.name)}</TooltipTitle>
{payload.map((item) => (
<Text variant="body3">{formatBalance(item.value as number, currency)}</Text>
))}
{payload.map((item) => {
return (
<Box>
<TooltipTitle>{formatDate(item.payload.name)}</TooltipTitle>
<Text variant="body3">{formatBalance(item.value ?? 0, 2, currency?.displayName)}</Text>
</Box>
)
})}
</TooltipContainer>
)
}
Expand Down
142 changes: 80 additions & 62 deletions centrifuge-app/src/components/Report/AssetList.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import { Pool } from '@centrifuge/centrifuge-js'
import { Box, Text } from '@centrifuge/fabric'
import {
AssetListReport,
AssetListReportPrivateCredit,
AssetListReportPublicCredit,
} from '@centrifuge/sdk/dist/types/reports'
import { useContext, useEffect, useMemo } from 'react'
import { formatBalance, formatPercentage } from '../../../src/utils/formatting-sdk'
import { useBasePath } from '../../../src/utils/useBasePath'
import { formatDate } from '../../utils/date'
import { formatBalance, formatPercentage } from '../../utils/formatting'
import { getCSVDownloadUrl } from '../../utils/getCSVDownloadUrl'
import { useAllPoolAssetSnapshots, usePoolMetadata } from '../../utils/usePools'
import { usePoolMetadata } from '../../utils/usePools'
import { DataTable, SortableTableHeader } from '../DataTable'
import { Spinner } from '../Spinner'
import { RouterTextLink } from '../TextLink'
import { ReportContext } from './ReportContext'
import { UserFeedback } from './UserFeedback'
import type { TableDataRow } from './index'
import { useReport } from './useReportsQuery'

const noop = (v: any) => v

const valuationLabels = {
discountedCashFlow: 'Non-fungible asset - DCF',
outstandingDebt: 'Non-fungible asset - at par',
oracle: 'Fungible asset - external pricing',
type AssetSnapshot = AssetListReport & {
transactionType: 'ACTIVE' | string
name: string
}

function getColumnConfig(isPrivate: boolean, symbol: string) {
function getColumnConfig(isPrivate: boolean, symbol: string, decimals: number) {
if (isPrivate) {
return [
{ header: 'Name', align: 'left', csvOnly: false, formatter: noop },
Expand All @@ -30,42 +35,42 @@ function getColumnConfig(isPrivate: boolean, symbol: string) {
align: 'left',
csvOnly: false,
sortable: true,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
{
header: 'Principal outstanding',
align: 'left',
csvOnly: false,
sortable: true,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
{
header: 'Interest outstanding',
align: 'left',
csvOnly: false,
sortable: true,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
{
header: 'Principal repaid',
align: 'left',
csvOnly: false,
sortable: true,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
{
header: 'Interest repaid',
align: 'left',
csvOnly: false,
sortable: true,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
{
header: 'Additional repaid',
align: 'left',
csvOnly: false,
sortable: true,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
{
header: 'Origination date',
Expand All @@ -92,31 +97,31 @@ function getColumnConfig(isPrivate: boolean, symbol: string) {
header: 'Advance rate',
align: 'left',
csvOnly: false,
formatter: (v: any) => (v ? formatPercentage(v, true, {}, 2) : '-'),
formatter: (v: any) => (v ? formatPercentage(v, 2, true, {}) : '-'),
},
{
header: 'Collateral value',
align: 'left',
csvOnly: false,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
{
header: 'Probability of default (PD)',
align: 'left',
csvOnly: false,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
{
header: 'Loss given default (LGD)',
align: 'left',
csvOnly: false,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
{
header: 'Discount rate',
align: 'left',
csvOnly: false,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
]
} else {
Expand All @@ -127,28 +132,28 @@ function getColumnConfig(isPrivate: boolean, symbol: string) {
align: 'left',
csvOnly: false,
sortable: true,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
{
header: 'Face value',
align: 'left',
csvOnly: false,
sortable: true,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
{
header: 'Quantity',
align: 'left',
csvOnly: false,
sortable: true,
formatter: (v: any) => (v ? formatBalance(v, undefined, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, '') : '-'),
},
{
header: 'Market price',
align: 'left',
csvOnly: false,
sortable: true,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
{
header: 'Maturity date',
Expand All @@ -162,28 +167,38 @@ function getColumnConfig(isPrivate: boolean, symbol: string) {
align: 'left',
csvOnly: false,
sortable: true,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
{
header: 'Realized profit',
align: 'left',
csvOnly: false,
sortable: true,
formatter: (v: any) => (v ? formatBalance(v, symbol, 2) : '-'),
formatter: (v: any) => (v ? formatBalance(v, 2, symbol) : '-'),
},
]
}
}

export function AssetList({ pool }: { pool: Pool }) {
const basePath = useBasePath()
const { loanStatus, startDate, setCsvData } = useContext(ReportContext)
const { loanStatus, startDate, setCsvData, endDate } = useContext(ReportContext)
const { data: poolMetadata } = usePoolMetadata(pool)
const { symbol } = pool.currency
const { symbol, decimals } = pool.currency
const poolCreditType = poolMetadata?.pool?.asset.class || 'privateCredit'
const { data: snapshots } = useAllPoolAssetSnapshots(pool.id, startDate)
const isPrivate = poolCreditType === 'Private credit' || poolCreditType === 'privateCredit'
const columnConfig = getColumnConfig(isPrivate, symbol)
const columnConfig = getColumnConfig(isPrivate, symbol, decimals)

const { data: snapshots = [], isLoading } = useReport(
'assetList',
pool,
new Date(startDate),
new Date(endDate),
undefined,
{
...(loanStatus && { status: loanStatus }),
}
)

const columns = useMemo(
() =>
Expand All @@ -210,19 +225,20 @@ export function AssetList({ pool }: { pool: Pool }) {
.filter((col) => !col.csvOnly),
[columnConfig, basePath, pool.id]
)

const data = useMemo((): any[] => {
if (!snapshots) return []

return snapshots
.filter((snapshot) => snapshot?.valuationMethod?.toLowerCase() !== 'cash')
return (snapshots as AssetSnapshot[])
.filter((snapshot) =>
isPrivate ? 'valuationMethod' in snapshot && snapshot?.valuationMethod?.toLowerCase() !== 'cash' : true
)
.filter((snapshot) => {
const isMaturityDatePassed = snapshot?.actualMaturityDate
? new Date() > new Date(snapshot.actualMaturityDate)
: false
const isDebtZero = snapshot?.outstandingDebt?.isZero()
const isMaturityDatePassed = snapshot?.maturityDate ? new Date() > new Date(snapshot.maturityDate) : false
const isDebtZero = 'outstandingQuantity' in snapshot ? snapshot.outstandingQuantity?.isZero() : false

if (loanStatus === 'ongoing') {
return snapshot.status === 'ACTIVE' && !isMaturityDatePassed && !isDebtZero
return snapshot.transactionType === 'ACTIVE' && !isMaturityDatePassed && !isDebtZero
} else if (loanStatus === 'repaid') {
return isMaturityDatePassed && isDebtZero
} else if (loanStatus === 'overdue') {
Expand All @@ -231,47 +247,49 @@ export function AssetList({ pool }: { pool: Pool }) {
})
.sort((a, b) => {
// Sort by actualMaturityDate in descending order
const dateA = new Date(a.actualMaturityDate || 0).getTime()
const dateB = new Date(b.actualMaturityDate || 0).getTime()
const dateA = new Date(a.maturityDate || 0).getTime()
const dateB = new Date(b.maturityDate || 0).getTime()
return dateB - dateA
})
.map((snapshot) => {
const valuationMethod = snapshot?.valuationMethod as keyof typeof valuationLabels
if (isPrivate) {
const privateSnapshot = snapshot as AssetSnapshot & AssetListReportPrivateCredit
return {
name: '',
value: [
snapshot?.name,
snapshot?.presentValue,
snapshot?.outstandingPrincipal,
snapshot?.outstandingInterest,
snapshot?.totalRepaidPrincipal,
snapshot?.totalRepaidInterest,
snapshot?.totalRepaidUnscheduled,
snapshot?.actualOriginationDate,
snapshot?.actualMaturityDate,
valuationMethod || snapshot?.valuationMethod,
snapshot?.advanceRate,
snapshot?.collateralValue,
snapshot?.probabilityOfDefault,
snapshot?.lossGivenDefault,
snapshot?.discountRate,
privateSnapshot.name,
privateSnapshot.presentValue,
privateSnapshot.outstandingPrincipal,
privateSnapshot.outstandingInterest,
privateSnapshot.repaidPrincipal,
privateSnapshot.repaidInterest,
privateSnapshot.repaidUnscheduled,
privateSnapshot.originationDate,
privateSnapshot.maturityDate,
privateSnapshot.valuationMethod,
privateSnapshot.advanceRate,
privateSnapshot.collateralValue,
privateSnapshot.probabilityOfDefault,
privateSnapshot.lossGivenDefault,
privateSnapshot.discountRate,
],
heading: false,
id: snapshot?.assetId,
}
} else {
const publicSnapshot = snapshot as AssetSnapshot & AssetListReportPublicCredit

return {
name: '',
value: [
snapshot?.name,
snapshot?.presentValue,
snapshot?.faceValue,
snapshot?.outstandingQuantity,
snapshot?.currentPrice,
snapshot?.actualMaturityDate,
snapshot?.unrealizedProfitAtMarketPrice,
snapshot?.sumRealizedProfitFifo,
publicSnapshot.name,
publicSnapshot.presentValue,
publicSnapshot.faceValue,
publicSnapshot.outstandingQuantity,
publicSnapshot.currentPrice,
publicSnapshot.maturityDate,
publicSnapshot.unrealizedProfit,
publicSnapshot.realizedProfit,
],
heading: false,
id: snapshot?.assetId,
Expand Down Expand Up @@ -304,7 +322,7 @@ export function AssetList({ pool }: { pool: Pool }) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data])

if (!snapshots) {
if (isLoading) {
return <Spinner />
}

Expand Down
Loading
Loading