1111// You should have received a copy of the GNU General Public License along with Rundler.
1212// If not, see https://www.gnu.org/licenses/.
1313
14- use alloy_primitives:: { Address , B256 , U128 , U256 } ;
14+ use alloy_primitives:: { Address , B256 , U64 , U128 , U256 } ;
1515use anyhow:: Context ;
1616use async_trait:: async_trait;
1717use futures_util:: future;
1818use jsonrpsee:: { core:: RpcResult , proc_macros:: rpc} ;
1919use rundler_provider:: { EvmProvider , FeeEstimator } ;
2020use rundler_types:: {
21- UserOperation , UserOperationId , UserOperationVariant , chain:: ChainSpec , pool:: Pool ,
21+ GasFees , UserOperation , UserOperationId , UserOperationVariant , chain:: ChainSpec , pool:: Pool ,
2222} ;
2323use tracing:: instrument;
2424
2525use crate :: {
2626 eth:: { EntryPointRouter , EthResult , EthRpcError } ,
2727 types:: {
28- RpcMinedUserOperation , RpcUserOperation , RpcUserOperationStatus , UOStatusEnum ,
29- UserOperationStatusEnum ,
28+ RpcMinedUserOperation , RpcSuggestedGasFees , RpcUserOperation , RpcUserOperationGasPrice ,
29+ RpcUserOperationStatus , UOStatusEnum , UserOperationStatusEnum ,
3030 } ,
3131 utils:: { self , TryIntoRundlerType } ,
3232} ;
3333
34+ /// Settings for the rundler API
35+ #[ derive( Clone , Copy , Debug ) ]
36+ pub struct RundlerApiSettings {
37+ /// Priority fee buffer percent for gas price suggestions (e.g., 30 for 30% above current)
38+ pub priority_fee_buffer_percent : u64 ,
39+ /// Base fee buffer percent for gas price suggestions (e.g., 50 for 1.5x base fee)
40+ pub base_fee_buffer_percent : u64 ,
41+ }
42+
43+ impl Default for RundlerApiSettings {
44+ fn default ( ) -> Self {
45+ Self {
46+ priority_fee_buffer_percent : 30 ,
47+ base_fee_buffer_percent : 50 ,
48+ }
49+ }
50+ }
51+
3452#[ rpc( client, server, namespace = "rundler" ) ]
3553pub trait RundlerApi {
3654 /// Returns the maximum priority fee per gas required by Rundler
@@ -72,6 +90,10 @@ pub trait RundlerApi {
7290 sender : Address ,
7391 nonce : U256 ,
7492 ) -> RpcResult < Option < RpcUserOperation > > ;
93+
94+ /// Returns suggested gas prices for user operations
95+ #[ method( name = "getUserOperationGasPrice" ) ]
96+ async fn get_user_operation_gas_price ( & self ) -> RpcResult < RpcUserOperationGasPrice > ;
7597}
7698
7799pub ( crate ) struct RundlerApi < P , F , E > {
@@ -80,6 +102,7 @@ pub(crate) struct RundlerApi<P, F, E> {
80102 pool_server : P ,
81103 entry_point_router : EntryPointRouter ,
82104 evm_provider : E ,
105+ settings : RundlerApiSettings ,
83106}
84107
85108#[ async_trait]
@@ -149,6 +172,15 @@ where
149172 )
150173 . await
151174 }
175+
176+ #[ instrument( skip_all, fields( rpc_method = "rundler_getUserOperationGasPrice" ) ) ]
177+ async fn get_user_operation_gas_price ( & self ) -> RpcResult < RpcUserOperationGasPrice > {
178+ utils:: safe_call_rpc_handler (
179+ "rundler_getUserOperationGasPrice" ,
180+ RundlerApi :: get_user_operation_gas_price ( self ) ,
181+ )
182+ . await
183+ }
152184}
153185
154186impl < P , F , E > RundlerApi < P , F , E >
@@ -163,22 +195,30 @@ where
163195 pool_server : P ,
164196 fee_estimator : F ,
165197 evm_provider : E ,
198+ settings : RundlerApiSettings ,
166199 ) -> Self {
167200 Self {
168201 chain_spec : chain_spec. clone ( ) ,
169202 entry_point_router,
170203 pool_server,
171204 fee_estimator,
172205 evm_provider,
206+ settings,
173207 }
174208 }
175209 #[ instrument( skip_all) ]
176210 async fn max_priority_fee_per_gas ( & self ) -> EthResult < U128 > {
177- let ( bundle_fees , _ ) = self
211+ let estimate = self
178212 . fee_estimator
179- . latest_bundle_fees ( )
213+ . latest_fee_estimate ( )
180214 . await
181215 . context ( "should get required fees" ) ?;
216+ let bundle_fees = GasFees {
217+ max_fee_per_gas : estimate
218+ . required_base_fee
219+ . saturating_add ( estimate. required_priority_fee ) ,
220+ max_priority_fee_per_gas : estimate. required_priority_fee ,
221+ } ;
182222 Ok ( U128 :: from (
183223 self . fee_estimator
184224 . required_op_fees ( bundle_fees)
@@ -344,4 +384,50 @@ where
344384
345385 Ok ( uo. map ( |uo| uo. uo . into ( ) ) )
346386 }
387+
388+ #[ instrument( skip_all) ]
389+ async fn get_user_operation_gas_price ( & self ) -> EthResult < RpcUserOperationGasPrice > {
390+ // Get base fee, priority fee (with bundler overhead), and block number
391+ let estimate = self
392+ . fee_estimator
393+ . latest_fee_estimate ( )
394+ . await
395+ . context ( "should get fee estimate" ) ?;
396+ let bundle_fees = GasFees {
397+ max_fee_per_gas : estimate
398+ . required_base_fee
399+ . saturating_add ( estimate. required_priority_fee ) ,
400+ max_priority_fee_per_gas : estimate. required_priority_fee ,
401+ } ;
402+
403+ // Convert to user operation fees (current required)
404+ let op_fees = self . fee_estimator . required_op_fees ( bundle_fees) ;
405+ let priority_fee = op_fees. max_priority_fee_per_gas ;
406+
407+ // Calculate suggested priority fee with configured buffer
408+ let priority_multiplier =
409+ 100u128 . saturating_add ( u128:: from ( self . settings . priority_fee_buffer_percent ) ) ;
410+ let suggested_priority_fee = priority_fee
411+ . saturating_mul ( priority_multiplier)
412+ . saturating_div ( 100 ) ;
413+
414+ // Calculate suggested max fee with configured base fee buffer
415+ let base_fee_multiplier =
416+ 100u128 . saturating_add ( u128:: from ( self . settings . base_fee_buffer_percent ) ) ;
417+ let suggested_max_fee = estimate
418+ . required_base_fee
419+ . saturating_mul ( base_fee_multiplier)
420+ . saturating_div ( 100 )
421+ . saturating_add ( suggested_priority_fee) ;
422+
423+ Ok ( RpcUserOperationGasPrice {
424+ priority_fee : U128 :: from ( priority_fee) ,
425+ base_fee : U128 :: from ( estimate. base_fee ) ,
426+ block_number : U64 :: from ( estimate. block_number ) ,
427+ suggested : RpcSuggestedGasFees {
428+ max_priority_fee_per_gas : U128 :: from ( suggested_priority_fee) ,
429+ max_fee_per_gas : U128 :: from ( suggested_max_fee) ,
430+ } ,
431+ } )
432+ }
347433}
0 commit comments