11use alloy:: primitives:: { Address , U256 } ;
2+ use alloy:: providers:: ProviderBuilder ;
23use alloy:: sol;
34use alloy:: sol_types:: SolCall ;
45use async_trait:: async_trait;
@@ -10,7 +11,6 @@ use defi_core::types::*;
1011sol ! {
1112 #[ sol( rpc) ]
1213 interface IBorrowerOperations {
13- // Liquity V2 openTrove signature
1414 function openTrove(
1515 address _owner,
1616 uint256 _ownerIndex,
@@ -45,13 +45,36 @@ sol! {
4545 function getTroveColl( uint256 _troveId) external view returns ( uint256) ;
4646 function getTroveStatus( uint256 _troveId) external view returns ( uint256) ;
4747 }
48+
49+ #[ sol( rpc) ]
50+ interface IHintHelpers {
51+ // Liquity V2: first param is collateral index (0 for WHYPE branch)
52+ function getApproxHint(
53+ uint256 _collIndex,
54+ uint256 _interestRate,
55+ uint256 _numTrials,
56+ uint256 _inputRandomSeed
57+ ) external view returns ( uint256 hintId, uint256 diff, uint256 latestRandomSeed) ;
58+ }
59+
60+ #[ sol( rpc) ]
61+ interface ISortedTroves {
62+ function findInsertPosition(
63+ uint256 _annualInterestRate,
64+ uint256 _prevId,
65+ uint256 _nextId
66+ ) external view returns ( uint256 prevId, uint256 nextId) ;
67+ }
4868}
4969
5070#[ allow( dead_code) ]
5171pub struct Felix {
5272 name : String ,
5373 borrower_operations : Address ,
5474 trove_manager : Option < Address > ,
75+ hint_helpers : Option < Address > ,
76+ sorted_troves : Option < Address > ,
77+ rpc_url : Option < String > ,
5578}
5679
5780impl Felix {
@@ -60,12 +83,16 @@ impl Felix {
6083 name,
6184 borrower_operations,
6285 trove_manager,
86+ hint_helpers : None ,
87+ sorted_troves : None ,
88+ rpc_url : None ,
6389 }
6490 }
6591
6692 pub fn from_contracts (
6793 name : String ,
6894 contracts : & std:: collections:: HashMap < String , Address > ,
95+ rpc_url : Option < String > ,
6996 ) -> Result < Self > {
7097 let borrower_operations =
7198 contracts
@@ -75,7 +102,57 @@ impl Felix {
75102 DefiError :: ContractError ( "Missing 'borrower_operations' contract" . to_string ( ) )
76103 } ) ?;
77104 let trove_manager = contracts. get ( "trove_manager" ) . copied ( ) ;
78- Ok ( Self :: new ( name, borrower_operations, trove_manager) )
105+ let hint_helpers = contracts. get ( "hint_helpers" ) . copied ( ) ;
106+ let sorted_troves = contracts. get ( "sorted_troves" ) . copied ( ) ;
107+ Ok ( Self {
108+ name,
109+ borrower_operations,
110+ trove_manager,
111+ hint_helpers,
112+ sorted_troves,
113+ rpc_url,
114+ } )
115+ }
116+
117+ /// Fetch optimal insertion hints via RPC.
118+ /// Returns (upperHint, lowerHint) for the sorted troves list.
119+ /// Falls back to (0, 0) if RPC or hint contracts unavailable.
120+ async fn get_hints ( & self , interest_rate : U256 ) -> ( U256 , U256 ) {
121+ let ( Some ( hint_helpers_addr) , Some ( sorted_troves_addr) , Some ( rpc_url) ) =
122+ ( self . hint_helpers , self . sorted_troves , self . rpc_url . as_ref ( ) )
123+ else {
124+ return ( U256 :: ZERO , U256 :: ZERO ) ;
125+ } ;
126+
127+ let Ok ( url) = rpc_url. parse :: < url:: Url > ( ) else {
128+ return ( U256 :: ZERO , U256 :: ZERO ) ;
129+ } ;
130+
131+ let provider = ProviderBuilder :: new ( ) . connect_http ( url) ;
132+
133+ // Step 1: getApproxHint — gives us a starting point near the correct position
134+ let hint_contract = IHintHelpers :: new ( hint_helpers_addr, & provider) ;
135+ // collIndex=0 for WHYPE branch (default)
136+ let approx = hint_contract
137+ . getApproxHint ( U256 :: ZERO , interest_rate, U256 :: from ( 15 ) , U256 :: from ( 42 ) )
138+ . call ( )
139+ . await ;
140+
141+ let approx_hint = match approx {
142+ Ok ( result) => result. hintId ,
143+ Err ( _) => return ( U256 :: ZERO , U256 :: ZERO ) ,
144+ } ;
145+
146+ // Step 2: findInsertPosition — refines the hint to exact (prev, next)
147+ let sorted = ISortedTroves :: new ( sorted_troves_addr, & provider) ;
148+ match sorted
149+ . findInsertPosition ( interest_rate, approx_hint, approx_hint)
150+ . call ( )
151+ . await
152+ {
153+ Ok ( result) => ( result. prevId , result. nextId ) ,
154+ Err ( _) => ( U256 :: ZERO , U256 :: ZERO ) ,
155+ }
79156 }
80157}
81158
@@ -86,36 +163,46 @@ impl Cdp for Felix {
86163 }
87164
88165 async fn build_open ( & self , params : OpenCdpParams ) -> Result < DeFiTx > {
166+ let interest_rate = U256 :: from ( 50000000000000000u64 ) ; // 5% default
167+ let ( upper_hint, lower_hint) = self . get_hints ( interest_rate) . await ;
168+
169+ let has_hints = !upper_hint. is_zero ( ) || !lower_hint. is_zero ( ) ;
170+
89171 let call = IBorrowerOperations :: openTroveCall {
90172 _owner : params. recipient ,
91173 _ownerIndex : U256 :: ZERO ,
92174 _collAmount : params. collateral_amount ,
93175 _boldAmount : params. debt_amount ,
94- _upperHint : U256 :: ZERO ,
95- _lowerHint : U256 :: ZERO ,
96- _annualInterestRate : U256 :: from ( 50000000000000000u64 ) , // 5% default
176+ _upperHint : upper_hint ,
177+ _lowerHint : lower_hint ,
178+ _annualInterestRate : interest_rate ,
97179 _maxUpfrontFee : U256 :: MAX ,
98- _addManager : Address :: ZERO ,
99- _removeManager : Address :: ZERO ,
180+ _addManager : params . recipient ,
181+ _removeManager : params . recipient ,
100182 _receiver : params. recipient ,
101183 } ;
102184
103185 Ok ( DeFiTx {
104186 description : format ! (
105- "[{}] Open trove: collateral={}, debt={}" ,
106- self . name, params. collateral_amount, params. debt_amount
187+ "[{}] Open trove: collateral={}, debt={} (hints={})" ,
188+ self . name,
189+ params. collateral_amount,
190+ params. debt_amount,
191+ if has_hints { "optimized" } else { "none" }
107192 ) ,
108193 to : self . borrower_operations ,
109194 data : call. abi_encode ( ) . into ( ) ,
110195 value : U256 :: ZERO ,
111- gas_estimate : Some ( 5_000_000 ) , // Needs high gas for sorted trove insertion without hints
196+ gas_estimate : Some ( if has_hints { 500_000 } else { 5_000_000 } ) ,
112197 } )
113198 }
114199
115200 async fn build_adjust ( & self , params : AdjustCdpParams ) -> Result < DeFiTx > {
116201 let coll_change = params. collateral_delta . unwrap_or ( U256 :: ZERO ) ;
117202 let debt_change = params. debt_delta . unwrap_or ( U256 :: ZERO ) ;
118203
204+ // For adjust, hints are also needed but we'd need the new interest rate.
205+ // Use (0,0) for now — adjust gas is lower than open since the trove already exists.
119206 let call = IBorrowerOperations :: adjustTroveCall {
120207 _troveId : params. cdp_id ,
121208 _collChange : coll_change,
0 commit comments