55//! readiness checks using simple transaction receipt verification without
66//! complex attestation mechanisms.
77
8- use crate :: { SettlementError , SettlementInterface } ;
9- use alloy_primitives:: { hex, Address as AlloyAddress , FixedBytes } ;
8+ use crate :: {
9+ utils:: parse_oracle_config, OracleConfig , SettlementError ,
10+ SettlementInterface ,
11+ } ;
12+ use alloy_primitives:: { hex, FixedBytes } ;
1013use alloy_provider:: { Provider , RootProvider } ;
1114use alloy_rpc_types:: BlockTransactionsKind ;
1215use alloy_transport_http:: Http ;
1316use async_trait:: async_trait;
1417use solver_types:: {
15- without_0x_prefix , ConfigSchema , Eip7683OrderData , Field , FieldType , FillProof , NetworksConfig ,
18+ with_0x_prefix , ConfigSchema , Eip7683OrderData , Field , FieldType , FillProof , NetworksConfig ,
1619 Order , Schema , TransactionHash ,
1720} ;
1821use std:: collections:: HashMap ;
@@ -22,35 +25,38 @@ use std::collections::HashMap;
2225/// This implementation validates fills by checking transaction receipts
2326/// and manages dispute periods before allowing claims.
2427pub struct DirectSettlement {
25- /// The order type this implementation handles
26- order : String ,
27- /// Supported network IDs
28- network_ids : Vec < u64 > ,
2928 /// RPC providers for each supported network.
3029 providers : HashMap < u64 , RootProvider < Http < reqwest:: Client > > > ,
31- /// Oracle addresses for each network (network_id -> oracle_address).
32- oracle_addresses : HashMap < u64 , String > ,
30+ /// Oracle configuration including addresses and routes
31+ oracle_config : OracleConfig ,
3332 /// Dispute period duration in seconds.
3433 dispute_period_seconds : u64 ,
3534}
3635
3736impl DirectSettlement {
3837 /// Creates a new DirectSettlement instance.
3938 ///
40- /// Configures settlement validation with multiple networks, oracle addresses,
39+ /// Configures settlement validation with oracle configuration
4140 /// and dispute period.
4241 pub async fn new (
43- order : String ,
44- network_ids : Vec < u64 > ,
4542 networks : & NetworksConfig ,
46- oracle_addresses : HashMap < u64 , String > ,
43+ oracle_config : OracleConfig ,
4744 dispute_period_seconds : u64 ,
4845 ) -> Result < Self , SettlementError > {
49- // Create RPC providers for each supported network
46+ // Create RPC providers for each network that has oracles configured
5047 let mut providers = HashMap :: new ( ) ;
48+ let mut all_network_ids = std:: collections:: HashSet :: new ( ) ;
5149
52- for network_id in & network_ids {
53- let network = networks. get ( network_id) . ok_or_else ( || {
50+ // Collect all network IDs from input and output oracles
51+ for network_id in oracle_config. input_oracles . keys ( ) {
52+ all_network_ids. insert ( * network_id) ;
53+ }
54+ for network_id in oracle_config. output_oracles . keys ( ) {
55+ all_network_ids. insert ( * network_id) ;
56+ }
57+
58+ for network_id in all_network_ids {
59+ let network = networks. get ( & network_id) . ok_or_else ( || {
5460 SettlementError :: ValidationFailed ( format ! (
5561 "Network {} not found in configuration" ,
5662 network_id
@@ -70,26 +76,12 @@ impl DirectSettlement {
7076 ) )
7177 } ) ?) ;
7278
73- providers. insert ( * network_id, provider) ;
74- }
75-
76- // Validate oracle addresses
77- let mut validated_oracle_addresses = HashMap :: new ( ) ;
78- for ( network_id, oracle_address) in oracle_addresses {
79- let oracle = oracle_address. parse :: < AlloyAddress > ( ) . map_err ( |e| {
80- SettlementError :: ValidationFailed ( format ! (
81- "Invalid oracle address for network {}: {}" ,
82- network_id, e
83- ) )
84- } ) ?;
85- validated_oracle_addresses. insert ( network_id, oracle. to_string ( ) ) ;
79+ providers. insert ( network_id, provider) ;
8680 }
8781
8882 Ok ( Self {
89- order,
90- network_ids : network_ids. clone ( ) ,
9183 providers,
92- oracle_addresses : validated_oracle_addresses ,
84+ oracle_config ,
9385 dispute_period_seconds,
9486 } )
9587 }
@@ -111,53 +103,27 @@ impl ConfigSchema for DirectSettlementSchema {
111103 let schema = Schema :: new (
112104 // Required fields
113105 vec ! [
114- Field :: new( "order" , FieldType :: String ) ,
115106 Field :: new(
116- "network_ids " ,
117- FieldType :: Array ( Box :: new ( FieldType :: Integer {
118- min: Some ( 1 ) ,
119- max: None ,
120- } ) ) ,
107+ "dispute_period_seconds " ,
108+ FieldType :: Integer {
109+ min: Some ( 0 ) ,
110+ max: Some ( 86400 ) ,
111+ } ,
121112 ) ,
122113 Field :: new(
123- "oracle_addresses " ,
114+ "oracles " ,
124115 FieldType :: Table ( Schema :: new(
125- vec![ ] , // No required fields - network IDs are dynamic
126- vec![ ] , // No optional fields - all entries should be valid addresses
116+ vec![
117+ Field :: new( "input" , FieldType :: Table ( Schema :: new( vec![ ] , vec![ ] ) ) ) ,
118+ Field :: new( "output" , FieldType :: Table ( Schema :: new( vec![ ] , vec![ ] ) ) ) ,
119+ ] ,
120+ vec![ ] ,
127121 ) ) ,
128- )
129- . with_validator( |value| {
130- // Validate that all values in the table are valid Ethereum addresses
131- if let Some ( table) = value. as_table( ) {
132- for ( network_id, address_value) in table {
133- if let Some ( addr) = address_value. as_str( ) {
134- if addr. len( ) != 42 || !addr. starts_with( "0x" ) {
135- return Err ( format!(
136- "oracle_addresses.{} must be a valid Ethereum address" ,
137- network_id
138- ) ) ;
139- }
140- } else {
141- return Err ( format!(
142- "oracle_addresses.{} must be a string" ,
143- network_id
144- ) ) ;
145- }
146- }
147- Ok ( ( ) )
148- } else {
149- Err ( "oracle_addresses must be a table" . to_string( ) )
150- }
151- } ) ,
122+ ) ,
123+ Field :: new( "routes" , FieldType :: Table ( Schema :: new( vec![ ] , vec![ ] ) ) ) ,
152124 ] ,
153125 // Optional fields
154- vec ! [ Field :: new(
155- "dispute_period_seconds" ,
156- FieldType :: Integer {
157- min: Some ( 0 ) ,
158- max: Some ( 86400 ) ,
159- } ,
160- ) ] ,
126+ vec ! [ Field :: new( "oracle_selection_strategy" , FieldType :: String ) ] ,
161127 ) ;
162128
163129 schema. validate ( config)
@@ -166,39 +132,14 @@ impl ConfigSchema for DirectSettlementSchema {
166132
167133#[ async_trait]
168134impl SettlementInterface for DirectSettlement {
169- fn supported_order ( & self ) -> & str {
170- & self . order
171- }
172-
173- fn supported_networks ( & self ) -> & [ u64 ] {
174- & self . network_ids
135+ fn oracle_config ( & self ) -> & OracleConfig {
136+ & self . oracle_config
175137 }
176138
177139 fn config_schema ( & self ) -> Box < dyn ConfigSchema > {
178140 Box :: new ( DirectSettlementSchema )
179141 }
180142
181- /// Returns the oracle address configured for a specific chain.
182- ///
183- /// This implementation stores oracle addresses per chain in its configuration.
184- /// The addresses are stored as hex strings and converted to the Address type on demand.
185- ///
186- /// # Arguments
187- /// * `chain_id` - The chain ID to get the oracle address for
188- ///
189- /// # Returns
190- /// * `Some(Address)` if an oracle is configured for this chain and the address is valid
191- /// * `None` if no oracle is configured or the address format is invalid
192- fn get_oracle_address ( & self , chain_id : u64 ) -> Option < solver_types:: Address > {
193- self . oracle_addresses . get ( & chain_id) . and_then ( |addr_str| {
194- let hex_str = without_0x_prefix ( addr_str) ;
195- hex:: decode ( hex_str)
196- . ok ( )
197- . filter ( |bytes| bytes. len ( ) == 20 )
198- . map ( solver_types:: Address )
199- } )
200- }
201-
202143 /// Gets attestation data for a filled order and generates a fill proof.
203144 ///
204145 /// Since the transaction is already confirmed by the delivery service,
@@ -233,13 +174,25 @@ impl SettlementInterface for DirectSettlement {
233174 ) )
234175 } ) ?;
235176
236- // Get the oracle address for this chain
237- let oracle_address = self . oracle_addresses . get ( & origin_chain_id) . ok_or_else ( || {
238- SettlementError :: ValidationFailed ( format ! (
239- "No oracle address configured for chain {}" ,
177+ // Get the oracle address for this chain using the selection strategy
178+ let oracle_addresses = self . get_input_oracles ( origin_chain_id) ;
179+ if oracle_addresses. is_empty ( ) {
180+ return Err ( SettlementError :: ValidationFailed ( format ! (
181+ "No input oracle configured for chain {}" ,
240182 origin_chain_id
241- ) )
242- } ) ?;
183+ ) ) ) ;
184+ }
185+
186+ // Use selection strategy with order nonce as context for deterministic selection
187+ let selection_context = order_data. nonce . to :: < u64 > ( ) ;
188+ let oracle_address = self
189+ . select_oracle ( & oracle_addresses, Some ( selection_context) )
190+ . ok_or_else ( || {
191+ SettlementError :: ValidationFailed ( format ! (
192+ "Failed to select oracle for chain {}" ,
193+ origin_chain_id
194+ ) )
195+ } ) ?;
243196
244197 // Convert tx hash
245198 let hash = FixedBytes :: < 32 > :: from_slice ( & tx_hash. 0 ) ;
@@ -283,7 +236,7 @@ impl SettlementInterface for DirectSettlement {
283236 Ok ( FillProof {
284237 tx_hash : tx_hash. clone ( ) ,
285238 block_number : tx_block,
286- oracle_address : oracle_address. clone ( ) ,
239+ oracle_address : with_0x_prefix ( & hex :: encode ( & oracle_address. 0 ) ) ,
287240 attestation_data : Some ( order_data. order_id . to_vec ( ) ) ,
288241 filled_timestamp : block_timestamp,
289242 } )
@@ -361,51 +314,8 @@ pub fn create_settlement(
361314 DirectSettlementSchema :: validate_config ( config)
362315 . map_err ( |e| SettlementError :: ValidationFailed ( format ! ( "Invalid configuration: {}" , e) ) ) ?;
363316
364- // Get order type
365- let order_standard = config
366- . get ( "order" )
367- . and_then ( |v| v. as_str ( ) )
368- . ok_or_else ( || SettlementError :: ValidationFailed ( "order is required" . to_string ( ) ) ) ?
369- . to_string ( ) ;
370-
371- // Get network IDs
372- let network_ids = config
373- . get ( "network_ids" )
374- . and_then ( |v| v. as_array ( ) )
375- . ok_or_else ( || SettlementError :: ValidationFailed ( "network_ids is required" . to_string ( ) ) ) ?
376- . iter ( )
377- . filter_map ( |v| v. as_integer ( ) . map ( |i| i as u64 ) )
378- . collect :: < Vec < _ > > ( ) ;
379-
380- if network_ids. is_empty ( ) {
381- return Err ( SettlementError :: ValidationFailed (
382- "network_ids cannot be empty" . to_string ( ) ,
383- ) ) ;
384- }
385-
386- // Get oracle addresses table
387- let addresses_table = config
388- . get ( "oracle_addresses" )
389- . and_then ( |v| v. as_table ( ) )
390- . ok_or_else ( || {
391- SettlementError :: ValidationFailed ( "oracle_addresses is required" . to_string ( ) )
392- } ) ?;
393-
394- // Build oracle addresses map
395- let mut oracle_addresses = HashMap :: new ( ) ;
396- for network_id in & network_ids {
397- let network_id_str = network_id. to_string ( ) ;
398- let address = addresses_table
399- . get ( & network_id_str)
400- . and_then ( |v| v. as_str ( ) )
401- . ok_or_else ( || {
402- SettlementError :: ValidationFailed ( format ! (
403- "oracle_addresses missing entry for network {}" ,
404- network_id
405- ) )
406- } ) ?;
407- oracle_addresses. insert ( * network_id, address. to_string ( ) ) ;
408- }
317+ // Parse oracle configuration using common utilities
318+ let oracle_config = parse_oracle_config ( config) ?;
409319
410320 let dispute_period_seconds = config
411321 . get ( "dispute_period_seconds" )
@@ -415,14 +325,7 @@ pub fn create_settlement(
415325 // Create settlement service synchronously
416326 let settlement = tokio:: task:: block_in_place ( || {
417327 tokio:: runtime:: Handle :: current ( ) . block_on ( async {
418- DirectSettlement :: new (
419- order_standard,
420- network_ids,
421- networks,
422- oracle_addresses,
423- dispute_period_seconds,
424- )
425- . await
328+ DirectSettlement :: new ( networks, oracle_config, dispute_period_seconds) . await
426329 } )
427330 } ) ?;
428331
0 commit comments