Skip to content

Commit

Permalink
Repay form: pay interest first (#2072)
Browse files Browse the repository at this point in the history
  • Loading branch information
onnovisser authored May 2, 2024
1 parent 7585749 commit 468af1f
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 30 deletions.
27 changes: 19 additions & 8 deletions centrifuge-app/src/pages/Loan/RepayForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ActiveLoan, CurrencyBalance, findBalance } from '@centrifuge/centrifuge-js'
import { ActiveLoan, CurrencyBalance, Rate, findBalance } from '@centrifuge/centrifuge-js'
import { useBalances, useCentrifugeTransaction } from '@centrifuge/centrifuge-react'
import { Button, Card, CurrencyInput, InlineFeedback, Shelf, Stack, Text } from '@centrifuge/fabric'
import BN from 'bn.js'
Expand Down Expand Up @@ -63,14 +63,25 @@ function InternalRepayForm({ loan }: { loan: ActiveLoan }) {
amount: '',
},
onSubmit: (values, actions) => {
const outstandingPrincipal = loan.totalBorrowed.sub(loan.repaid.principal)
let amount: BN = CurrencyBalance.fromFloat(values.amount, pool.currency.decimals)
let interest = new BN(0)
if (amount.gt(outstandingPrincipal)) {
interest = amount.sub(outstandingPrincipal)
amount = outstandingPrincipal
// Pay the interest with a small margin first, then the principal
let interest: BN = CurrencyBalance.fromFloat(values.amount, pool.currency.decimals)
let principal = new BN(0)

// Calculate interest from the time the loan was fetched until now
const time = Date.now() - loan.fetchedAt.getTime()
const margin = CurrencyBalance.fromFloat(
loan.outstandingPrincipal
.toDecimal()
.mul(Rate.fractionFromApr(loan.pricing.interestRate.toDecimal()).toDecimal())
.mul(time),
pool.currency.decimals
)
const interestWithMargin = loan.outstandingInterest.add(margin)
if (interest.gt(interestWithMargin)) {
principal = interest.sub(interestWithMargin)
interest = interestWithMargin
}
doRepayTransaction([loan.poolId, loan.id, amount, interest, new BN(0)], { account, forceProxyType: 'Borrow' })
doRepayTransaction([loan.poolId, loan.id, principal, interest, new BN(0)], { account, forceProxyType: 'Borrow' })
actions.setSubmitting(false)
},
validateOnMount: true,
Expand Down
41 changes: 24 additions & 17 deletions centrifuge-app/src/pages/Loan/TransferDebtForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ export function TransferDebtForm({ loan }: { loan: LoanType }) {
let repay: any = { principal, interest }
if (isExternalLoan(selectedLoan)) {
const repayPriceBN = CurrencyBalance.fromFloat(form.values.targetLoanPrice || 1, pool.currency.decimals)
const repayQuantityBN = Price.fromFloat(Dec(values.targetLoanFaceValue || 0).div(selectedLoan.pricing.notional.toDecimal()))
const repayQuantityBN = Price.fromFloat(
Dec(values.targetLoanFaceValue || 0).div(selectedLoan.pricing.notional.toDecimal())
)
repay = { quantity: repayQuantityBN, price: repayPriceBN, interest }
}

Expand All @@ -97,26 +99,28 @@ export function TransferDebtForm({ loan }: { loan: LoanType }) {
})
actions.setSubmitting(false)
},
validate(values) {
validate(values) {
const financeAmount = isExternalLoan(loan)
? Dec(values.price || 0)
.mul(Dec(values.faceValue || 0))
.div(loan.pricing.notional.toDecimal())
: selectedLoan && isExternalLoan(selectedLoan) ? Dec(values.targetLoanPrice || 0)
.mul(Dec(values.targetLoanFaceValue || 0))
.div(selectedLoan.pricing.notional.toDecimal()): Dec(values.amount || 0)
: selectedLoan && isExternalLoan(selectedLoan)
? Dec(values.targetLoanPrice || 0)
.mul(Dec(values.targetLoanFaceValue || 0))
.div(selectedLoan.pricing.notional.toDecimal())
: Dec(values.amount || 0)

let errors: any = {}
const error = validate(financeAmount)

const error = validate(financeAmount)
if (error) {
if (selectedLoan && isExternalLoan(selectedLoan)) {
errors = setIn(errors, 'targetLoanPrice', error)
} else {
errors = setIn(errors, 'amount', error)
}
}

return errors
},
})
Expand Down Expand Up @@ -151,9 +155,11 @@ export function TransferDebtForm({ loan }: { loan: LoanType }) {
? Dec(form.values.price || 0)
.mul(Dec(form.values.faceValue || 0))
.div(loan.pricing.notional.toDecimal())
: selectedLoan && isExternalLoan(selectedLoan) ? Dec(form.values.targetLoanPrice || 0)
.mul(Dec(form.values.targetLoanFaceValue || 0))
.div(selectedLoan.pricing.notional.toDecimal()): Dec(form.values.amount || 0)
: selectedLoan && isExternalLoan(selectedLoan)
? Dec(form.values.targetLoanPrice || 0)
.mul(Dec(form.values.targetLoanFaceValue || 0))
.div(selectedLoan.pricing.notional.toDecimal())
: Dec(form.values.amount || 0)

return (
<Stack as={Card} gap={2} p={2}>
Expand Down Expand Up @@ -239,7 +245,10 @@ export function TransferDebtForm({ loan }: { loan: LoanType }) {
)
}}
</Field>
<Field name="targetLoanPrice" validate={combine(settlementPrice(), maxPriceVariance(selectedLoan.pricing))}>
<Field
name="targetLoanPrice"
validate={combine(settlementPrice(), maxPriceVariance(selectedLoan.pricing))}
>
{({ field, meta, form }: FieldProps) => {
return (
<CurrencyInput
Expand All @@ -255,9 +264,7 @@ export function TransferDebtForm({ loan }: { loan: LoanType }) {
</Field>
<Shelf justifyContent="space-between">
<Text variant="emphasized">Total amount</Text>
<Text variant="emphasized">
{formatBalance(financeAmount, pool?.currency.symbol, 2)}
</Text>
<Text variant="emphasized">{formatBalance(financeAmount, pool?.currency.symbol, 2)}</Text>
</Shelf>
</>
)}
Expand All @@ -276,8 +283,8 @@ function LoanOption({ loan }: { loan: Loan }) {
const nft = useCentNFT(loan.asset.collectionId, loan.asset.nftId, false, false)
const { data: metadata } = useMetadata(nft?.metadataUri, nftMetadataSchema)
return (
<option value={loan.id}>
<>
{loan.id} - {metadata?.name}
</option>
</>
)
}
4 changes: 2 additions & 2 deletions centrifuge-app/src/utils/useLoans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ export function useAvailableFinancing(poolId: string, assetId: string) {
if (loan.status !== 'Active') return { current: initialCeiling, initial: initialCeiling }

const debtWithMargin =
'interestRate' in loan.pricing
'interestRate' in loan.pricing && 'outstandingPrincipal' in loan
? loan.outstandingDebt
.toDecimal()
.add(loan.outstandingDebt.toDecimal().mul(loan.pricing.interestRate.toDecimal().div(365 * 8))) // Additional 3 hour interest as margin
.add(loan.outstandingPrincipal.toDecimal().mul(loan.pricing.interestRate.toDecimal().div(365 * 8))) // Additional 3 hour interest as margin
: Dec(0)

let ceiling = initialCeiling
Expand Down
12 changes: 9 additions & 3 deletions centrifuge-js/src/modules/pools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ export type TinlakeLoan = {
// transformed type for UI
export type CreatedLoan = {
status: 'Created'
fetchedAt: Date
id: string
poolId: string
pricing: PricingInfo
Expand All @@ -481,6 +482,7 @@ export type CreatedLoan = {
// transformed type for UI
export type ActiveLoan = {
status: 'Active'
fetchedAt: Date
id: string
poolId: string
pricing: PricingInfo
Expand Down Expand Up @@ -511,6 +513,7 @@ export type ActiveLoan = {
// transformed type for UI
export type ClosedLoan = {
status: 'Closed'
fetchedAt: Date
id: string
poolId: string
pricing: PricingInfo
Expand Down Expand Up @@ -3074,7 +3077,7 @@ export function getPoolsModule(inst: Centrifuge) {
filter(({ api, events }) => {
const event = events.find(
({ event }) =>
api.events.priceOracle.NewFeedData.is(event) ||
api.events.oraclePriceFeed.Fed.is(event) ||
api.events.loans.Created.is(event) ||
api.events.loans.Borrowed.is(event) ||
api.events.loans.Repaid.is(event) ||
Expand All @@ -3083,10 +3086,9 @@ export function getPoolsModule(inst: Centrifuge) {
api.events.loans.Closed.is(event) ||
api.events.loans.PortfolioValuationUpdated.is(event)
)

if (!event) return false

const { poolId: eventPoolId } = (event.toHuman() as any).event.data
if (!eventPoolId) return true
return eventPoolId.replace(/\D/g, '') === poolId
})
)
Expand Down Expand Up @@ -3181,6 +3183,10 @@ export function getPoolsModule(inst: Centrifuge) {
? pricingInfo.valuationMethod.discountedCashFlow
: undefined
return {
// Return the time the loans were fetched, in order to calculate a more accurate/up-to-date outstandingInterest
// Mainly for when repaying interest, to repay as close to the correct amount of interest
// Refetching before repaying would be another ideas, but less practical with substriptions
fetchedAt: new Date(),
asset: {
collectionId: collectionId.toString(),
nftId: nftId.toString(),
Expand Down

0 comments on commit 468af1f

Please sign in to comment.