diff --git a/centrifuge-app/src/pages/NavManagement/NavManagementAssetTable.tsx b/centrifuge-app/src/pages/NavManagement/NavManagementAssetTable.tsx
index 04596cd21..87d551707 100644
--- a/centrifuge-app/src/pages/NavManagement/NavManagementAssetTable.tsx
+++ b/centrifuge-app/src/pages/NavManagement/NavManagementAssetTable.tsx
@@ -1,11 +1,20 @@
-import { ActiveLoan, CreatedLoan, CurrencyBalance, ExternalLoan } from '@centrifuge/centrifuge-js'
+import {
+ ActiveLoan,
+ CreatedLoan,
+ CurrencyBalance,
+ CurrencyKey,
+ CurrencyMetadata,
+ ExternalLoan,
+} from '@centrifuge/centrifuge-js'
import { useCentrifugeApi, useCentrifugeQuery, useCentrifugeTransaction } from '@centrifuge/centrifuge-react'
import {
Box,
Button,
CurrencyInput,
+ Divider,
Drawer,
IconArrowRight,
+ IconClockForward,
IconDownload,
Shelf,
Stack,
@@ -15,7 +24,7 @@ import {
import { BN } from 'bn.js'
import { Field, FieldProps, FormikProvider, useFormik } from 'formik'
import * as React from 'react'
-import { switchMap } from 'rxjs'
+import { combineLatest, switchMap } from 'rxjs'
import daiLogo from '../../assets/images/dai-logo.svg'
import usdcLogo from '../../assets/images/usdc-logo.svg'
import { ButtonGroup } from '../../components/ButtonGroup'
@@ -23,14 +32,15 @@ import { DataTable } from '../../components/DataTable'
import { LayoutSection } from '../../components/LayoutBase/LayoutSection'
import { AssetName } from '../../components/LoanList'
import { RouterTextLink } from '../../components/TextLink'
+import { Dec } from '../../utils/Decimal'
import { formatBalance } from '../../utils/formatting'
import { useLiquidity } from '../../utils/useLiquidity'
+import { useActiveDomains } from '../../utils/useLiquidityPools'
import { useSuitableAccounts } from '../../utils/usePermissions'
import { usePool, usePoolAccountOrders, usePoolFees } from '../../utils/usePools'
import { usePoolsForWhichAccountIsFeeder } from '../../utils/usePoolsForWhichAccountIsFeeder'
import { positiveNumber } from '../../utils/validation'
import { isCashLoan, isExternalLoan } from '../Loan/utils'
-import { VisualNavCard } from './Overview'
type FormValues = {
feed: {
@@ -49,6 +59,7 @@ type Row = FormValues['feed'][0] | ActiveLoan | CreatedLoan
const MAX_COLLECT = 100 // maximum number of transactions to collect in one batch
export function NavManagementAssetTable({ poolId }: { poolId: string }) {
+ const { data: domains } = useActiveDomains(poolId)
const allowedPools = usePoolsForWhichAccountIsFeeder()
const isFeeder = !!allowedPools?.find((p) => p.id === poolId)
const [isEditing, setIsEditing] = React.useState(false)
@@ -91,8 +102,20 @@ export function NavManagementAssetTable({ poolId }: { poolId: string }) {
const { execute, isLoading } = useCentrifugeTransaction(
'Update NAV',
(cent) => (args: [values: FormValues], options) => {
- return cent.pools.closeEpoch([poolId, false], { batch: true }).pipe(
- switchMap((closeTx) => {
+ const domain = domains?.find((domain) => domain.isActive && domain.hasDeployedLp)
+ const updateTokenPrices = domain
+ ? Object.entries(domain.liquidityPools).flatMap(([tid, poolsByCurrency]) => {
+ return domain.currencies
+ .filter((cur) => !!poolsByCurrency[cur.address])
+ .map((cur) => [tid, cur.key] satisfies [string, CurrencyKey])
+ .map(([tid, curKey]) =>
+ cent.liquidityPools.updateTokenPrice([poolId, tid, curKey, domain.chainId], { batch: true })
+ )
+ })
+ : []
+
+ return combineLatest([cent.pools.closeEpoch([poolId, false], { batch: true }), ...updateTokenPrices]).pipe(
+ switchMap(([closeTx, ...updateTokenPricesTxs]) => {
const [values] = args
const batch = [
...values.feed
@@ -103,6 +126,7 @@ export function NavManagementAssetTable({ poolId }: { poolId: string }) {
}),
api.tx.oraclePriceCollection.updateCollection(poolId),
api.tx.loans.updatePortfolioValuation(poolId),
+ ...updateTokenPricesTxs,
]
if (liquidityAdminAccount && orders?.length) {
@@ -290,90 +314,205 @@ export function NavManagementAssetTable({ poolId }: { poolId: string }) {
}
return (
-
-
- setIsConfirming(false)}>
-
+ <>
+
+
+
+
+
+
+ setIsConfirming(false)}>
- Confirm NAV
-
-
- {pool.tranches.length === 1 && (
- Token price update
-
-
- {pool.tranches[0].currency.symbol} price:{' '}
- {formatBalance(pool.tranches[0].tokenPrice ?? 0, pool.currency.symbol, 5)}
-
- {' '}
-
- {pool.tranches[0].currency.symbol} price: {formatBalance(newPrice ?? 0, pool.currency.symbol, 5)}
-
-
+ Confirm NAV
+
- )}
-
-
-
-
-
- {liquidityAdminAccount && orders?.length ? (
-
- There are open investment or redemption orders, updating the NAV will trigger the execution of orders.
-
- ) : null}
-
-
-
-
+ {pool.tranches.length === 1 && (
+
+ Token price update
+
+
+ {pool.tranches[0].currency.symbol} price:{' '}
+ {formatBalance(pool.tranches[0].tokenPrice ?? 0, pool.currency.symbol, 5)}
+
+ {' '}
+
+ {pool.tranches[0].currency.symbol} price: {formatBalance(newPrice ?? 0, pool.currency.symbol, 5)}
+
+
+
+ )}
+
-
- ) : (
-
-
-
- )
- }
- >
-
-
-
+
+ {liquidityAdminAccount && orders?.length ? (
+
+ There are open investment or redemption orders, updating the NAV will trigger the execution of orders.
+
+ ) : null}
+
+
+
+ setIsEditing(false)} small>
+ Cancel
+
+ setIsConfirming(true)}
+ loading={isLoading || form.isSubmitting}
+ loadingMessage={isLoading ? 'Pending...' : undefined}
+ disabled={!isFeeder}
+ >
+ Done
+
+
+ ) : (
+
+
+ Download
+
+ setIsEditing(true)} small>
+ Edit
+
+
+ )
+ }
+ >
+
+
+
+
+ >
+ )
+}
+
+export function NavOverviewCard({ poolId, updatedPrices }: { poolId: string; updatedPrices: FormValues['feed'] }) {
+ const pool = usePool(poolId)
+ const poolFees = usePoolFees(poolId)
+ const today = new Date()
+ today.setHours(0, 0, 0, 0)
+
+ const pendingFees = React.useMemo(() => {
+ return new CurrencyBalance(
+ poolFees?.map((f) => f.amounts.pending).reduce((acc, f) => acc.add(f), new BN(0)) ?? new BN(0),
+ pool.currency.decimals
+ )
+ }, [poolFees, pool.currency.decimals])
+
+ const [allLoans] = useCentrifugeQuery(['loans', poolId], (cent) => cent.pools.getLoans([poolId]), {
+ enabled: !!poolId && !!pool,
+ })
+
+ const externalLoans = React.useMemo(
+ () =>
+ (allLoans?.filter(
+ // Keep external loans, except ones that are fully repaid
+ (l) =>
+ isExternalLoan(l) &&
+ l.status !== 'Closed' &&
+ l.status !== 'Created' &&
+ (!('presentValue' in l) || !l.presentValue.isZero())
+ ) as ActiveLoan[]) ?? [],
+ [allLoans]
+ )
+
+ const changeInValuation = React.useMemo(() => {
+ return externalLoans.reduce((prev, curr) => {
+ const price = curr.currentPrice.toDecimal()
+ const quantity = (curr as ExternalLoan).pricing.outstandingQuantity.toDecimal()
+ const updatedPrice = Dec(updatedPrices.find((p) => p.id === curr.id)?.value || 0)
+ return CurrencyBalance.fromFloat(
+ prev.toDecimal().add(price.sub(updatedPrice).mul(quantity)).toString(),
+ pool.currency.decimals
+ )
+ }, new CurrencyBalance(0, pool.currency.decimals))
+ }, [externalLoans, pool?.nav, updatedPrices])
+
+ return (
+
+ )
+}
+
+export function VisualNavCard({
+ currency,
+ current,
+ change,
+ pendingFees,
+ pendingNav,
+}: {
+ currency: Pick
+ current: number
+ change: number
+ pendingFees: number
+ pendingNav: number
+}) {
+ return (
+
+
+
+ Current NAV
+
+ {formatBalance(current, currency.displayName, 2)}
+
+
+
+
+
+ Change in asset valuation
+
+ = 0 ? 'statusOk' : 'statusCritical'}>
+ {formatBalance(change, currency.displayName, 2)}
+
+
+
+
+ Pending fees
+
+
+ -{formatBalance(pendingFees, currency.displayName, 2)}
+
+
+
+
+
+
+
+
+ Pending NAV
+
+
+
+ {formatBalance(pendingNav, currency.displayName, 2)}
+
+
)
}
diff --git a/centrifuge-app/src/pages/NavManagement/Overview.tsx b/centrifuge-app/src/pages/NavManagement/Overview.tsx
index 385efb4d0..6a0e25299 100644
--- a/centrifuge-app/src/pages/NavManagement/Overview.tsx
+++ b/centrifuge-app/src/pages/NavManagement/Overview.tsx
@@ -1,14 +1,12 @@
import { useParams } from 'react-router'
-import { LayoutBase } from '../../components/LayoutBase'
import { PageSummary } from '../../components/PageSummary'
import { Tooltips } from '../../components/Tooltips'
import { formatBalance } from '../../utils/formatting'
-import { useDailyPoolStates, usePool, usePoolFees } from '../../utils/usePools'
+import { useDailyPoolStates, usePool } from '../../utils/usePools'
-import { CurrencyBalance, CurrencyMetadata } from '@centrifuge/centrifuge-js'
-import { Divider, IconClockForward, Shelf, Stack, Text } from '@centrifuge/fabric'
+import { CurrencyBalance } from '@centrifuge/centrifuge-js'
+import { Stack, Text } from '@centrifuge/fabric'
import { BN } from 'bn.js'
-import React from 'react'
import { LayoutSection } from '../../components/LayoutBase/LayoutSection'
import { NavManagementAssetTable } from './NavManagementAssetTable'
@@ -16,7 +14,7 @@ export default function NavManagementOverviewPage() {
const { pid } = useParams<{ pid: string }>()
if (!pid) throw new Error('Pool not found')
return (
-
+ <>
@@ -25,11 +23,8 @@ export default function NavManagementOverviewPage() {
-
-
-
-
+ >
)
}
@@ -77,100 +72,3 @@ export function NavManagementPageSummary({ poolId }: { poolId: string }) {
/>
)
}
-
-export function NavOverviewCard({ poolId }: { poolId: string }) {
- const pool = usePool(poolId)
- const poolFees = usePoolFees(poolId)
- const today = new Date()
- today.setHours(0, 0, 0, 0)
- const { poolStates: dailyPoolStates } =
- useDailyPoolStates(poolId, new Date(new Date(pool.createdAt || today)), today) || {}
-
- const pendingFees = React.useMemo(() => {
- return new CurrencyBalance(
- poolFees?.map((f) => f.amounts.pending).reduce((acc, f) => acc.add(f), new BN(0)) ?? new BN(0),
- pool.currency.decimals
- )
- }, [poolFees, pool.currency.decimals])
-
- const changeInValuation = React.useMemo(() => {
- const lastUpdated = pool?.nav.lastUpdated || new Date()
- const lastUpdatedSumBorrowedAmountByPeriod = dailyPoolStates?.find(
- (state) => state.timestamp >= lastUpdated
- )?.sumBorrowedAmountByPeriod
- const todaySumBorrowedAmountByPeriod = dailyPoolStates?.[0]?.sumBorrowedAmountByPeriod
- return lastUpdatedSumBorrowedAmountByPeriod && todaySumBorrowedAmountByPeriod
- ? new BN(todaySumBorrowedAmountByPeriod).sub(new BN(lastUpdatedSumBorrowedAmountByPeriod))
- : new BN(0)
- }, [dailyPoolStates, pool?.nav.lastUpdated])
-
- return (
-
- )
-}
-
-export function VisualNavCard({
- currency,
- current,
- change,
- pendingFees,
- pendingNav,
-}: {
- currency: Pick
- current: number
- change: number
- pendingFees: number
- pendingNav: number
-}) {
- return (
-
-
-
- Current NAV
-
- {formatBalance(current, currency.displayName, 2)}
-
-
-
-
-
- Change in asset valuation
-
- = 0 ? 'statusOk' : 'statusCritical'}>
- {formatBalance(change, currency.displayName, 2)}
-
-
-
-
- Pending fees
-
-
- -{formatBalance(pendingFees, currency.displayName, 2)}
-
-
-
-
-
-
-
-
- Pending NAV
-
-
-
- {formatBalance(pendingNav, currency.displayName, 2)}
-
-
-
- )
-}