From c9d9a7f2abbbf1ca37933c4e4a4af40490ba54a0 Mon Sep 17 00:00:00 2001 From: sophian Date: Thu, 8 Aug 2024 18:14:44 -0400 Subject: [PATCH] Add tooltips, improve spacing in summary and attempt to charge margin on interest --- centrifuge-app/src/components/Tooltips.tsx | 8 ++ .../src/pages/Loan/ExternalRepayForm.tsx | 43 ++++----- centrifuge-app/src/pages/Loan/RepayForm.tsx | 90 +++++++++++++------ 3 files changed, 88 insertions(+), 53 deletions(-) diff --git a/centrifuge-app/src/components/Tooltips.tsx b/centrifuge-app/src/components/Tooltips.tsx index 44163a686..2b51b6bf3 100644 --- a/centrifuge-app/src/components/Tooltips.tsx +++ b/centrifuge-app/src/components/Tooltips.tsx @@ -318,6 +318,14 @@ export const tooltipText = { label: 'Additional amount', body: 'This can be used to repay an additional amount beyond the outstanding principal and interest of the asset. This will lead to an increase in the NAV of the pool.', }, + repayFormAvailableBalance: { + label: 'Available balance', + body: 'Balance of the asset originator account on Centrifuge.', + }, + repayFormAvailableBalanceUnlimited: { + label: 'Available balance', + body: 'Unlimited because this is a virtual accounting process.', + }, } export type TooltipsProps = { diff --git a/centrifuge-app/src/pages/Loan/ExternalRepayForm.tsx b/centrifuge-app/src/pages/Loan/ExternalRepayForm.tsx index 8f585e0f1..cf3f5f00c 100644 --- a/centrifuge-app/src/pages/Loan/ExternalRepayForm.tsx +++ b/centrifuge-app/src/pages/Loan/ExternalRepayForm.tsx @@ -5,7 +5,7 @@ import { useCentrifugeUtils, wrapProxyCallsForAccount, } from '@centrifuge/centrifuge-react' -import { Box, Button, CurrencyInput, InlineFeedback, Shelf, Stack, Text, Tooltip } from '@centrifuge/fabric' +import { Box, Button, CurrencyInput, InlineFeedback, Shelf, Stack, Text } from '@centrifuge/fabric' import { BN } from 'bn.js' import Decimal from 'decimal.js-light' import { Field, FieldProps, Form, FormikProvider, useFormik } from 'formik' @@ -282,35 +282,28 @@ export function ExternalRepayForm({ loan, destination }: { loan: ExternalLoan; d )} - Transaction summary - - - Available balance - - - - {maxAvailable === UNLIMITED ? 'No limit' : formatBalance(maxAvailable, displayCurrency, 2)} - - - - + Transaction summary - - Sale amount + + + {maxAvailable === UNLIMITED ? 'No limit' : formatBalance(maxAvailable, displayCurrency, 2)} - {formatBalance(totalRepay, displayCurrency, 2)} - - {poolFees.renderSummary()} + + + + Sale amount + + {formatBalance(totalRepay, displayCurrency, 2)} + + + + {poolFees.renderSummary()} + {destination === 'reserve' ? ( diff --git a/centrifuge-app/src/pages/Loan/RepayForm.tsx b/centrifuge-app/src/pages/Loan/RepayForm.tsx index 161aa25ae..ac2f2dbc5 100644 --- a/centrifuge-app/src/pages/Loan/RepayForm.tsx +++ b/centrifuge-app/src/pages/Loan/RepayForm.tsx @@ -1,4 +1,12 @@ -import { ActiveLoan, CreatedLoan, CurrencyBalance, ExternalLoan, findBalance, Loan } from '@centrifuge/centrifuge-js' +import { + ActiveLoan, + CreatedLoan, + CurrencyBalance, + ExternalLoan, + findBalance, + Loan, + Rate, +} from '@centrifuge/centrifuge-js' import { useBalances, useCentrifugeApi, @@ -6,7 +14,7 @@ import { useCentrifugeUtils, wrapProxyCallsForAccount, } from '@centrifuge/centrifuge-react' -import { Box, Button, CurrencyInput, InlineFeedback, Select, Shelf, Stack, Text, Tooltip } from '@centrifuge/fabric' +import { Box, Button, CurrencyInput, InlineFeedback, Select, Shelf, Stack, Text } from '@centrifuge/fabric' import Decimal from 'decimal.js-light' import { Field, FieldProps, Form, FormikProvider, useFormik } from 'formik' import * as React from 'react' @@ -75,6 +83,7 @@ function InternalRepayForm({ loan, destination }: { loan: ActiveLoan | CreatedLo const destinationLoan = loans?.find((l) => l.id === destination) as Loan const displayCurrency = destination === 'reserve' ? pool.currency.symbol : 'USD' const utils = useCentrifugeUtils() + const [usedMaxInterest, setUsedMaxInterest] = React.useState(false) const { execute: doRepayTransaction, isLoading: isRepayLoading } = useCentrifugeTransaction( isCashLoan(loan) ? 'Withdraw funds' : 'Repay asset', @@ -127,10 +136,37 @@ function InternalRepayForm({ loan, destination }: { loan: ActiveLoan | CreatedLo category: 'correction', }, onSubmit: (values, actions) => { - const interest = CurrencyBalance.fromFloat(values.interest || 0, pool.currency.decimals) + let interest = CurrencyBalance.fromFloat(values.interest || 0, pool.currency.decimals) const additionalAmount = CurrencyBalance.fromFloat(values.amountAdditional || 0, pool.currency.decimals) const principal = CurrencyBalance.fromFloat(values.principal || 0, pool.currency.decimals) + if (usedMaxInterest) { + const time = Date.now() - loan.fetchedAt.getTime() + const outstandingInterest = + 'outstandingInterest' in loan + ? loan.outstandingInterest + : CurrencyBalance.fromFloat(0, pool.currency.decimals) + const outstandingPrincipal = new CurrencyBalance( + loan.outstandingDebt.sub(outstandingInterest), + pool.currency.decimals + ) + + // TODO: this number is way too small, after repaying we still end up with interest outstanding + const mostUpToDateInterest = CurrencyBalance.fromFloat( + outstandingPrincipal + .toDecimal() + .mul(Rate.fractionFromAprPercent(loan.pricing.interestRate.toDecimal().mul(100)).toDecimal()) + .mul(time / (60 * 60 * 24 * 365)) + .add(outstandingInterest.toDecimal()), + pool.currency.decimals + ) + interest = mostUpToDateInterest + console.log( + `Repaying with the most up to date outstanding interest: ${mostUpToDateInterest.toDecimal()} instead of ${outstandingInterest.toDecimal()}`, + loan.pricing.interestRate.toDecimal().toString() + ) + } + doRepayTransaction([principal, interest, additionalAmount], { account, forceProxyType: 'Borrow', @@ -193,7 +229,9 @@ function InternalRepayForm({ loan, destination }: { loan: ActiveLoan | CreatedLo disabled={isRepayLoading} currency={displayCurrency} onChange={(value) => form.setFieldValue('principal', value)} - onSetMax={() => form.setFieldValue('principal', maxPrincipal.gte(0) ? maxPrincipal : 0)} + onSetMax={() => { + form.setFieldValue('principal', maxPrincipal.gte(0) ? maxPrincipal : 0) + }} secondaryLabel={`${formatBalance(maxPrincipal, displayCurrency)} outstanding`} /> ) @@ -217,7 +255,10 @@ function InternalRepayForm({ loan, destination }: { loan: ActiveLoan | CreatedLo disabled={isRepayLoading} currency={displayCurrency} onChange={(value) => form.setFieldValue('interest', value)} - onSetMax={() => form.setFieldValue('interest', maxInterest.gte(0) ? maxInterest : 0)} + onSetMax={() => { + setUsedMaxInterest(true) + form.setFieldValue('interest', maxInterest.gte(0) ? maxInterest : 0) + }} /> ) }} @@ -300,34 +341,27 @@ function InternalRepayForm({ loan, destination }: { loan: ActiveLoan | CreatedLo Transaction summary - - - Available balance - - - - {maxAvailable === UNLIMITED ? 'No limit' : formatBalance(maxAvailable, displayCurrency, 2)} - - - - - - {isCashLoan(loan) ? 'Withdrawal amount' : 'Repayment amount'} + + + {maxAvailable === UNLIMITED ? 'No limit' : formatBalance(maxAvailable, displayCurrency, 2)} - {formatBalance(totalRepay, displayCurrency, 2)} - - {poolFees.renderSummary()} + + + + {isCashLoan(loan) ? 'Withdrawal amount' : 'Repayment amount'} + + {formatBalance(totalRepay, displayCurrency, 2)} + + + + {poolFees.renderSummary()} + {destination === 'reserve' ? (