diff --git a/frontend/app/src/screens/LoanScreen/LoanCard.tsx b/frontend/app/src/screens/LoanScreen/LoanCard.tsx index 8fb7f72e..1727bfd5 100644 --- a/frontend/app/src/screens/LoanScreen/LoanCard.tsx +++ b/frontend/app/src/screens/LoanScreen/LoanCard.tsx @@ -5,7 +5,6 @@ import type { LoanLoadingState } from "./LoanScreen"; import { INFINITY } from "@/src/characters"; import { Spinner } from "@/src/comps/Spinner/Spinner"; import { Value } from "@/src/comps/Value/Value"; -import { dnum18 } from "@/src/dnum-utils"; import { formatRisk } from "@/src/formatting"; import { fmtnum } from "@/src/formatting"; import { getLoanDetails } from "@/src/liquity-math"; @@ -190,7 +189,7 @@ export function LoanCard({ - {fmtnum(dn.mul(loan.interestRate, 100))}% + {fmtnum(loan.interestRate, 2, 100)}%
- {ltv && fmtnum(dn.mul(ltv, 100))}% + {fmtnum(ltv, "2z", 100)}%
@@ -455,7 +454,6 @@ function GridItem({ }) { return (
@@ -189,7 +193,21 @@ export function PanelClosePosition({ loan }: { loan: PositionLoan }) { size="large" wide onClick={() => { - router.push("/transactions/close-loan"); + if (account.address) { + txFlow.start({ + flowId: "closeLoanPosition", + backLink: [ + `/loan/close?id=${loan.collIndex}:${loan.troveId}`, + "Back to editing", + ], + successLink: ["/", "Go to the dashboard"], + successMessage: "The loan position has been closed successfully.", + + collIndex: loan.collIndex, + prefixedTroveId: getPrefixedTroveId(loan.collIndex, loan.troveId), + }); + router.push("/transactions"); + } }} />
diff --git a/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx b/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx index 4347f9f3..77317a31 100644 --- a/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx +++ b/frontend/app/src/screens/LoanScreen/PanelUpdateBorrowPosition.tsx @@ -46,7 +46,6 @@ export function PanelUpdateBorrowPosition({ }) { const router = useRouter(); const account = useAccount(); - const txFlow = useTransactionFlow(); const collateral = TOKENS_BY_SYMBOL[loan.collateral]; diff --git a/frontend/app/src/screens/LoanScreen/PanelUpdateRate.tsx b/frontend/app/src/screens/LoanScreen/PanelUpdateRate.tsx index 6b324e22..4e6872b1 100644 --- a/frontend/app/src/screens/LoanScreen/PanelUpdateRate.tsx +++ b/frontend/app/src/screens/LoanScreen/PanelUpdateRate.tsx @@ -5,21 +5,26 @@ import { Field } from "@/src/comps/Field/Field"; import { InfoBox } from "@/src/comps/InfoBox/InfoBox"; import { InterestRateField } from "@/src/comps/InterestRateField/InterestRateField"; import { ValueUpdate } from "@/src/comps/ValueUpdate/ValueUpdate"; +import { dnum18 } from "@/src/dnum-utils"; import { useInputFieldValue } from "@/src/form-utils"; import { fmtnum, formatRisk } from "@/src/formatting"; import { getLoanDetails } from "@/src/liquity-math"; +import { getPrefixedTroveId } from "@/src/liquity-utils"; import { useAccount } from "@/src/services/Ethereum"; import { usePrice } from "@/src/services/Prices"; +import { useTransactionFlow } from "@/src/services/TransactionFlow"; import { riskLevelToStatusMode } from "@/src/uikit-utils"; import { css } from "@/styled-system/css"; import { Button, HFlex, InfoTooltip, StatusDot, TOKENS_BY_SYMBOL } from "@liquity2/uikit"; import * as dn from "dnum"; import { useRouter } from "next/navigation"; import { useState } from "react"; +import { maxUint256 } from "viem"; export function PanelUpdateRate({ loan }: { loan: PositionLoan }) { const router = useRouter(); const account = useAccount(); + const txFlow = useTransactionFlow(); const collateral = TOKENS_BY_SYMBOL[loan.collateral]; const collPrice = usePrice(collateral.symbol); @@ -145,7 +150,26 @@ export function PanelUpdateRate({ loan }: { loan: PositionLoan }) { size="large" wide onClick={() => { - router.push("/transactions/update-loan"); + if (account.address) { + txFlow.start({ + flowId: "updateLoanInterestRate", + backLink: [ + `/loan/rate?id=${loan.collIndex}:${loan.troveId}`, + "Back to editing", + ], + successLink: ["/", "Go to the dashboard"], + successMessage: "The position interest rate has been updated successfully.", + + collIndex: loan.collIndex, + interestRate, + lowerHint: dnum18(0), + maxUpfrontFee: dnum18(maxUint256), + owner: account.address, + prefixedTroveId: getPrefixedTroveId(loan.collIndex, loan.troveId), + upperHint: dnum18(0), + }); + router.push("/transactions"); + } }} />
diff --git a/frontend/app/src/screens/TransactionsScreen/LoanCard.tsx b/frontend/app/src/screens/TransactionsScreen/LoanCard.tsx index 86b23468..dab9fce2 100644 --- a/frontend/app/src/screens/TransactionsScreen/LoanCard.tsx +++ b/frontend/app/src/screens/TransactionsScreen/LoanCard.tsx @@ -19,6 +19,7 @@ import * as dn from "dnum"; import { match, P } from "ts-pattern"; const LOAN_CARD_HEIGHT = 246 - 16; +const LOAN_CARD_HEIGHT_REDUCED = 176; export function LoanCard({ leverageMode, @@ -33,9 +34,15 @@ export function LoanCard({ prevLoan?: PositionLoan | null; onRetry: () => void; }) { - const collateral = loan && TOKENS_BY_SYMBOL[loan.collateral]; + const collateral = ( + loan && TOKENS_BY_SYMBOL[loan.collateral] + ) || ( + prevLoan && TOKENS_BY_SYMBOL[prevLoan.collateral] + ); const collPriceUsd = usePrice(collateral ? collateral.symbol : null); + const isLoanClosing = prevLoan && !loan; + const loanDetails = loan && collateral && getLoanDetails( loan.deposit, loan.borrowed, @@ -67,266 +74,413 @@ export function LoanCard({ return ( - {loan - && loanDetails - && collateral - && typeof leverageFactor === "number" - && depositPreLeverage - && maxLtv - && liquidationRisk - && ( + {isLoanClosing + ? ( <> +
+ {collateral && ( + +
+
+ {fmtnum(0)} {collateral.name} +
+ {prevLoan && ( +
+ {fmtnum(prevLoan.deposit)} {collateral.name} +
+ )} +
+
+ )} +
+ + ) + : loan + && loanDetails + && collateral + && typeof leverageFactor === "number" + && depositPreLeverage + && maxLtv + && liquidationRisk + && ( + <> + {leverageMode + ? ( + + ) + : ( + + )}
{leverageMode ? ( -
-
{fmtnum(loan.deposit)}
- + + + {fmtnum(depositPreLeverage)} {collateral.name} + + + ) + : ( +
-
- + {fmtnum(loan.deposit)} {collateral.name} +
+ {prevLoan && !dn.eq(prevLoan.deposit, loan.deposit) && ( +
- {loanDetails.status === "underwater" || leverageFactor === null - ? INFINITY - : `${roundToDecimal(leverageFactor, 1)}x`} - -
+ {fmtnum(prevLoan.deposit)} {collateral.name} +
+ )}
-
- ) - : ( -
- {fmtnum(loan.borrowed)} - - {prevLoan && !dn.eq(prevLoan.borrowed, loan.borrowed) && ( -
- {fmtnum(prevLoan.borrowed)} -
- )} -
+
)} - - -
- {leverageMode - ? ( - - - {fmtnum(depositPreLeverage)} {collateral.name} + +
+ + ${fmtnum(loanDetails.liquidationPrice)} - - ) - : ( - -
-
- {fmtnum(loan.deposit)} {collateral.name} -
- {prevLoan && !dn.eq(prevLoan.deposit, loan.deposit) && ( + {loanDetails?.liquidationPrice + && prevLoanDetails?.liquidationPrice + && !dn.eq( + prevLoanDetails.liquidationPrice, + loanDetails.liquidationPrice, + ) + && (
- {fmtnum(prevLoan.deposit)} {collateral.name} + ${fmtnum(prevLoanDetails.liquidationPrice)}
)} -
-
- )} - -
- - ${fmtnum(loanDetails.liquidationPrice)} - - {loanDetails?.liquidationPrice - && prevLoanDetails?.liquidationPrice - && !dn.eq( - prevLoanDetails.liquidationPrice, - loanDetails.liquidationPrice, - ) - && ( +
+
+ +
+ {fmtnum(dn.mul(loan.interestRate, 100))}% + {prevLoan && !dn.eq(prevLoan.interestRate, loan.interestRate) && (
- ${fmtnum(prevLoanDetails.liquidationPrice)} + {fmtnum(dn.mul(prevLoan.interestRate, 100))}%
)} -
-
- - {fmtnum(dn.mul(loan.interestRate, 100))}% - - -
+
+
+
- {ltv && fmtnum(dn.mul(ltv, 100))}% +
+ {ltv && fmtnum(dn.mul(ltv, 100))}% +
+ {ltv + && prevLoanDetails?.ltv + && !dn.eq(prevLoanDetails.ltv, ltv) + && ( +
+ {prevLoanDetails.ltv && fmtnum(dn.mul(prevLoanDetails.ltv, 100))}% +
+ )}
- {ltv - && prevLoanDetails?.ltv - && !dn.eq(prevLoanDetails.ltv, ltv) - && ( -
- {prevLoanDetails.ltv && fmtnum(dn.mul(prevLoanDetails.ltv, 100))}% -
- )} -
-
- - - - {formatRisk(liquidationRisk)} - {prevLoanDetails && liquidationRisk !== prevLoanDetails.liquidationRisk && ( - <> - -
- {formatRisk(prevLoanDetails.liquidationRisk)} -
- - )} -
-
- {redemptionRisk && ( - + + - {formatRisk(redemptionRisk)} + {formatRisk(liquidationRisk)} + {prevLoanDetails && liquidationRisk !== prevLoanDetails.liquidationRisk && ( + <> + +
+ {formatRisk(prevLoanDetails.liquidationRisk)} +
+ + )}
- )} -
- - )} + {redemptionRisk && ( + + + + {formatRisk(redemptionRisk)} + {prevLoanDetails && redemptionRisk !== prevLoanDetails.redemptionRisk && ( + <> + +
+ {formatRisk(prevLoanDetails.redemptionRisk)} +
+ + )} +
+
+ )} + + + )} ); } +function TotalDebt({ + positive, + loan, + prevLoan, +}: { + positive?: boolean; + loan: PositionLoan; + prevLoan?: PositionLoan | null; +}) { + return ( +
+
+
+
+ {fmtnum(loan.borrowed)} +
+ + {prevLoan && !dn.eq(prevLoan.borrowed, loan.borrowed) && ( +
+ {fmtnum(prevLoan.borrowed)} +
+ )} +
+
+
+ ); +} + +function TotalExposure({ + loan, + loanDetails, +}: { + loan: PositionLoan; + loanDetails: ReturnType; +}) { + const collateral = loan && TOKENS_BY_SYMBOL[loan.collateral]; + return ( +
+
+
+
{fmtnum(loan.deposit)}
+ +
+
+ + {loanDetails.status === "underwater" || loanDetails.leverageFactor === null + ? INFINITY + : `${roundToDecimal(loanDetails.leverageFactor, 1)}x`} + +
+
+
+
+
+ ); +} + function LoadingCard({ children, + height, leverage, loadingState, onRetry, }: { children: ReactNode; + height: number; leverage: boolean; loadingState: LoadingState; onRetry: () => void; @@ -358,8 +512,8 @@ function LoadingCard({ ) .otherwise(() => ({ cardtransform: "scale3d(1, 1, 1)", - containerHeight: LOAN_CARD_HEIGHT, - cardHeight: LOAN_CARD_HEIGHT, + containerHeight: height, + cardHeight: height, cardBackground: token("colors.blue:950"), cardColor: token("colors.white"), })), @@ -390,7 +544,7 @@ function LoadingCard({ userSelect: "none", })} style={{ - height: loadingState === "success" ? LOAN_CARD_HEIGHT : spring.cardHeight, + height: loadingState === "success" ? height : spring.cardHeight, color: spring.cardColor, background: spring.cardBackground, transform: spring.cardtransform, diff --git a/frontend/app/src/services/TransactionFlow.tsx b/frontend/app/src/services/TransactionFlow.tsx index 4dc067df..f8f69afb 100644 --- a/frontend/app/src/services/TransactionFlow.tsx +++ b/frontend/app/src/services/TransactionFlow.tsx @@ -10,8 +10,9 @@ // - Flow declaration: Contains the logic for a specific flow (get steps, parse request, tx params). // - Flow context: a transaction flow as stored in local storage (steps + request). +import type { Request as CloseLoanPositionRequest } from "@/src/tx-flows/closeLoanPosition"; import type { Request as OpenLoanPositionRequest } from "@/src/tx-flows/openLoanPosition"; -import type { Request as RepayAndCloseLoanPositionRequest } from "@/src/tx-flows/repayAndCloseLoanPosition"; +import type { Request as UpdateLoanInterestRateRequest } from "@/src/tx-flows/updateLoanInterestRate"; import type { Request as UpdateLoanPositionRequest } from "@/src/tx-flows/updateLoanPosition"; import type { Address } from "@/src/types"; import type { WriteContractParameters } from "@wagmi/core"; @@ -21,8 +22,9 @@ import { LOCAL_STORAGE_PREFIX } from "@/src/constants"; import { useContracts } from "@/src/contracts"; import { jsonParseWithDnum, jsonStringifyWithDnum } from "@/src/dnum-utils"; import { useAccount, useWagmiConfig } from "@/src/services/Ethereum"; +import { closeLoanPosition } from "@/src/tx-flows/closeLoanPosition"; import { openLoanPosition } from "@/src/tx-flows/openLoanPosition"; -import { repayAndCloseLoanPosition } from "@/src/tx-flows/repayAndCloseLoanPosition"; +import { updateLoanInterestRate } from "@/src/tx-flows/updateLoanInterestRate"; import { updateLoanPosition } from "@/src/tx-flows/updateLoanPosition"; import { noop } from "@/src/utils"; import { vAddress } from "@/src/valibot-utils"; @@ -35,8 +37,9 @@ const TRANSACTION_FLOW_KEY = `${LOCAL_STORAGE_PREFIX}transaction_flow`; export type FlowRequest = | OpenLoanPositionRequest - | RepayAndCloseLoanPositionRequest - | UpdateLoanPositionRequest; + | UpdateLoanPositionRequest + | UpdateLoanInterestRateRequest + | CloseLoanPositionRequest; const flowDeclarations: { [K in FlowIdFromFlowRequest]: FlowDeclaration< @@ -44,15 +47,17 @@ const flowDeclarations: { any // Use 'any' here to allow any StepId type >; } = { + closeLoanPosition, openLoanPosition, - repayAndCloseLoanPosition, + updateLoanInterestRate, updateLoanPosition, }; const FlowIdSchema = v.union([ + v.literal("closeLoanPosition"), v.literal("openLoanPosition"), - v.literal("repayAndCloseLoanPosition"), v.literal("updateLoanPosition"), + v.literal("updateLoanInterestRate"), ]); type ExtractStepId = T extends FlowDeclaration ? S : never; @@ -161,8 +166,8 @@ export type FlowDeclaration< FR extends FlowRequest, StepId extends string = string, > = { - title: string; - subtitle: string; + title: ReactNode; + subtitle: ReactNode; Summary: ComponentType<{ flow: FlowContext }>; Details: ComponentType<{ flow: FlowContext }>; getSteps: GetStepsFn; diff --git a/frontend/app/src/subgraph-queries.graphql b/frontend/app/src/subgraph-queries.graphql index ad1f6f1d..0b6f79f8 100644 --- a/frontend/app/src/subgraph-queries.graphql +++ b/frontend/app/src/subgraph-queries.graphql @@ -1,5 +1,5 @@ query TrovesByAccount($account: Bytes!) { - troves(where: { borrower: $account }) { + troves(where: { borrower: $account, closedAt: null }) { id troveId borrower diff --git a/frontend/app/src/tx-flows/closeLoanPosition.tsx b/frontend/app/src/tx-flows/closeLoanPosition.tsx new file mode 100644 index 00000000..9d4ee463 --- /dev/null +++ b/frontend/app/src/tx-flows/closeLoanPosition.tsx @@ -0,0 +1,133 @@ +import type { LoadingState } from "@/src/screens/TransactionsScreen/TransactionsScreen"; +import type { FlowDeclaration } from "@/src/services/TransactionFlow"; + +import { fmtnum } from "@/src/formatting"; +import { useCollateral } from "@/src/liquity-utils"; +import { parsePrefixedTroveId } from "@/src/liquity-utils"; +import { LoanCard } from "@/src/screens/TransactionsScreen/LoanCard"; +import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; +import { useLoanById } from "@/src/subgraph-hooks"; +import { vCollIndex, vPrefixedTroveId } from "@/src/valibot-utils"; +import { match, P } from "ts-pattern"; +import * as v from "valibot"; + +const FlowIdSchema = v.literal("closeLoanPosition"); + +const RequestSchema = v.object({ + flowId: FlowIdSchema, + + backLink: v.union([ + v.null(), + v.tuple([ + v.string(), // path + v.string(), // label + ]), + ]), + successLink: v.tuple([ + v.string(), // path + v.string(), // label + ]), + successMessage: v.string(), + + collIndex: vCollIndex(), + prefixedTroveId: vPrefixedTroveId(), +}); + +export type Request = v.InferOutput; + +type Step = "closeLoanPosition"; + +const stepNames: Record = { + closeLoanPosition: "Close Position", +}; + +export const closeLoanPosition: FlowDeclaration = { + title: "Review & Send Transaction", + subtitle: ( +
+ You are repaying your debt and closing this position.
+ The deposit will be returned to your wallet +
+ ), + + Summary({ flow }) { + const loan = useLoanById(flow.request.prefixedTroveId); + + const loadingState = match(loan) + .returnType() + .with({ status: "error" }, () => "error") + .with({ status: "pending" }, () => "loading") + .with({ data: null }, () => "not-found") + .with({ data: P.nonNullable }, () => "success") + .otherwise(() => "error"); + + return ( + {}} + /> + ); + }, + + Details({ flow }) { + const { request } = flow; + const collateral = useCollateral(request.collIndex); + const loan = useLoanById(request.prefixedTroveId); + + return loan.data && ( + <> + + {fmtnum(loan.data.borrowed, 4)} BOLD + , + ]} + /> + + {fmtnum(loan.data.deposit, "2z")} {collateral.symbol} + , + ]} + /> + + ); + }, + getStepName(stepid) { + return stepNames[stepid]; + }, + async getSteps() { + return ["closeLoanPosition"]; + }, + parseRequest(request) { + return v.parse(RequestSchema, request); + }, + async writeContractParams({ contracts, request, stepId }) { + const collateral = contracts.collaterals[request.collIndex]; + const { BorrowerOperations } = collateral.contracts; + + if (!BorrowerOperations) { + throw new Error(`Collateral ${collateral.symbol} not supported`); + } + + if (stepId === "closeLoanPosition") { + const { troveId } = parsePrefixedTroveId(request.prefixedTroveId); + + return { + ...BorrowerOperations, + functionName: "closeTrove" as const, + args: [troveId], + }; + } + return null; + }, +}; diff --git a/frontend/app/src/tx-flows/openLoanPosition.tsx b/frontend/app/src/tx-flows/openLoanPosition.tsx index cafaf4cc..48479805 100644 --- a/frontend/app/src/tx-flows/openLoanPosition.tsx +++ b/frontend/app/src/tx-flows/openLoanPosition.tsx @@ -52,7 +52,7 @@ const stepNames: Record = { }; export const openLoanPosition: FlowDeclaration = { - title: "Review & Confirm", + title: "Review & Send Transaction", subtitle: "Please review your borrow position before confirming", Summary({ flow }) { diff --git a/frontend/app/src/tx-flows/repayAndCloseLoanPosition.tsx b/frontend/app/src/tx-flows/repayAndCloseLoanPosition.tsx deleted file mode 100644 index 7f98d936..00000000 --- a/frontend/app/src/tx-flows/repayAndCloseLoanPosition.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import type { FlowDeclaration } from "@/src/services/TransactionFlow"; - -import { getTroveId } from "@/src/liquity-utils"; -import { vAddress } from "@/src/valibot-utils"; -import * as v from "valibot"; - -const FlowIdSchema = v.literal("repayAndCloseLoanPosition"); - -const RequestSchema = v.object({ - flowId: FlowIdSchema, - backLink: v.union([ - v.null(), - v.tuple([ - v.string(), // path - v.string(), // label - ]), - ]), - successLink: v.tuple([ - v.string(), // path - v.string(), // label - ]), - successMessage: v.string(), - - collIndex: v.number(), - owner: vAddress(), - ownerIndex: v.number(), -}); - -export type Request = v.InferOutput; - -export const repayAndCloseLoanPosition: FlowDeclaration = { - title: "", - subtitle: "", - Summary: () => null, - Details: () => null, - getStepName: () => "", - - getSteps: async function getSteps() { - return ["closeTrove"]; - }, - parseRequest: (request): Request => { - return v.parse(RequestSchema, request); - }, - writeContractParams: async ({ contracts, request, stepId }) => { - const collateral = contracts.collaterals[request.collIndex]; - const { BorrowerOperations } = collateral.contracts; - - if (!BorrowerOperations) { - throw new Error(`Collateral ${collateral.symbol} not supported`); - } - - if (stepId === "closeTrove") { - const troveId = getTroveId(request.owner, request.ownerIndex); - return { - ...BorrowerOperations, - functionName: "closeTrove" as const, - args: [troveId], - }; - } - return null; - }, -}; diff --git a/frontend/app/src/tx-flows/updateLoanInterestRate.tsx b/frontend/app/src/tx-flows/updateLoanInterestRate.tsx new file mode 100644 index 00000000..cba6b21b --- /dev/null +++ b/frontend/app/src/tx-flows/updateLoanInterestRate.tsx @@ -0,0 +1,129 @@ +import type { LoadingState } from "@/src/screens/TransactionsScreen/TransactionsScreen"; +import type { FlowDeclaration } from "@/src/services/TransactionFlow"; + +import { fmtnum } from "@/src/formatting"; +import { parsePrefixedTroveId } from "@/src/liquity-utils"; +import { LoanCard } from "@/src/screens/TransactionsScreen/LoanCard"; +import { TransactionDetailsRow } from "@/src/screens/TransactionsScreen/TransactionsScreen"; +import { useLoanById } from "@/src/subgraph-hooks"; +import { vAddress, vCollIndex, vDnum, vPrefixedTroveId } from "@/src/valibot-utils"; +import * as dn from "dnum"; +import { match, P } from "ts-pattern"; +import * as v from "valibot"; + +const FlowIdSchema = v.literal("updateLoanInterestRate"); + +const RequestSchema = v.object({ + flowId: FlowIdSchema, + + backLink: v.union([ + v.null(), + v.tuple([ + v.string(), // path + v.string(), // label + ]), + ]), + successLink: v.tuple([ + v.string(), // path + v.string(), // label + ]), + successMessage: v.string(), + + collIndex: vCollIndex(), + interestRate: vDnum(), + lowerHint: vDnum(), + maxUpfrontFee: vDnum(), + owner: vAddress(), + prefixedTroveId: vPrefixedTroveId(), + upperHint: vDnum(), +}); + +export type Request = v.InferOutput; + +type Step = "adjustInterestRate"; + +const stepNames: Record = { + adjustInterestRate: "Update Interest Rate", +}; + +export const updateLoanInterestRate: FlowDeclaration = { + title: "Review & Confirm", + subtitle: "Please review the changes of your borrow position before confirming", + Summary({ flow }) { + const loan = useLoanById(flow.request.prefixedTroveId); + + const loadingState = match(loan) + .returnType() + .with({ status: "error" }, () => "error") + .with({ status: "pending" }, () => "loading") + .with({ data: null }, () => "not-found") + .with({ data: P.nonNullable }, () => "success") + .otherwise(() => "error"); + + return ( + {}} + /> + ); + }, + Details({ flow }) { + const { request } = flow; + + const loan = useLoanById(flow.request.prefixedTroveId); + const boldPerYear = dn.mul(loan.data?.borrowed ?? 0n, request.interestRate); + + return ( + + {fmtnum(request.interestRate, "full", 100)}% + , +
+ ~{fmtnum(boldPerYear, 4)} BOLD per year +
, + ]} + /> + ); + }, + getStepName(stepid) { + return stepNames[stepid]; + }, + async getSteps() { + return ["adjustInterestRate"]; + }, + parseRequest(request) { + return v.parse(RequestSchema, request); + }, + async writeContractParams({ contracts, request, stepId }) { + const collateral = contracts.collaterals[request.collIndex]; + const { BorrowerOperations } = collateral.contracts; + + if (!BorrowerOperations) { + throw new Error(`Collateral ${collateral.symbol} not supported`); + } + + if (stepId === "adjustInterestRate") { + const { troveId } = parsePrefixedTroveId(request.prefixedTroveId); + return { + ...BorrowerOperations, + functionName: "adjustTroveInterestRate" as const, + args: [ + troveId, + request.interestRate[0], + request.upperHint[0], + request.lowerHint[0], + request.maxUpfrontFee[0], + ], + }; + } + return null; + }, +}; diff --git a/frontend/app/src/tx-flows/updateLoanPosition.tsx b/frontend/app/src/tx-flows/updateLoanPosition.tsx index bfed7455..8f46cc77 100644 --- a/frontend/app/src/tx-flows/updateLoanPosition.tsx +++ b/frontend/app/src/tx-flows/updateLoanPosition.tsx @@ -47,11 +47,11 @@ type Step = "approve" | "adjustTrove"; const stepNames: Record = { approve: "Approve", - adjustTrove: "Adjust Trove", + adjustTrove: "Update Position", }; export const updateLoanPosition: FlowDeclaration = { - title: "Review & Confirm", + title: "Review & Send Transaction", subtitle: "Please review the changes of your borrow position before confirming", Summary({ flow }) { const { symbol } = useCollateral(flow.request.collIndex); @@ -189,7 +189,7 @@ export const updateLoanPosition: FlowDeclaration = { return isApproved ? ["adjustTrove"] : ["approve", "adjustTrove"]; }, - parseRequest(request): Request { + parseRequest(request) { return v.parse(RequestSchema, request); }, async writeContractParams({ contracts, request, stepId }) {