77"""
88
99import os
10- import re
1110from dataclasses import dataclass , field
1211
12+ from .utils .multi_rpc_provider import sanitize_url
1313
14- def parse_indexed_rpc_urls (prefix : str ) -> list [str ]:
14+
15+ def parse_rpc_urls () -> list [str ]:
1516 """
16- Parse indexed environment variables like PREFIX_1, PREFIX_2, etc .
17+ Parse SOURCE_RPC_URLS env var into a list of RPC endpoint URLs .
1718
18- Args:
19- prefix: The base name without the index (e.g., "SOURCE_RPC_URL")
19+ Splits on commas, strips whitespace, and filters empty entries.
2020
2121 Returns:
22- List of URLs in ascending index order
22+ List of non-empty, trimmed URLs
2323
2424 Raises:
25- ValueError: If no indexed environment variables are found
25+ ValueError: If SOURCE_RPC_URLS is missing or contains no valid URLs
2626 """
27- pattern = re .compile (rf"^{ re .escape (prefix )} _(\d+)$" )
28- indexed_urls : list [tuple [int , str ]] = []
29-
30- for key , value in os .environ .items ():
31- match = pattern .match (key )
32- if match :
33- index = int (match .group (1 ))
34- indexed_urls .append ((index , value ))
27+ raw = os .environ .get ("SOURCE_RPC_URLS" , "" )
28+ urls = [url .strip () for url in raw .split ("," ) if url .strip ()]
3529
36- if not indexed_urls :
37- raise ValueError (f"No { prefix } _N environment variables found " )
30+ if not urls :
31+ raise ValueError ("SOURCE_RPC_URLS environment variable is missing or empty " )
3832
39- # Sort by index and return just the URLs
40- indexed_urls .sort (key = lambda x : x [0 ])
41- return [url for _ , url in indexed_urls ]
33+ return urls
4234
4335
4436@dataclass (frozen = True , slots = True )
4537class SourceChainConfig :
4638 """Configuration for the source chain (e.g., Base/Sepolia)."""
4739
48- rpc_url : str
40+ rpc_urls : list [ str ]
4941 paymaster_vault_address : str
5042
5143
@@ -68,7 +60,9 @@ class MonitoringConfig:
6860 retry_count : int = 3
6961 lookback_blocks : int = 9
7062 process_batch_size : int = 10 # max events to process in one batch
71- max_block_range : int = 10 # max blocks per get_logs request (Alchemy free tier limit)
63+ max_block_range : int = (
64+ 10 # max blocks per get_logs request (Alchemy free tier limit)
65+ )
7266
7367 def __post_init__ (self ) -> None :
7468 """Validate monitoring configuration."""
@@ -100,7 +94,6 @@ def __post_init__(self) -> None:
10094 f"Lookback blocks too high (max 1000), got { self .lookback_blocks } "
10195 )
10296
103-
10497 # Validate batch size
10598 if self .process_batch_size <= 0 :
10699 raise ValueError (
@@ -121,6 +114,7 @@ def __post_init__(self) -> None:
121114 f"Max block range too large (max 10000), got { self .max_block_range } "
122115 )
123116
117+
124118@dataclass (frozen = True , slots = True )
125119class RelayerConfig :
126120 """Main configuration class for the ROFL Relayer."""
@@ -141,13 +135,14 @@ def from_env(cls, local_mode: bool = False) -> "RelayerConfig":
141135 Raises:
142136 ValueError: If required environment variables are missing
143137 """
144- # Source chain configuration
145- source_rpc_url = os .environ .get ("SOURCE_RPC_URL" )
146- if not source_rpc_url :
138+ # Source chain configuration - parse comma-delimited RPC URLs
139+ try :
140+ source_rpc_urls = parse_rpc_urls ()
141+ except ValueError :
147142 raise ValueError (
148- "SOURCE_RPC_URL environment variable is required. "
149- "Example: https://ethereum-sepolia.publicnode .com"
150- )
143+ "SOURCE_RPC_URLS environment variable is required (comma-separated) . "
144+ "Example: SOURCE_RPC_URLS= https://rpc1.example.com,https://rpc2.example .com"
145+ ) from None
151146
152147 paymaster_vault_address = os .environ .get ("PAYMASTER_VAULT_ADDRESS" )
153148 if not paymaster_vault_address :
@@ -204,7 +199,7 @@ def from_env(cls, local_mode: bool = False) -> "RelayerConfig":
204199
205200 # Create configuration objects
206201 source_chain = SourceChainConfig (
207- rpc_url = source_rpc_url ,
202+ rpc_urls = source_rpc_urls ,
208203 paymaster_vault_address = paymaster_vault_address ,
209204 )
210205
@@ -228,11 +223,13 @@ def log_config(self) -> None:
228223 print (f"Mode: { 'LOCAL' if self .local_mode else 'ROFL' } " )
229224
230225 print ("\n [Source Chain]" )
231- print (f" RPC URL: { self .source_chain .rpc_url } " )
226+ print (f" RPC URLs ({ len (self .source_chain .rpc_urls )} configured):" )
227+ for i , url in enumerate (self .source_chain .rpc_urls , 1 ):
228+ print (f" [{ i } ] { sanitize_url (url )} " )
232229 print (f" PaymasterVault: { self .source_chain .paymaster_vault_address } " )
233230
234231 print ("\n [Target Chain]" )
235- print (f" RPC URL: { self .target_chain .rpc_url } " )
232+ print (f" RPC URL: { sanitize_url ( self .target_chain .rpc_url ) } " )
236233 print (f" CrossChainPaymaster: { self .target_chain .paymaster_address } " )
237234 print (f" ROFLAdapter: { self .target_chain .rofl_adapter_address } " )
238235 print (
0 commit comments