@@ -3,24 +3,84 @@ import {
33 Decimal ,
44 LENDING_BROKER_ABI ,
55 BROKER_RATE_CALCULATOR_ABI ,
6+ calculateDynamicLoanRepayment ,
67 calculateFixedLoanRepayment ,
78 type BrokerUserPositionsData ,
89 type FixedLoanPosition ,
910 type DynamicLoanPosition ,
1011 type RawFixedTerm ,
1112} from "@lista-dao/moolah-sdk-core" ;
1213
14+ const ONE_E27 = 10n ** 27n ;
1315const SECONDS_PER_WEEK = 604800n ;
16+ const RATE_SCALE_18 = 10n ** 18n ;
17+ const FLEXIBLE_RATE_NUMERATOR = 100n ;
18+ const FLEXIBLE_RATE_DENOMINATOR = 95n ;
1419
1520/**
1621 * Normalize APR rate from contract format
1722 * Contract stores APR as (1 + rate) * 1e27, we convert to rate * 1e27
1823 */
1924function normalizeAprRate ( apr : bigint ) : bigint {
20- const ONE_E27 = 10n ** 27n ;
2125 return apr > ONE_E27 ? apr - ONE_E27 : apr ;
2226}
2327
28+ /**
29+ * Convert broker dynamic rate (27 decimals) to WAD (18 decimals)
30+ * expected by calculateDynamicLoanRepayment.
31+ */
32+ function normalizeDynamicRateToWad ( dynamicRate : bigint ) : bigint {
33+ return dynamicRate / RATE_SCALE_18 ;
34+ }
35+
36+ function buildTermRateData ( terms : readonly RawFixedTerm [ ] ) {
37+ const termRateByDuration = new Map < string , Decimal > ( ) ;
38+ let currentFlexibleRate = Decimal . ZERO ;
39+
40+ for ( const term of terms ) {
41+ const normalizedRate = normalizeAprRate ( term . apr ) ;
42+
43+ if ( term . duration === SECONDS_PER_WEEK ) {
44+ currentFlexibleRate = new Decimal (
45+ ( normalizedRate * FLEXIBLE_RATE_NUMERATOR ) / FLEXIBLE_RATE_DENOMINATOR ,
46+ 27 ,
47+ ) ;
48+ }
49+
50+ termRateByDuration . set (
51+ term . duration . toString ( ) ,
52+ new Decimal ( normalizedRate , 27 ) ,
53+ ) ;
54+ }
55+
56+ return { termRateByDuration, currentFlexibleRate } ;
57+ }
58+
59+ function calculateDynamicOutstanding (
60+ dynamicPosition : DynamicLoanPosition ,
61+ dynamicRate : bigint ,
62+ loanDecimals : number ,
63+ ) : Decimal | null {
64+ if ( dynamicPosition . principal <= 0n ) {
65+ return null ;
66+ }
67+
68+ const { totalRepay } = calculateDynamicLoanRepayment (
69+ {
70+ principal : dynamicPosition . principal ,
71+ normalizedDebt : dynamicPosition . normalizedDebt ,
72+ rate : normalizeDynamicRateToWad ( dynamicRate ) ,
73+ } ,
74+ loanDecimals ,
75+ ) ;
76+
77+ return totalRepay . roundDown ( loanDecimals ) ;
78+ }
79+
80+ function getPositionDuration ( position : FixedLoanPosition ) : bigint {
81+ return position . end > position . start ? position . end - position . start : 0n ;
82+ }
83+
2484/**
2585 * Get broker user positions data
2686 */
@@ -60,34 +120,12 @@ export async function getBrokerUserPositions(
60120 } ) as Promise < bigint > ,
61121 ] ) ;
62122
63- // Build term rate map
64- const termRateByDuration = new Map < string , Decimal > ( ) ;
65- let currentFlexibleRate = Decimal . ZERO ;
66-
67- terms . forEach ( ( term ) => {
68- if ( term . duration === SECONDS_PER_WEEK ) {
69- // Flexible rate = term rate * 100 / 95
70- currentFlexibleRate = new Decimal (
71- ( normalizeAprRate ( term . apr ) * 100n ) / 95n ,
72- 27 ,
73- ) ;
74- }
75- const normalizedRate = new Decimal ( normalizeAprRate ( term . apr ) , 27 ) ;
76- termRateByDuration . set ( term . duration . toString ( ) , normalizedRate ) ;
77- } ) ;
78-
79- // Calculate dynamic position data
80- let dynamicOutstanding : Decimal | null = null ;
81-
82- if ( dynamicPosition ?. principal && dynamicPosition . principal > 0n ) {
83- const normalizedDebt = new Decimal (
84- dynamicPosition . normalizedDebt ?? dynamicPosition . principal ,
85- loanDecimals ,
86- ) ;
87- dynamicOutstanding = normalizedDebt
88- . mul ( new Decimal ( dynamicRate , 27 ) )
89- . roundDown ( loanDecimals ) ;
90- }
123+ const { termRateByDuration, currentFlexibleRate } = buildTermRateData ( terms ) ;
124+ const dynamicOutstanding = calculateDynamicOutstanding (
125+ dynamicPosition ,
126+ dynamicRate ,
127+ loanDecimals ,
128+ ) ;
91129
92130 // Calculate fixed positions data
93131 let fixedOutstanding = Decimal . ZERO ;
@@ -98,23 +136,18 @@ export async function getBrokerUserPositions(
98136 // Add dynamic position to totals
99137 if ( dynamicOutstanding && dynamicOutstanding . gt ( Decimal . ZERO ) ) {
100138 totalOutstanding = totalOutstanding . add ( dynamicOutstanding ) ;
101- weightedSum = weightedSum . add (
102- dynamicOutstanding . mul ( currentFlexibleRate ?? Decimal . ZERO ) ,
103- ) ;
139+ weightedSum = weightedSum . add ( dynamicOutstanding . mul ( currentFlexibleRate ) ) ;
104140 }
105141
106142 // Process fixed positions
107143 const currentTimestamp = Math . floor ( Date . now ( ) / 1000 ) ;
108- fixedPositions . forEach ( ( position ) => {
109- const principal = BigInt ( position . principal ?? 0n ) ;
110- const principalRepaid = BigInt ( position . principalRepaid ?? 0n ) ;
111-
144+ for ( const position of fixedPositions ) {
112145 // Skip fully repaid or matured positions
113- if ( principal <= principalRepaid ) {
114- return ;
146+ if ( position . principal <= position . principalRepaid ) {
147+ continue ;
115148 }
116- if ( Number ( position . end ?? 0n ) <= currentTimestamp ) {
117- return ;
149+ if ( Number ( position . end ) <= currentTimestamp ) {
150+ continue ;
118151 }
119152
120153 const {
@@ -130,18 +163,13 @@ export async function getBrokerUserPositions(
130163 fixedOutstanding = fixedOutstanding . add ( totalRepayNoPenalty ) ;
131164 totalPenalty = totalPenalty . add ( new Decimal ( penalty , loanDecimals ) ) ;
132165
133- const duration =
134- BigInt ( position . end ?? 0n ) > BigInt ( position . start ?? 0n )
135- ? BigInt ( position . end ?? 0n ) - BigInt ( position . start ?? 0n )
136- : 0n ;
137-
138166 const normalizedFixedRate =
139- termRateByDuration . get ( duration . toString ( ) ) ??
167+ termRateByDuration . get ( getPositionDuration ( position ) . toString ( ) ) ??
140168 new Decimal ( normalizeAprRate ( position . apr ) , 27 ) ;
141169
142170 totalOutstanding = totalOutstanding . add ( totalRepayNoPenalty ) ;
143171 weightedSum = weightedSum . add ( totalRepayNoPenalty . mul ( normalizedFixedRate ) ) ;
144- } ) ;
172+ }
145173
146174 // Calculate weighted borrow rate
147175 const weightedBorrowRate = totalOutstanding . gt ( Decimal . ZERO )
0 commit comments