1+ use alloy_primitives:: U256 ;
12use clap:: Args ;
23use reth_optimism_payload_builder:: BridgeInterceptConfig ;
4+ use std:: time:: Duration ;
35
46/// X Layer specific configuration flags
5- #[ derive( Debug , Clone , Args , PartialEq , Eq , Default ) ]
7+ #[ derive( Debug , Clone , Args , PartialEq , Default ) ]
68#[ command( next_help_heading = "X Layer" ) ]
79pub struct XLayerArgs {
810 /// Bridge transaction interception configuration
911 #[ command( flatten) ]
1012 pub intercept : XLayerInterceptArgs ,
13+
14+ /// XLayer gas price configuration
15+ #[ command( flatten) ]
16+ pub gas_price : XLayerGasPriceArgs ,
1117 // /// Another X Layer feature
1218 // #[command(flatten)]
1319 // pub another_feature: AnotherFeatureArgs,
@@ -16,8 +22,9 @@ pub struct XLayerArgs {
1622impl XLayerArgs {
1723 /// Validate all X Layer configurations
1824 pub fn validate ( & self ) -> Result < ( ) , String > {
19- self . intercept . validate ( )
25+ self . intercept . validate ( ) ? ;
2026 // self.another_feature.validate()?;
27+ Ok ( ( ) )
2128 }
2229}
2330
@@ -122,6 +129,134 @@ impl XLayerInterceptArgs {
122129 }
123130}
124131
132+ /// XLayer gas price configuration
133+ #[ derive( Debug , Clone , Args , PartialEq ) ]
134+ #[ command( next_help_heading = "XLayer Gas Price" ) ]
135+ pub struct XLayerGasPriceArgs {
136+ /// Gas price calculation type: "default", "follower", or "fixed"
137+ /// If not specified, XLayer gas price scheduler will not be initialized
138+ #[ arg( long = "xlayer.gasprice.type" ) ]
139+ pub price_type : Option < String > ,
140+
141+ /// Gas price update period
142+ #[ arg( long = "xlayer.gasprice.update-period" , value_parser = parse_duration) ]
143+ pub update_period : Option < Duration > ,
144+
145+ /// Gas price calculation factor
146+ #[ arg( long = "xlayer.gasprice.factor" ) ]
147+ pub factor : Option < f64 > ,
148+
149+ /// Kafka URL for gas price updates
150+ #[ arg( long = "xlayer.gasprice.kafka-url" ) ]
151+ pub kafka_url : Option < String > ,
152+
153+ /// Kafka topic for gas price updates
154+ #[ arg( long = "xlayer.gasprice.topic" ) ]
155+ pub topic : Option < String > ,
156+
157+ /// Kafka consumer group ID
158+ #[ arg( long = "xlayer.gasprice.group-id" ) ]
159+ pub group_id : Option < String > ,
160+
161+ /// Kafka username for authentication
162+ #[ arg( long = "xlayer.gasprice.username" ) ]
163+ pub username : Option < String > ,
164+
165+ /// Kafka password for authentication
166+ #[ arg( long = "xlayer.gasprice.password" ) ]
167+ pub password : Option < String > ,
168+
169+ /// Path to Kafka root CA certificate
170+ #[ arg( long = "xlayer.gasprice.root-ca-path" ) ]
171+ pub root_ca_path : Option < String > ,
172+
173+ /// L1 coin ID for price tracking
174+ #[ arg( long = "xlayer.gasprice.l1-coin-id" ) ]
175+ pub l1_coin_id : Option < i32 > ,
176+
177+ /// L2 coin ID for price tracking
178+ #[ arg( long = "xlayer.gasprice.l2-coin-id" ) ]
179+ pub l2_coin_id : Option < i32 > ,
180+
181+ /// Default L1 coin price (fallback value)
182+ #[ arg( long = "xlayer.gasprice.default-l1-coin-price" ) ]
183+ pub default_l1_coin_price : Option < f64 > ,
184+
185+ /// Default L2 coin price (fallback value)
186+ #[ arg( long = "xlayer.gasprice.default-l2-coin-price" ) ]
187+ pub default_l2_coin_price : Option < f64 > ,
188+
189+ /// Fixed gas price in USDT (for "fixed" type)
190+ #[ arg( long = "xlayer.gasprice.gas-price-usdt" ) ]
191+ pub gas_price_usdt : Option < f64 > ,
192+
193+ /// Congestion threshold for dynamic gas price adjustment
194+ #[ arg( long = "xlayer.gasprice.congestion-threshold" ) ]
195+ pub congestion_threshold : Option < i32 > ,
196+
197+ /// Default gas price for XLayer (in wei)
198+ #[ arg( long = "xlayer.gasprice.default" ) ]
199+ pub default : Option < U256 > ,
200+ }
201+
202+ /// Helper function to parse duration from string
203+ fn parse_duration ( s : & str ) -> Result < Duration , String > {
204+ // Parse duration string like "10s", "5m", "1h"
205+ let s = s. trim ( ) ;
206+ if s. is_empty ( ) {
207+ return Err ( "empty duration string" . to_string ( ) ) ;
208+ }
209+
210+ let ( num_str, unit) = if let Some ( pos) = s. find ( |c : char | !c. is_ascii_digit ( ) ) {
211+ ( & s[ ..pos] , & s[ pos..] )
212+ } else {
213+ return Err ( "duration must have a unit (s, m, h)" . to_string ( ) ) ;
214+ } ;
215+
216+ let num: u64 = num_str. parse ( ) . map_err ( |e| format ! ( "invalid number: {}" , e) ) ?;
217+
218+ match unit {
219+ "s" | "sec" | "second" | "seconds" => Ok ( Duration :: from_secs ( num) ) ,
220+ "m" | "min" | "minute" | "minutes" => Ok ( Duration :: from_secs ( num * 60 ) ) ,
221+ "h" | "hour" | "hours" => Ok ( Duration :: from_secs ( num * 3600 ) ) ,
222+ "ms" | "millisecond" | "milliseconds" => Ok ( Duration :: from_millis ( num) ) ,
223+ _ => Err ( format ! ( "unknown duration unit: {}" , unit) ) ,
224+ }
225+ }
226+
227+ impl Default for XLayerGasPriceArgs {
228+ fn default ( ) -> Self {
229+ Self {
230+ price_type : None ,
231+ update_period : None ,
232+ factor : None ,
233+ kafka_url : None ,
234+ topic : None ,
235+ group_id : None ,
236+ username : None ,
237+ password : None ,
238+ root_ca_path : None ,
239+ l1_coin_id : None ,
240+ l2_coin_id : None ,
241+ default_l1_coin_price : None ,
242+ default_l2_coin_price : None ,
243+ gas_price_usdt : None ,
244+ congestion_threshold : None ,
245+ default : None ,
246+ }
247+ }
248+ }
249+
250+ impl reth_xlayer_gasprice:: suggester:: XLayerGasPriceArgsTrait for XLayerGasPriceArgs {
251+ fn price_type ( & self ) -> Option < & str > {
252+ self . price_type . as_deref ( )
253+ }
254+
255+ fn default ( & self ) -> Option < alloy_primitives:: U256 > {
256+ self . default
257+ }
258+ }
259+
125260#[ cfg( test) ]
126261mod tests {
127262 use super :: * ;
@@ -141,6 +276,7 @@ mod tests {
141276 assert_eq ! ( args. intercept. enabled, default_args. intercept. enabled) ;
142277 assert_eq ! ( args. intercept. bridge_contract, default_args. intercept. bridge_contract) ;
143278 assert_eq ! ( args. intercept. target_token, default_args. intercept. target_token) ;
279+ assert_eq ! ( args. gas_price, default_args. gas_price) ;
144280 assert ! ( args. validate( ) . is_ok( ) ) ;
145281 }
146282
@@ -321,4 +457,27 @@ mod tests {
321457 assert ! ( config. enabled) ;
322458 assert ! ( !config. wildcard) ;
323459 }
460+
461+ #[ test]
462+ fn test_parse_xlayer_gas_price_args ( ) {
463+ let args = CommandParser :: < XLayerGasPriceArgs > :: parse_from ( [
464+ "reth" ,
465+ "--xlayer.gasprice.type" ,
466+ "follower" ,
467+ "--xlayer.gasprice.factor" ,
468+ "1.5" ,
469+ ] )
470+ . args ;
471+ assert_eq ! ( args. price_type, Some ( "follower" . to_string( ) ) ) ;
472+ assert_eq ! ( args. factor, Some ( 1.5 ) ) ;
473+ }
474+
475+ #[ test]
476+ fn test_parse_duration ( ) {
477+ assert_eq ! ( parse_duration( "10s" ) . unwrap( ) , Duration :: from_secs( 10 ) ) ;
478+ assert_eq ! ( parse_duration( "5m" ) . unwrap( ) , Duration :: from_secs( 300 ) ) ;
479+ assert_eq ! ( parse_duration( "1h" ) . unwrap( ) , Duration :: from_secs( 3600 ) ) ;
480+ assert_eq ! ( parse_duration( "500ms" ) . unwrap( ) , Duration :: from_millis( 500 ) ) ;
481+ assert ! ( parse_duration( "invalid" ) . is_err( ) ) ;
482+ }
324483}
0 commit comments