diff --git a/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx b/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx index f8c7abf124..4230f7e431 100644 --- a/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx +++ b/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx @@ -70,7 +70,6 @@ function PoolPerformanceChart() { // querying chain for more accurate data, since data for today from subquery is not necessarily up to date const todayAssetValue = pool?.nav.total.toDecimal().toNumber() || 0 - const todayReserve = pool?.reserve.total.toDecimal().toNumber() || 0 const chartData = data.slice(-rangeNumber) diff --git a/centrifuge-app/src/components/Menu/NavManagementMenu.tsx b/centrifuge-app/src/components/Menu/NavManagementMenu.tsx index d5c54ce90a..3fb96ee2e6 100644 --- a/centrifuge-app/src/components/Menu/NavManagementMenu.tsx +++ b/centrifuge-app/src/components/Menu/NavManagementMenu.tsx @@ -2,7 +2,7 @@ import { Box, IconChevronDown, IconChevronRight, IconMonitor, Menu, MenuItemGrou import * as React from 'react' import { useRouteMatch } from 'react-router' import { useTheme } from 'styled-components' -import { usePoolsForWhichAccountIsFeeder } from '../../pages/NavManagement/Overview' +import { usePoolsForWhichAccountIsFeeder } from '../../utils/usePoolsForWhichAccountIsFeeder' import { PoolLink } from './PoolLink' import { Toggle } from './Toggle' @@ -22,7 +22,7 @@ export function NavManagementMenu({ stacked }: NavManagementMenuProps) { return ( allowedPools && - allowedPools?.length > 1 && ( + allowedPools?.length >= 1 && ( {open && ( - + NAV management - {!stacked && (open ? : )} + {!stacked && (open ? : )} { - const [transferToMultisig, aoProxy, adminProxy, , , , , , { adminMultisig }] = args + const [transferToMultisig, aoProxy, adminProxy, , , , , { adminMultisig }] = args const multisigAddr = adminMultisig && createKeyMulti(adminMultisig.signers, adminMultisig.threshold) const poolArgs = args.slice(2) as any return combineLatest([ @@ -403,7 +402,6 @@ function CreatePoolForm() { const currency = currencies.find((c) => c.symbol === values.currency)! const poolId = await centrifuge.pools.getAvailablePoolId() - const collectionId = await centrifuge.nfts.getAvailableCollectionId() if (!values.poolIcon || !values.executiveSummary) { return } @@ -479,7 +477,6 @@ function CreatePoolForm() { aoProxy, adminProxy, poolId, - collectionId, tranches, currency.key, CurrencyBalance.fromFloat(values.maxReserve, currency.decimals), diff --git a/centrifuge-app/src/pages/MultisigApproval.tsx b/centrifuge-app/src/pages/MultisigApproval.tsx index b66e51b769..96e9beea71 100644 --- a/centrifuge-app/src/pages/MultisigApproval.tsx +++ b/centrifuge-app/src/pages/MultisigApproval.tsx @@ -46,11 +46,35 @@ export default function MultisigApprovalPage() { const suitableAccount = accounts?.find((acc) => multisig.signers.includes(acc.address)) return ( - + + + {isCallDataNeeded && !callString && ( + <> + setCallFormInput(e.target.value)} + /> + {callInputError && ( + + Calldata doesn't match hash + + )} + + )} + {callString && ( +
+ + Call details + + + {callString} + +
+ )} + {selectedAddress && !multisig.signers.includes(selectedAddress) ? ( selected account not signer to multisig {suitableAccount && ( @@ -63,35 +87,8 @@ export default function MultisigApprovalPage() { - ) - } - > - - {isCallDataNeeded && !callString && ( - <> - setCallFormInput(e.target.value)} - /> - {callInputError && ( - - Calldata doesn't match hash - - )} - - )} - {callString && ( -
- - Call details - - - {callString} - -
- )} + )} +
) diff --git a/centrifuge-app/src/pages/NavManagement/NavManagementAssetTable.tsx b/centrifuge-app/src/pages/NavManagement/NavManagementAssetTable.tsx new file mode 100644 index 0000000000..2fdd41b066 --- /dev/null +++ b/centrifuge-app/src/pages/NavManagement/NavManagementAssetTable.tsx @@ -0,0 +1,291 @@ +import { ActiveLoan, CreatedLoan, CurrencyBalance, ExternalLoan } from '@centrifuge/centrifuge-js' +import { useCentrifugeApi, useCentrifugeQuery, useCentrifugeTransaction } from '@centrifuge/centrifuge-react' +import { Box, Button, CurrencyInput, Drawer, IconDownload, Shelf, Text, Thumbnail } from '@centrifuge/fabric' +import { Field, FieldProps, FormikProvider, useFormik } from 'formik' +import * as React from 'react' +import daiLogo from '../../assets/images/dai-logo.svg' +import usdcLogo from '../../assets/images/usdc-logo.svg' +import { ButtonGroup } from '../../components/ButtonGroup' +import { DataCol, DataRow, DataTable } from '../../components/DataTable' +import { LayoutSection } from '../../components/LayoutBase/LayoutSection' +import { AssetName } from '../../components/LoanList' +import { formatDate } from '../../utils/date' +import { formatBalance } from '../../utils/formatting' +import { usePool } from '../../utils/usePools' +import { usePoolsForWhichAccountIsFeeder } from '../../utils/usePoolsForWhichAccountIsFeeder' +import { settlementPrice } from '../../utils/validation' +import { isCashLoan, isExternalLoan } from '../Loan/utils' + +type FormValues = { + feed: { + formIndex: number + id: string + oldValue: number + value: number | '' + Isin: string + quantity: number + maturity: string + currentPrice: number + }[] + closeEpoch: boolean +} +type Row = FormValues['feed'][0] | ActiveLoan | CreatedLoan + +export function NavManagementAssetTable({ poolId }: { poolId: string }) { + const allowedPools = usePoolsForWhichAccountIsFeeder() + const isFeeder = !!allowedPools?.find((p) => p.id === poolId) + const [isEditing, setIsEditing] = React.useState(false) + const [isConfirming, setIsConfirming] = React.useState(false) + + const pool = usePool(poolId, false) + const [allLoans] = useCentrifugeQuery(['loans', poolId], (cent) => cent.pools.getLoans([poolId]), { + enabled: !!poolId && !!pool, + }) + + const externalLoans = React.useMemo( + () => (allLoans?.filter((l) => isExternalLoan(l) && l.status !== 'Closed') as ExternalLoan[]) ?? [], + [allLoans] + ) + const cashLoans = + (allLoans?.filter((l) => isCashLoan(l) && l.status !== 'Closed') as (CreatedLoan | ActiveLoan)[]) ?? [] + const api = useCentrifugeApi() + + const reserveRow = [ + { + id: 'reserve', + Isin: 'Reserve', + quantity: 1, + currentPrice: 0, + value: pool?.reserve.total.toDecimal().toNumber(), + formIndex: -1, + maturity: '', + oldValue: '', + }, + ] + + const { execute, isLoading } = useCentrifugeTransaction( + 'Set oracle prices', + (cent) => (args: [values: FormValues], options) => { + const [values] = args + const batch = [ + ...values.feed + .filter((f) => typeof f.value === 'number' && !Number.isNaN(f.value)) + .map((f) => api.tx.oraclePriceFeed.feed({ Isin: f.Isin }, CurrencyBalance.fromFloat(f.value, 18))), + api.tx.oraclePriceCollection.updateCollection(poolId), + api.tx.loans.updatePortfolioValuation(poolId), + ] + if (values.closeEpoch) { + batch.push(api.tx.poolSystem.closeEpoch(poolId)) + } + const tx = api.tx.utility.batchAll(batch) + return cent.wrapSignAndSend(api, tx, options) + } + ) + + const initialValues = React.useMemo( + () => ({ + feed: + externalLoans?.map((l, i) => { + let latestOraclePrice = l.pricing.oracle[0] + l.pricing.oracle.forEach((price) => { + if (price.timestamp > latestOraclePrice.timestamp) { + latestOraclePrice = price + } + }) + return { + formIndex: i, + id: l.id, + oldValue: latestOraclePrice.value.toFloat(), + value: '' as const, + Isin: l.pricing.Isin, + quantity: l.pricing.outstandingQuantity.toFloat(), + maturity: formatDate(l.pricing.maturityDate), + currentPrice: l.status === 'Active' ? l?.currentPrice.toDecimal().toNumber() : 0, + } + }) ?? [], + closeEpoch: false, + }), + [externalLoans] + ) + + const form = useFormik({ + initialValues, + onSubmit(values, actions) { + execute([values]) + actions.setSubmitting(false) + }, + }) + + React.useEffect(() => { + if (isEditing && !isLoading) return + form.resetForm() + form.setValues(initialValues, false) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [initialValues, isEditing, isLoading]) + + const poolReserve = pool?.reserve.total.toDecimal().toNumber() || 0 + const newNavExternal = form.values.feed.reduce( + (acc, cur) => acc + cur.quantity * (isEditing ? cur.currentPrice : cur.value || cur.oldValue), + 0 + ) + const newNavCash = cashLoans.reduce((acc, cur) => acc + cur.outstandingDebt.toFloat(), 0) + const newNav = newNavExternal + newNavCash + poolReserve + const isTinlakePool = poolId.startsWith('0x') + + const columns = [ + { + align: 'left', + header: 'Asset', + cell: (row: Row) => + 'oldValue' in row ? ( + + {row.id === 'reserve' ? ( + + + + ) : ( + + )} + + {row.Isin} + + + ) : ( + + + + ), + }, + { + align: 'left', + header: 'Maturity date', + cell: (row: Row) => ('oldValue' in row ? row.maturity : ''), + }, + { + align: 'right', + header: 'Quantity', + cell: (row: Row) => + row.id !== 'reserve' ? formatBalance('oldValue' in row ? row.quantity : row.outstandingDebt) : '', + }, + { + align: 'right', + header: 'Asset price', + cell: (row: Row) => + row.id !== 'reserve' ? formatBalance('oldValue' in row ? row.oldValue : 1, pool?.currency.symbol, 8) : '', + }, + { + align: 'right', + header: 'New price', + cell: (row: Row) => { + return 'oldValue' in row && row.id !== 'reserve' ? ( + + {({ field, meta, form }: FieldProps) => ( + form.setFieldValue(`feed.${row.formIndex}.value`, value)} + value={row.currentPrice} + onClick={(e) => e.preventDefault()} + /> + )} + + ) : ( + '' + ) + }, + }, + { + align: 'right', + header: 'Value', + cell: (row: Row) => + 'oldValue' in row + ? formatBalance(row.quantity * (row.value || row.oldValue), pool?.currency.symbol) + : formatBalance(row.outstandingDebt, pool?.currency.symbol), + }, + ] + + if (!isEditing) { + columns.splice(4, 1) + } + + return ( + <> + + setIsConfirming(false)}> + + + + + + + + + + ) : ( + + + + + ) + } + > + + row.id !== 'reserve' ? `/issuer/${pool?.id}/assets/${row.id}` : `/nav-management/${pool?.id}` + } + footer={ + + + + Total + + + + + + {isEditing && } + + + {formatBalance(newNav, pool?.currency.symbol)} + + + + } + /> + + + + ) +} diff --git a/centrifuge-app/src/pages/NavManagement/Overview.tsx b/centrifuge-app/src/pages/NavManagement/Overview.tsx index 7451646715..77d691ea68 100644 --- a/centrifuge-app/src/pages/NavManagement/Overview.tsx +++ b/centrifuge-app/src/pages/NavManagement/Overview.tsx @@ -1,28 +1,15 @@ -import { ActiveLoan, addressToHex, CreatedLoan, CurrencyBalance, ExternalLoan } from '@centrifuge/centrifuge-js' -import { - useAddress, - useCentrifugeApi, - useCentrifugeQuery, - useCentrifugeTransaction, -} from '@centrifuge/centrifuge-react' -import { Button, CurrencyInput, Drawer, IconDownload, Shelf, Text, Thumbnail } from '@centrifuge/fabric' -import { Field, FieldProps, FormikProvider, useFormik } from 'formik' -import * as React from 'react' import { useParams } from 'react-router' -import { map } from 'rxjs' -import { ButtonGroup } from '../../components/ButtonGroup' -import { DataCol, DataRow, DataTable } from '../../components/DataTable' import { LayoutBase } from '../../components/LayoutBase' -import { LayoutSection } from '../../components/LayoutBase/LayoutSection' -import { AssetName } from '../../components/LoanList' import { PageSummary } from '../../components/PageSummary' import { Tooltips } from '../../components/Tooltips' -import { isSubstrateAddress } from '../../utils/address' -import { formatDate } from '../../utils/date' import { formatBalance } from '../../utils/formatting' -import { usePool, usePoolMetadata, usePools } from '../../utils/usePools' -import { settlementPrice } from '../../utils/validation' -import { isCashLoan, isExternalLoan } from '../Loan/utils' +import { useDailyPoolStates, usePool } from '../../utils/usePools' + +import { CurrencyBalance } from '@centrifuge/centrifuge-js' +import { Box, Divider, IconClockForward, Shelf, Stack, Text } from '@centrifuge/fabric' +import { BN } from 'bn.js' +import React from 'react' +import { NavManagementAssetTable } from './NavManagementAssetTable' import { NavManagementHeader } from './NavManagementHeader' export default function NavManagementOverviewPage() { @@ -30,311 +17,137 @@ export default function NavManagementOverviewPage() { return ( - + + + ) } -type FormValues = { - feed: { - formIndex: number - id: string - oldValue: number - value: number | '' - Isin: string - quantity: number - maturity: string - }[] - closeEpoch: boolean -} -type Row = FormValues['feed'][0] | ActiveLoan | CreatedLoan - -function NavManagement({ poolId }: { poolId: string }) { - const allowedPools = usePoolsForWhichAccountIsFeeder() - const isFeeder = !!allowedPools?.find((p) => p.id === poolId) - const [isEditing, setIsEditing] = React.useState(false) - const [isConfirming, setIsConfirming] = React.useState(false) - - const pool = usePool(poolId, false) - const [allLoans] = useCentrifugeQuery(['loans', poolId], (cent) => cent.pools.getLoans([poolId]), { - enabled: !!poolId && !!pool, - }) - - const externalLoans = React.useMemo( - () => (allLoans?.filter((l) => isExternalLoan(l) && l.status !== 'Closed') as ExternalLoan[]) ?? [], - [allLoans] - ) - const cashLoans = - (allLoans?.filter((l) => isCashLoan(l) && l.status !== 'Closed') as (CreatedLoan | ActiveLoan)[]) ?? [] - const api = useCentrifugeApi() - - const { execute, isLoading } = useCentrifugeTransaction( - 'Set oracle prices', - (cent) => (args: [values: FormValues], options) => { - const [values] = args - const batch = [ - ...values.feed - .filter((f) => typeof f.value === 'number' && !Number.isNaN(f.value)) - .map((f) => api.tx.oraclePriceFeed.feed({ Isin: f.Isin }, CurrencyBalance.fromFloat(f.value, 18))), - api.tx.oraclePriceCollection.updateCollection(poolId), - api.tx.loans.updatePortfolioValuation(poolId), - ] - if (values.closeEpoch) { - batch.push(api.tx.poolSystem.closeEpoch(poolId)) - } - const tx = api.tx.utility.batchAll(batch) - return cent.wrapSignAndSend(api, tx, options) - } - ) - - const initialValues = React.useMemo( - () => ({ - feed: - externalLoans?.map((l, i) => { - let latestOraclePrice = l.pricing.oracle[0] - l.pricing.oracle.forEach((price) => { - if (price.timestamp > latestOraclePrice.timestamp) { - latestOraclePrice = price - } - }) - return { - formIndex: i, - id: l.id, - oldValue: latestOraclePrice.value.toFloat(), - value: '' as const, - Isin: l.pricing.Isin, - quantity: l.pricing.outstandingQuantity.toFloat(), - maturity: formatDate(l.pricing.maturityDate), - } - }) ?? [], - closeEpoch: false, - }), - [externalLoans] - ) - - const form = useFormik({ - initialValues, - onSubmit(values, actions) { - execute([values]) - actions.setSubmitting(false) - }, - }) - - React.useEffect(() => { - if (isEditing && !isLoading) return - form.resetForm() - form.setValues(initialValues, false) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [initialValues, isEditing]) - - const newNavExternal = form.values.feed.reduce((acc, cur) => acc + cur.quantity * (cur.value || cur.oldValue), 0) - const newNavCash = cashLoans.reduce((acc, cur) => acc + cur.outstandingDebt.toFloat(), 0) - const newNav = newNavExternal + newNavCash - - const columns = [ - { - align: 'left', - header: 'Asset', - cell: (row: Row) => - 'oldValue' in row ? ( - - - - {row.Isin} - - - ) : ( - - - - ), - }, - { - align: 'left', - header: 'Maturity date', - cell: (row: Row) => ('oldValue' in row ? row.maturity : ''), - }, - { - align: 'right', - header: 'Quantity', - cell: (row: Row) => formatBalance('oldValue' in row ? row.quantity : row.outstandingDebt), - }, - { - align: 'right', - header: 'Asset price', - cell: (row: Row) => formatBalance('oldValue' in row ? row.oldValue : 1, pool?.currency.symbol, 8), - }, - { - align: 'right', - header: 'New price', - cell: (row: Row) => - 'oldValue' in row - ? (console.log('row.formIndex', row.formIndex), - ( - - {({ field, meta, form }: FieldProps) => ( - form.setFieldValue(`feed.${row.formIndex}.value`, value)} - /> - )} - - )) - : '', - }, - { - align: 'right', - header: 'Value', - cell: (row: Row) => - 'oldValue' in row - ? formatBalance(row.quantity * (row.value || row.oldValue), pool?.currency.symbol) - : formatBalance(row.outstandingDebt, pool?.currency.symbol), - }, - ] +export const NavManagementPageSummary = ({ poolId }: { poolId: string }) => { + const pool = usePool(poolId) + const today = new Date() + today.setHours(0, 0, 0, 0) + const dailyPoolStates = useDailyPoolStates(poolId, new Date(pool.createdAt || today), today) + const investments = + pool && + dailyPoolStates?.poolStates?.reduce( + (acc, state) => + state && state?.sumInvestedAmountByPeriod ? acc.add(new BN(state.sumInvestedAmountByPeriod)) : new BN(0), + new BN(0) + ) - if (!isEditing) { - columns.splice(4, 1) - } + const redemptions = + pool && + dailyPoolStates?.poolStates?.reduce( + (acc, state) => + state && state?.sumRedeemedAmountByPeriod ? acc.add(new BN(state.sumRedeemedAmountByPeriod)) : new BN(0), + new BN(0) + ) return ( - - , - value: formatBalance(pool?.nav.latest ?? 0, pool?.currency.symbol), - }, - ]} - /> - setIsConfirming(false)}> - - - - - - - - - - ) : ( - - - - - ) - } - > - - - - Total - - - - - - {isEditing && } - - - {formatBalance(newNav, pool?.currency.symbol)} - - - - } - /> - - + , + value: formatBalance(pool?.nav.total ?? 0, pool?.currency.symbol, 2), + }, + { + label: 'Investments', + value: formatBalance( + new CurrencyBalance(investments ?? 0, pool?.currency.decimals || 18), + pool?.currency.symbol, + 2 + ), + }, + { + label: 'Redemptions', + value: formatBalance(new CurrencyBalance(redemptions ?? 0, pool.currency.decimals), pool?.currency.symbol, 2), + }, + ]} + /> ) } -function PoolName({ poolId }: { poolId: string }) { +export const NavOverviewCard = ({ poolId }: { poolId: string }) => { const pool = usePool(poolId) - const { data: metadata } = usePoolMetadata(pool) - return metadata?.pool?.name || poolId -} - -function usePoolFeeders() { - const api = useCentrifugeApi() - const [storedInfo] = useCentrifugeQuery(['oracleCollectionInfos'], () => - api.query.oraclePriceCollection.collectionInfo.entries().pipe( - map((data) => { - const poolsByFeeder: Record = {} - const feedersByPool: Record = {} - data.forEach(([keys, value]) => { - const poolId = (keys.toHuman() as string[])[0].replace(/\D/g, '') - const info = value.toPrimitive() as any - const feeders = info.feeders - .filter((f: any) => !!f.system.signed) - .map((f: any) => addressToHex(f.system.signed)) as string[] - - feeders.forEach((feeder) => { - if (poolsByFeeder[feeder]) { - poolsByFeeder[feeder].push(poolId) - } else { - poolsByFeeder[feeder] = [poolId] - } - }) - - feedersByPool[poolId] = { - valueLifetime: info.valueLifetime as number, - minFeeders: info.minFeeders as number, - feeders, - } - }) - - return { - poolsByFeeder, - feedersByPool, - } - }) + 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( + pool?.poolFees?.map((f) => f.amounts.pending).reduce((acc, f) => acc.add(f), new BN(0)) ?? new BN(0), + pool.currency.decimals ) + }, [pool.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]) + + const pendingNav = React.useMemo(() => { + return dailyPoolStates && dailyPoolStates?.length + ? new BN(dailyPoolStates.reverse()[0].portfolioValuation).add(pool.reserve.total) + : new BN(0) + }, [dailyPoolStates, pool.reserve.total]) + return ( + + + + + Current NAV + + {formatBalance(pool?.nav.total, pool.currency.displayName, 2)} + + + + + Change in asset valuation + + + {formatBalance( + changeInValuation ? new CurrencyBalance(changeInValuation, pool.currency.decimals) : 0, + pool.currency.displayName, + 2 + )} + + + + + Pending fees + + + -{formatBalance(pendingFees, pool.currency.displayName, 2)} + + + + + + + + Pending NAV + + + + {formatBalance(new CurrencyBalance(pendingNav, pool.currency.decimals), pool.currency.displayName, 2)} + + + + ) - - return { - poolsByFeeder: storedInfo?.poolsByFeeder ?? {}, - feedersByPool: storedInfo?.feedersByPool ?? {}, - } -} - -export function usePoolsForWhichAccountIsFeeder(address?: string) { - const defaultAddress = useAddress('substrate') - address ??= defaultAddress - const { poolsByFeeder } = usePoolFeeders() - const poolIds = (address && isSubstrateAddress(address) && poolsByFeeder[address]) || [] - return usePools()?.filter((p) => poolIds.includes(p.id)) } diff --git a/centrifuge-app/src/utils/useCreatePoolFee.ts b/centrifuge-app/src/utils/useCreatePoolFee.ts index 0dc88bbb51..1611eb26bc 100644 --- a/centrifuge-app/src/utils/useCreatePoolFee.ts +++ b/centrifuge-app/src/utils/useCreatePoolFee.ts @@ -2,7 +2,7 @@ import Centrifuge, { CurrencyBalance, Perquintill, PoolMetadataInput, Rate } fro import { useCentrifuge, useCentrifugeConsts, useWallet } from '@centrifuge/centrifuge-react' import BN from 'bn.js' import * as React from 'react' -import { combineLatest, map, of, Subject, switchMap } from 'rxjs' +import { Subject, combineLatest, map, of, switchMap } from 'rxjs' import { config } from '../config' import { useCurrencies } from './useCurrencies' @@ -115,12 +115,11 @@ export function useCreatePoolFee(formValues: Pick + api.query.oraclePriceCollection.collectionInfo.entries().pipe( + map((data) => { + const poolsByFeeder: Record = {} + const feedersByPool: Record = {} + data.forEach(([keys, value]) => { + const poolId = (keys.toHuman() as string[])[0].replace(/\D/g, '') + const info = value.toPrimitive() as any + const feeders = info.feeders + .filter((f: any) => !!f.system.signed) + .map((f: any) => addressToHex(f.system.signed)) as string[] + + feeders.forEach((feeder) => { + if (poolsByFeeder[feeder]) { + poolsByFeeder[feeder].push(poolId) + } else { + poolsByFeeder[feeder] = [poolId] + } + }) + + feedersByPool[poolId] = { + valueLifetime: info.valueLifetime as number, + minFeeders: info.minFeeders as number, + feeders, + } + }) + + return { + poolsByFeeder, + feedersByPool, + } + }) + ) + ) + + return { + poolsByFeeder: storedInfo?.poolsByFeeder ?? {}, + feedersByPool: storedInfo?.feedersByPool ?? {}, + } +} + +export function usePoolsForWhichAccountIsFeeder(address?: string) { + const defaultAddress = useAddress('substrate') + address ??= defaultAddress + const { poolsByFeeder } = usePoolFeeders() + const poolIds = (address && isSubstrateAddress(address) && poolsByFeeder[address]) || [] + return usePools()?.filter((p) => poolIds.includes(p.id)) +} diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index 63d53e14b8..2980c20404 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -508,6 +508,7 @@ export type ActiveLoan = { outstandingPrincipal: CurrencyBalance outstandingInterest: CurrencyBalance presentValue: CurrencyBalance + currentPrice: CurrencyBalance // may not actually be set yet, this is what the price should be } // transformed type for UI @@ -890,7 +891,6 @@ export function getPoolsModule(inst: Centrifuge) { args: [ admin: string, poolId: string, - collectionId: string, tranches: TrancheInput[], currency: CurrencyKey, maxReserve: BN, @@ -899,7 +899,7 @@ export function getPoolsModule(inst: Centrifuge) { ], options?: TransactionOptions ) { - const [admin, poolId, , tranches, currency, maxReserve, metadata, fees] = args + const [admin, poolId, tranches, currency, maxReserve, metadata, fees] = args const trancheInput = tranches.map((t, i) => ({ trancheType: t.interestRatePerSec ? { @@ -3171,6 +3171,7 @@ export function getPoolsModule(inst: Centrifuge) { presentValue: CurrencyBalance outstandingPrincipal: CurrencyBalance outstandingInterest: CurrencyBalance + currentPrice: CurrencyBalance } > = {} @@ -3180,6 +3181,7 @@ export function getPoolsModule(inst: Centrifuge) { presentValue: new CurrencyBalance(data.presentValue, currency.decimals), outstandingPrincipal: new CurrencyBalance(data.outstandingPrincipal, currency.decimals), outstandingInterest: new CurrencyBalance(data.outstandingInterest, currency.decimals), + currentPrice: new CurrencyBalance(data.currentPrice ?? 0, currency.decimals), } }) @@ -3331,6 +3333,7 @@ export function getPoolsModule(inst: Centrifuge) { outstandingPrincipal: portfolio.outstandingPrincipal, outstandingInterest: portfolio.outstandingInterest, presentValue: portfolio.presentValue, + currentPrice: portfolio.currentPrice, } } )