Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
onnovisser committed Oct 4, 2023
2 parents e396c36 + e947f53 commit 344b1fe
Show file tree
Hide file tree
Showing 24 changed files with 1,114 additions and 727 deletions.
45 changes: 45 additions & 0 deletions centrifuge-app/src/components/AssetSummary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Loan, TinlakeLoan } from '@centrifuge/centrifuge-js'
import { Box, Shelf, Stack, Text } from '@centrifuge/fabric'
import * as React from 'react'
import { useTheme } from 'styled-components'
import LoanLabel from './LoanLabel'

type Props = {
data?: {
label: React.ReactNode
value: React.ReactNode
}[]
children?: React.ReactNode
loan: Loan | TinlakeLoan
}

export const AssetSummary: React.FC<Props> = ({ data, children, loan }) => {
const theme = useTheme()
return (
<Stack bg={theme.colors.backgroundSecondary} pl={3}>
<Box paddingTop={3}>
<Shelf gap="2">
<Text variant="heading2">Details</Text>
<LoanLabel loan={loan} />
</Shelf>
</Box>
<Shelf
gap="6"
py="3"
style={{
boxShadow: `0 1px 0 ${theme.colors.borderSecondary}`,
}}
>
{data?.map(({ label, value }, index) => (
<Stack gap="4px" key={`${value}-${label}-${index}`}>
<Text variant="body3" style={{ fontWeight: 500 }}>
{label}
</Text>
<Text variant="body2">{value}</Text>
</Stack>
))}
{children}
</Shelf>
</Stack>
)
}
12 changes: 9 additions & 3 deletions centrifuge-app/src/components/LoanLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ interface Props {
loan: Loan | TinlakeLoan
}

export function getLoanLabelStatus(l: Loan | TinlakeLoan): [LabelStatus, string] {
export function getLoanLabelStatus(l: Loan | TinlakeLoan, isExternalAssetRepaid?: boolean): [LabelStatus, string] {
const today = new Date()
today.setUTCHours(0, 0, 0, 0)
if (l.status === 'Active' && (l as ActiveLoan).writeOffStatus) return ['critical', 'Write-off']
if (l.status === 'Closed') return ['ok', 'Repaid']
if (l.status === 'Closed' || isExternalAssetRepaid) return ['ok', 'Repaid']
if (
l.status === 'Active' &&
'interestRate' in l.pricing &&
Expand All @@ -38,7 +38,13 @@ export function getLoanLabelStatus(l: Loan | TinlakeLoan): [LabelStatus, string]
}

const LoanLabel: React.FC<Props> = ({ loan }) => {
const [status, text] = getLoanLabelStatus(loan)
const currentFace =
loan.pricing && 'outstandingQuantity' in loan.pricing
? loan.pricing.outstandingQuantity.toDecimal().mul(loan.pricing.notional.toDecimal())
: null

const isExternalAssetRepaid = currentFace?.isZero() && loan.status === 'Active'
const [status, text] = getLoanLabelStatus(loan, isExternalAssetRepaid)
return <StatusChip status={status}>{text}</StatusChip>
}

Expand Down
13 changes: 10 additions & 3 deletions centrifuge-app/src/components/LoanList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@ import { useCentNFT } from '../utils/useNFTs'
import { usePool, usePoolMetadata } from '../utils/usePools'
import { Column, DataTable, SortableTableHeader } from './DataTable'
import { LoadBoundary } from './LoadBoundary'
import LoanLabel, { getLoanLabelStatus } from './LoanLabel'
import LoanLabel from './LoanLabel'

type Row = (Loan | TinlakeLoan) & {
idSortKey: number
statusLabel: string
originationDateSortKey: string
}

Expand Down Expand Up @@ -120,7 +119,6 @@ export function LoanList({ loans }: Props) {

const rows: Row[] = loans.map((loan) => {
return {
statusLabel: getLoanLabelStatus(loan)[1],
nftIdSortKey: loan.asset.nftId,
idSortKey: parseInt(loan.id, 10),
outstandingDebtSortKey: loan.status !== 'Closed' && loan?.outstandingDebt?.toDecimal().toNumber(),
Expand Down Expand Up @@ -206,6 +204,11 @@ function Amount({ loan }: { loan: Row }) {
const pool = usePool(loan.poolId)
const { current } = useAvailableFinancing(loan.poolId, loan.id)

const currentFace =
loan?.pricing && 'outstandingQuantity' in loan.pricing
? loan.pricing.outstandingQuantity.toDecimal().mul(loan.pricing.notional.toDecimal())
: null

function getAmount(l: Row) {
switch (l.status) {
case 'Closed':
Expand All @@ -220,6 +223,10 @@ function Amount({ loan }: { loan: Row }) {
return formatBalance(l.totalRepaid, pool?.currency.symbol)
}

if ('valuationMethod' in loan.pricing && loan.pricing.valuationMethod === 'oracle' && currentFace) {
return formatBalance(currentFace, pool?.currency.symbol)
}

return formatBalance(l.outstandingDebt, pool?.currency.symbol)

default:
Expand Down
2 changes: 1 addition & 1 deletion centrifuge-app/src/components/PageSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const PageSection: React.FC<Props> = ({
{headerRight}
</Shelf>
)}
<Box pl={[0, 0, 4]}>{collapsible ? <Collapsible open={open}>{children}</Collapsible> : children}</Box>
<Box pl={[0, 0, 0]}>{collapsible ? <Collapsible open={open}>{children}</Collapsible> : children}</Box>
</Stack>
)
}
2 changes: 1 addition & 1 deletion centrifuge-app/src/components/Report/AssetList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const headers = [
'Total repaid',
'Financing date',
'Maturity date',
'Financing fee',
'Interest rate',
'Advance rate',
'PD',
'LGD',
Expand Down
24 changes: 18 additions & 6 deletions centrifuge-app/src/components/Tooltips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ export const tooltipText = {
label: 'Ongoing assets',
body: 'Number of assets currently being financed in the pool and awaiting repayment.',
},
averageFinancingFee: {
label: 'Average financing fee',
body: 'The average financing fee of the active assets in the pool.',
averageInterestRate: {
label: 'Average interest rate',
body: 'The average interest rate of the active assets in the pool.',
},
averageAmount: {
label: 'Average amount',
Expand Down Expand Up @@ -134,9 +134,9 @@ export const tooltipText = {
label: 'Advance rate',
body: 'The advance rate is the percentage amount of the value of the collateral that an issuer can borrow from the pool against the NFT representing the collateral.',
},
financingFee: {
label: 'Financing fee',
body: 'The financing fee is the rate at which the outstanding amount of an individual financing accrues interest. It is expressed as an "APR" (Annual Percentage Rate) and compounds interest every second.',
interestRate: {
label: 'Interest Rate',
body: 'The interest rate is the rate at which the outstanding amount of an individual financing accrues interest. It is expressed as an APR (Annual Percentage Rate) and compounds every second.',
},
probabilityOfDefault: {
label: 'Prob of default',
Expand Down Expand Up @@ -242,6 +242,18 @@ export const tooltipText = {
label: 'Extension period',
body: 'Number of days the maturity can be extended without restrictions.',
},
maxPriceVariation: {
label: 'Max price variation',
body: 'The maximum price variation defines the price difference between settlement and oracle price.',
},
isin: {
label: 'ISIN',
body: 'An International Securities Identification Number (ISIN) is a code that uniquely identifies a security globally for the purposes of facilitating clearing, reporting and settlement of trades.',
},
notionalValue: {
label: 'Notional value',
body: 'The notional value is the total value of the underlying asset.',
},
}

export type TooltipsProps = {
Expand Down
4 changes: 4 additions & 0 deletions centrifuge-app/src/pages/IssuerCreatePool/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import {
imageFile,
integer,
isin,
maturityDate,
max,
maxDecimals,
maxFileSize,
maxImageSize,
maxLength,
mimeType,
min,
minLength,
nonNegativeNumber,
pattern,
Expand Down Expand Up @@ -49,6 +51,8 @@ export const validate = {
minInvestment: combine(required(), nonNegativeNumber(), max(Number.MAX_SAFE_INTEGER)),
interestRate: combine(required(), positiveNumber(), max(Number.MAX_SAFE_INTEGER)),
minRiskBuffer: combine(required(), positiveNumber(), max(100)),
maxPriceVariation: combine(required(), min(0), max(10000)),
maturityDate: combine(required(), maturityDate()),

// risk groups
groupName: maxLength(30),
Expand Down
12 changes: 7 additions & 5 deletions centrifuge-app/src/pages/IssuerPool/Assets/CreateLoan.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CurrencyBalance, Rate } from '@centrifuge/centrifuge-js'
import { CurrencyBalance, Price, Rate } from '@centrifuge/centrifuge-js'
import {
formatBalance,
Transaction,
Expand Down Expand Up @@ -70,6 +70,7 @@ export type CreateLoanFormValues = {
maxBorrowQuantity: number | ''
Isin: string
notional: number | ''
maxPriceVariation: number | ''
}
}

Expand Down Expand Up @@ -224,6 +225,7 @@ function IssuerCreateLoan() {
maxBorrowQuantity: '',
Isin: '',
notional: '',
maxPriceVariation: '',
},
},
onSubmit: async (values, { setSubmitting }) => {
Expand All @@ -233,12 +235,12 @@ function IssuerCreateLoan() {
values.pricing.valuationMethod === 'oracle'
? {
valuationMethod: values.pricing.valuationMethod,
maxPriceVariation: Rate.fromPercent(values.pricing.maxPriceVariation),
maxBorrowAmount: values.pricing.maxBorrowQuantity
? CurrencyBalance.fromFloat(values.pricing.maxBorrowQuantity, decimals)
? Price.fromFloat(values.pricing.maxBorrowQuantity)
: null,
Isin: values.pricing.Isin || '',
maturityDate: new Date(values.pricing.maturityDate),
maturityExtensionDays: values.pricing.maturityExtensionDays,
interestRate: Rate.fromPercent(values.pricing.interestRate),
notional: CurrencyBalance.fromFloat(values.pricing.notional, decimals),
}
Expand All @@ -259,7 +261,7 @@ function IssuerCreateLoan() {

const tx: Transaction = {
id: txId,
title: 'Create document',
title: 'Create asset',
status: 'creating',
args: [],
}
Expand Down Expand Up @@ -325,7 +327,7 @@ function IssuerCreateLoan() {
doTransaction([submittable], undefined, txId)
} catch (e) {
console.error(e)
updateTransaction(txId, { status: 'failed', failedReason: 'Failed to create document NFT' })
updateTransaction(txId, { status: 'failed', failedReason: 'Failed to create asset' })
}

setSubmitting(false)
Expand Down
49 changes: 21 additions & 28 deletions centrifuge-app/src/pages/IssuerPool/Assets/PricingInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Field, FieldProps, useFormikContext } from 'formik'
import { FieldWithErrorMessage } from '../../../components/FieldWithErrorMessage'
import { Tooltips } from '../../../components/Tooltips'
import { usePool } from '../../../utils/usePools'
import { combine, max, positiveNumber, required } from '../../../utils/validation'
import { combine, max, nonNegativeNumber, positiveNumber, required } from '../../../utils/validation'
import { validate } from '../../IssuerCreatePool/validate'
import { CreateLoanFormValues } from './CreateLoan'

Expand All @@ -30,16 +30,9 @@ export function PricingInput({ poolId }: { poolId: string }) {
</Field>
{values.pricing.valuationMethod === 'oracle' && (
<>
{/* <FieldWithErrorMessage
as={NumberInput}
label={<Tooltips type="financingFee" variant="secondary" label="Max quantity*" />}
placeholder="0"
name="pricing.maxBorrowQuantity"
validate={validate.maxBorrowQuantity}
/> */}
<FieldWithErrorMessage
as={TextInput}
label={<Tooltips type="financingFee" variant="secondary" label="ISIN*" />}
label={<Tooltips type="isin" variant="secondary" label="ISIN*" />}
placeholder="010101010000"
name="pricing.Isin"
validate={validate.Isin}
Expand All @@ -48,15 +41,23 @@ export function PricingInput({ poolId }: { poolId: string }) {
{({ field, meta, form }: FieldProps) => (
<CurrencyInput
{...field}
label="Notional*"
label={<Tooltips type="notionalValue" variant="secondary" label="Notional value*" />}
placeholder="0.00"
errorMessage={meta.touched ? meta.error : undefined}
currency={pool?.currency.symbol}
onChange={(value) => form.setFieldValue('pricing.notional', value)}
variant="small"
currency={pool.currency.symbol}
/>
)}
</Field>
<FieldWithErrorMessage
as={NumberInput}
label={<Tooltips type="maxPriceVariation" variant="secondary" label="Max price variation*" />}
placeholder={0}
rightElement="%"
name="pricing.maxPriceVariation"
validate={validate.maxPriceVariation}
/>
</>
)}

Expand Down Expand Up @@ -94,9 +95,17 @@ export function PricingInput({ poolId }: { poolId: string }) {
</Field>
</>
)}
<FieldWithErrorMessage
as={NumberInput}
label={<Tooltips type="interestRate" variant="secondary" label="Interest rate*" />}
placeholder="0.00"
rightElement="%"
name="pricing.interestRate"
validate={combine(required(), nonNegativeNumber(), max(100))}
/>
<FieldWithErrorMessage
as={DateInput}
validate={required()}
validate={validate.maturityDate}
name="pricing.maturityDate"
label="Maturity date*"
type="date"
Expand All @@ -105,23 +114,7 @@ export function PricingInput({ poolId }: { poolId: string }) {
// Max 5 years from now
max={new Date(Date.now() + 5 * 365 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10)}
/>
<FieldWithErrorMessage
as={NumberInput}
label={<Tooltips type="maturityExtensionDays" variant="secondary" label="Extension period*" />}
placeholder={0}
rightElement="days"
name="pricing.maturityExtensionDays"
validate={validate.maturityExtensionDays}
/>

<FieldWithErrorMessage
as={NumberInput}
label={<Tooltips type="financingFee" variant="secondary" label="Financing fee*" />}
placeholder="0.00"
rightElement="%"
name="pricing.interestRate"
validate={validate.fee}
/>
{(values.pricing.valuationMethod === 'discountedCashFlow' ||
values.pricing.valuationMethod === 'outstandingDebt') && (
<>
Expand Down
Loading

0 comments on commit 344b1fe

Please sign in to comment.