@@ -84,6 +84,22 @@ fn quote_hyperlane_message_gas_limit(payload_size: usize) -> U256 {
8484 U256 :: from ( base_gas + ( payload_size * gas_per_byte) + buffer)
8585}
8686
87+ fn extra_native_fee_configured ( config : & Config , chain_id : u64 ) -> bool {
88+ let chain_key = chain_id. to_string ( ) ;
89+ config
90+ . delivery
91+ . implementations
92+ . values ( )
93+ . any ( |implementation| {
94+ implementation
95+ . get ( "fee_policy" )
96+ . and_then ( |fee_policy| fee_policy. get ( "chains" ) )
97+ . and_then ( |chains| chains. get ( & chain_key) )
98+ . and_then ( |chain| chain. get ( "extra_native_fee" ) )
99+ . is_some ( )
100+ } )
101+ }
102+
87103#[ allow( clippy:: too_many_arguments) ]
88104fn encode_quote_hyperlane_fill_description (
89105 solver_identifier : [ u8 ; 32 ] ,
@@ -888,6 +904,65 @@ impl CostProfitService {
888904 gas_units. post_fill_units = live_units;
889905 }
890906
907+ let mut l1_data_fee_wei = U256 :: ZERO ;
908+ let mut l1_data_fee_buffer_wei = U256 :: ZERO ;
909+
910+ match self
911+ . build_fill_tx_for_quote (
912+ request,
913+ context,
914+ & swap_amounts_with_info,
915+ config,
916+ solver_address,
917+ )
918+ . await
919+ {
920+ Ok ( mut fill_tx) => {
921+ fill_tx. gas_limit = Some ( gas_units. fill_units ) ;
922+ let ( raw, buffer) = self
923+ . estimate_extra_native_fee_wei ( config, dest_chain_id, & fill_tx)
924+ . await ?;
925+ l1_data_fee_wei = l1_data_fee_wei. saturating_add ( raw) ;
926+ l1_data_fee_buffer_wei = l1_data_fee_buffer_wei. saturating_add ( buffer) ;
927+ } ,
928+ Err ( e) => {
929+ tracing:: warn!(
930+ chain_id = dest_chain_id,
931+ error = %e,
932+ "Skipping fill extra native fee estimate: failed to build synthetic fill tx"
933+ ) ;
934+ } ,
935+ }
936+
937+ match self
938+ . build_post_fill_tx_for_quote (
939+ request,
940+ context,
941+ & swap_amounts_with_info,
942+ config,
943+ solver_address,
944+ settlement_fee_wei,
945+ )
946+ . await
947+ {
948+ Ok ( Some ( mut post_fill) ) => {
949+ post_fill. tx . gas_limit = Some ( gas_units. post_fill_units ) ;
950+ let ( raw, buffer) = self
951+ . estimate_extra_native_fee_wei ( config, dest_chain_id, & post_fill. tx )
952+ . await ?;
953+ l1_data_fee_wei = l1_data_fee_wei. saturating_add ( raw) ;
954+ l1_data_fee_buffer_wei = l1_data_fee_buffer_wei. saturating_add ( buffer) ;
955+ } ,
956+ Ok ( None ) => { } ,
957+ Err ( e) => {
958+ tracing:: warn!(
959+ chain_id = dest_chain_id,
960+ error = %e,
961+ "Skipping post-fill extra native fee estimate: failed to build synthetic post-fill tx"
962+ ) ;
963+ } ,
964+ }
965+
891966 // Parse inputs/outputs to proper types for cost calculation
892967 let mut parsed_inputs = Vec :: new ( ) ;
893968 for input in inputs {
@@ -925,6 +1000,8 @@ impl CostProfitService {
9251000 dest_chain_id,
9261001 & gas_units,
9271002 settlement_fee_wei,
1003+ l1_data_fee_wei,
1004+ l1_data_fee_buffer_wei,
9281005 )
9291006 . await ?;
9301007
@@ -988,7 +1065,9 @@ impl CostProfitService {
9881065 cost_breakdown. gas_fill
9891066 + cost_breakdown. gas_post_fill
9901067 + cost_breakdown. settlement_fee
991- + cost_breakdown. settlement_fee_buffer ,
1068+ + cost_breakdown. settlement_fee_buffer
1069+ + cost_breakdown. l1_data_fee
1070+ + cost_breakdown. l1_data_fee_buffer ,
9921071 ) ;
9931072
9941073 // Calculate adjusted amounts (swap amounts +/- costs based on swap type)
@@ -1050,6 +1129,8 @@ impl CostProfitService {
10501129 dest_chain_id : u64 ,
10511130 gas_units : & GasUnits ,
10521131 settlement_fee_wei : U256 ,
1132+ l1_data_fee_wei : U256 ,
1133+ l1_data_fee_buffer_wei : U256 ,
10531134 ) -> Result < CostBreakdown , CostProfitError > {
10541135 // Read gas_buffer_bps from solver config (hot-reloadable)
10551136 let gas_buffer_bps_value = config. solver . gas_buffer_bps ;
@@ -1118,6 +1199,10 @@ impl CostProfitService {
11181199 Decimal :: new ( config. solver . settlement_fee_buffer_bps as i64 , 0 ) ;
11191200 let settlement_fee_buffer =
11201201 ( settlement_fee * settlement_fee_buffer_bps) / Decimal :: from ( 10000 ) ;
1202+ let ( l1_data_fee, l1_data_fee_buffer) = tokio:: try_join!(
1203+ self . wei_to_usd( & l1_data_fee_wei) ,
1204+ self . wei_to_usd( & l1_data_fee_buffer_wei) ,
1205+ ) ?;
11211206
11221207 // Input and output valuations do not depend on each other.
11231208 let ( total_input_value_usd, total_output_value_usd) = tokio:: try_join!(
@@ -1149,6 +1234,8 @@ impl CostProfitService {
11491234 + gas_claim + gas_buffer
11501235 + settlement_fee
11511236 + settlement_fee_buffer
1237+ + l1_data_fee
1238+ + l1_data_fee_buffer
11521239 + rate_buffer;
11531240
11541241 // Calculate subtotal (actual costs only, excluding profit)
@@ -1166,6 +1253,8 @@ impl CostProfitService {
11661253 gas_buffer,
11671254 settlement_fee,
11681255 settlement_fee_buffer,
1256+ l1_data_fee,
1257+ l1_data_fee_buffer,
11691258 rate_buffer,
11701259 base_price,
11711260 min_profit,
@@ -1405,6 +1494,15 @@ impl CostProfitService {
14051494 None => U256 :: ZERO ,
14061495 } ;
14071496
1497+ let ( l1_data_fee_wei, l1_data_fee_buffer_wei) = if let Some ( fill_tx) = fill_tx {
1498+ let mut fill_tx = fill_tx. clone ( ) ;
1499+ fill_tx. gas_limit = Some ( gas_units. fill_units ) ;
1500+ self . estimate_extra_native_fee_wei ( config, dest_chain_id, & fill_tx)
1501+ . await ?
1502+ } else {
1503+ ( U256 :: ZERO , U256 :: ZERO )
1504+ } ;
1505+
14081506 // Use the unified cost calculation method
14091507 let cost_breakdown = self
14101508 . calculate_total_cost (
@@ -1415,6 +1513,8 @@ impl CostProfitService {
14151513 dest_chain_id,
14161514 & gas_units,
14171515 settlement_fee_wei,
1516+ l1_data_fee_wei,
1517+ l1_data_fee_buffer_wei,
14181518 )
14191519 . await ?;
14201520
@@ -2537,6 +2637,35 @@ impl CostProfitService {
25372637 } )
25382638 }
25392639
2640+ async fn estimate_extra_native_fee_wei (
2641+ & self ,
2642+ config : & Config ,
2643+ chain_id : u64 ,
2644+ tx : & Transaction ,
2645+ ) -> Result < ( U256 , U256 ) , CostProfitError > {
2646+ if !extra_native_fee_configured ( config, chain_id) {
2647+ return Ok ( ( U256 :: ZERO , U256 :: ZERO ) ) ;
2648+ }
2649+
2650+ let estimate = self
2651+ . delivery_service
2652+ . estimate_extra_native_fee ( chain_id, tx)
2653+ . await
2654+ . map_err ( |e| APIError :: InternalServerError {
2655+ error_type : ApiErrorType :: ServiceError ,
2656+ message : format ! ( "Failed to estimate extra native fee: {e}" ) ,
2657+ } ) ?;
2658+ let raw = U256 :: from_str ( & estimate. raw_fee_wei ) . map_err ( |e| {
2659+ CostProfitError :: Calculation ( format ! ( "Failed to parse extra native raw fee wei: {e}" ) )
2660+ } ) ?;
2661+ let buffer = U256 :: from_str ( & estimate. buffer_wei ) . map_err ( |e| {
2662+ CostProfitError :: Calculation ( format ! (
2663+ "Failed to parse extra native fee buffer wei: {e}"
2664+ ) )
2665+ } ) ?;
2666+ Ok ( ( raw, buffer) )
2667+ }
2668+
25402669 async fn wei_to_usd ( & self , value_wei : & U256 ) -> Result < Decimal , CostProfitError > {
25412670 let usd_value = self
25422671 . pricing_service
@@ -3287,6 +3416,8 @@ mod tests {
32873416 gas_buffer : Decimal :: from_str ( "0.004" ) . unwrap ( ) ,
32883417 settlement_fee : Decimal :: ZERO ,
32893418 settlement_fee_buffer : Decimal :: ZERO ,
3419+ l1_data_fee : Decimal :: ZERO ,
3420+ l1_data_fee_buffer : Decimal :: ZERO ,
32903421 rate_buffer : Decimal :: ZERO ,
32913422 base_price : Decimal :: ZERO ,
32923423 min_profit : Decimal :: from_str ( "5.00" ) . unwrap ( ) ,
@@ -4133,11 +4264,11 @@ mod tests {
41334264 }
41344265
41354266 #[ tokio:: test]
4136- async fn test_calculate_total_cost_includes_optional_settlement_gas ( ) {
4267+ async fn test_calculate_total_cost_includes_optional_settlement_and_l1_data_fees ( ) {
41374268 let mut mock_pricing = MockPricingInterface :: new ( ) ;
41384269 mock_pricing
41394270 . expect_wei_to_currency ( )
4140- . times ( 6 )
4271+ . times ( 8 )
41414272 . returning ( |wei, _| {
41424273 let wei = wei. to_string ( ) ;
41434274 Box :: pin ( async move { Ok ( wei) } )
@@ -4205,6 +4336,8 @@ mod tests {
42054336 claim_units : 5 ,
42064337 } ,
42074338 U256 :: from ( 10u64 ) ,
4339+ U256 :: from ( 7u64 ) ,
4340+ U256 :: from ( 8u64 ) ,
42084341 )
42094342 . await
42104343 . unwrap ( ) ;
@@ -4217,7 +4350,9 @@ mod tests {
42174350 assert_eq ! ( breakdown. gas_buffer, Decimal :: from( 60 ) ) ;
42184351 assert_eq ! ( breakdown. settlement_fee, Decimal :: from( 10 ) ) ;
42194352 assert_eq ! ( breakdown. settlement_fee_buffer, Decimal :: from( 1 ) ) ;
4220- assert_eq ! ( breakdown. operational_cost, Decimal :: from( 671 ) ) ;
4353+ assert_eq ! ( breakdown. l1_data_fee, Decimal :: from( 7 ) ) ;
4354+ assert_eq ! ( breakdown. l1_data_fee_buffer, Decimal :: from( 8 ) ) ;
4355+ assert_eq ! ( breakdown. operational_cost, Decimal :: from( 686 ) ) ;
42214356 }
42224357
42234358 // ============================================================================
@@ -4318,6 +4453,8 @@ mod tests {
43184453 gas_buffer : Decimal :: from_str ( "0.004" ) . unwrap ( ) ,
43194454 settlement_fee : Decimal :: ZERO ,
43204455 settlement_fee_buffer : Decimal :: ZERO ,
4456+ l1_data_fee : Decimal :: ZERO ,
4457+ l1_data_fee_buffer : Decimal :: ZERO ,
43214458 rate_buffer : Decimal :: ZERO ,
43224459 base_price : Decimal :: ZERO ,
43234460 min_profit,
0 commit comments