@@ -3,57 +3,143 @@ use alloy_chains::NamedChain;
33use std:: env;
44use thiserror:: Error ;
55
6- pub type AlchemyResult < T > = Result < T , AlchemyError > ;
6+ pub type RpcUrlResult < T > = Result < T , RpcUrlError > ;
77
88#[ derive( Error , Debug , Clone ) ]
9- pub enum AlchemyError {
10- #[ error( "The alchemy subdomain was not found for this chain." ) ]
9+ pub enum RpcUrlError {
10+ #[ error( "No RPC provider subdomain was found for this chain." ) ]
1111 SubdomainNotFound ,
12- #[ error( "The Alchemy API key is not set in the environment." ) ]
12+ #[ error( "The RPC provider API key is not set in the environment." ) ]
1313 ApiKeyEnvVarNotSet ,
1414 #[ error( "The url could not be parsed." ) ]
1515 UrlParsingError ,
1616}
1717
18- /// Returns the Alchemy RPC URL for the given chain.
19- pub fn alchemy_url ( chain : & NamedChain ) -> AlchemyResult < Url > {
20- dotenvy:: dotenv ( ) . ok ( ) ;
18+ /// The RPC providers we can build URLs for.
19+ ///
20+ /// Each variant knows how to turn a `(subdomain, api_key)` pair into a URL and
21+ /// which environment variable holds its API key. Add a variant here when
22+ /// onboarding a new provider, then wire its chains up in [`rpc_provider`].
23+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
24+ pub enum RpcProvider {
25+ Alchemy ,
26+ NowNodes ,
27+ }
28+
29+ impl RpcProvider {
30+ /// The environment variable holding this provider's API key.
31+ pub const fn api_key_env_var ( self ) -> & ' static str {
32+ match self {
33+ RpcProvider :: Alchemy => "ALCHEMY_API_KEY" ,
34+ RpcProvider :: NowNodes => "NOWNODES_API_KEY" ,
35+ }
36+ }
37+
38+ /// Builds the RPC URL for the given subdomain, reading the API key from the
39+ /// environment.
40+ fn build_url ( self , subdomain : & str ) -> RpcUrlResult < Url > {
41+ let api_key =
42+ env:: var ( self . api_key_env_var ( ) ) . map_err ( |_| RpcUrlError :: ApiKeyEnvVarNotSet ) ?;
2143
22- format ! (
23- "https://{subdomain}.g.alchemy.com/v2/{api_key}" ,
24- subdomain = alchemy_subdomain ( chain ) ? ,
25- api_key = env :: var ( "ALCHEMY_API_KEY" ) . map_err ( |_| AlchemyError :: ApiKeyEnvVarNotSet ) ?
26- )
27- . parse ( )
28- . map_err ( |_| AlchemyError :: UrlParsingError )
44+ let url = match self {
45+ RpcProvider :: Alchemy => format ! ( "https://{subdomain}.g.alchemy.com/v2/{api_key}" ) ,
46+ RpcProvider :: NowNodes => format ! ( "https://{subdomain}.nownodes.io/{api_key}" ) ,
47+ } ;
48+
49+ url . parse ( ) . map_err ( |_| RpcUrlError :: UrlParsingError )
50+ }
2951}
3052
31- /// Returns the Alchemy subdomain for the given chain.
32- pub fn alchemy_subdomain ( chain : & NamedChain ) -> AlchemyResult < & ' static str > {
53+ /// Returns the RPC provider and subdomain configured for the given chain.
54+ ///
55+ /// This is the single place that maps chains to providers; extend it when a new
56+ /// chain is added or moved between providers. Chains served by Alchemy and those
57+ /// served by NowNodes are matched side by side here.
58+ pub fn rpc_provider ( chain : & NamedChain ) -> RpcUrlResult < ( RpcProvider , & ' static str ) > {
3359 use NamedChain :: * ;
60+ use RpcProvider :: * ;
3461
35- match chain {
36- Mainnet => Ok ( "eth-mainnet" ) ,
37- Sepolia => Ok ( "eth-sepolia" ) ,
62+ Ok ( match chain {
63+ // Alchemy
64+ Mainnet => ( Alchemy , "eth-mainnet" ) ,
65+ Sepolia => ( Alchemy , "eth-sepolia" ) ,
66+ //
67+ Arbitrum => ( Alchemy , "arb-mainnet" ) ,
68+ ArbitrumSepolia => ( Alchemy , "arb-sepolia" ) ,
3869 //
39- Arbitrum => Ok ( "arb -mainnet") ,
40- ArbitrumSepolia => Ok ( "arb -sepolia") ,
70+ Optimism => ( Alchemy , "opt -mainnet") ,
71+ OptimismSepolia => ( Alchemy , "opt -sepolia") ,
4172 //
42- Optimism => Ok ( "opt -mainnet") ,
43- OptimismSepolia => Ok ( "opt -sepolia") ,
73+ Base => ( Alchemy , "base -mainnet") ,
74+ BaseSepolia => ( Alchemy , "base -sepolia") ,
4475 //
45- Base => Ok ( "base -mainnet") ,
46- BaseSepolia => Ok ( "base-sepolia ") ,
76+ Polygon => ( Alchemy , "polygon -mainnet") ,
77+ PolygonAmoy => ( Alchemy , "polygon-amoy ") ,
4778 //
48- Polygon => Ok ( "polygon -mainnet") ,
49- PolygonAmoy => Ok ( "polygon-amoy ") ,
79+ BinanceSmartChain => ( Alchemy , "bnb -mainnet") ,
80+ BinanceSmartChainTestnet => ( Alchemy , "bnb-testnet ") ,
5081 //
51- BinanceSmartChain => Ok ( "bnb -mainnet") ,
52- BinanceSmartChainTestnet => Ok ( "bnb -testnet") ,
82+ Monad => ( Alchemy , "monad -mainnet") ,
83+ MonadTestnet => ( Alchemy , "monad -testnet") ,
5384 //
54- Monad => Ok ( "monad-mainnet" ) ,
55- MonadTestnet => Ok ( "monad-testnet" ) ,
85+ StableMainnet => ( Alchemy , "stable-mainnet" ) ,
86+ StableTestnet => ( Alchemy , "stable-testnet" ) ,
87+ //
88+ MegaEth => ( Alchemy , "megaeth-mainnet" ) ,
89+ MegaEthTestnet => ( Alchemy , "megaeth-testnet" ) ,
90+
91+ // NowNodes
92+ Aurora => ( NowNodes , "aurora" ) ,
93+
94+ _ => return Err ( RpcUrlError :: SubdomainNotFound ) ,
95+ } )
96+ }
97+
98+ /// Returns the RPC URL for the given chain, using whichever provider is
99+ /// configured for it in [`rpc_provider`].
100+ pub fn rpc_url ( chain : & NamedChain ) -> RpcUrlResult < Url > {
101+ dotenvy:: dotenv ( ) . ok ( ) ;
102+
103+ let ( provider, subdomain) = rpc_provider ( chain) ?;
104+ provider. build_url ( subdomain)
105+ }
106+
107+ // ---------------------------------------------------------------------------
108+ // Backwards-compatible API
109+ //
110+ // The helpers below predate [`rpc_url`]/[`rpc_provider`] and are kept so
111+ // existing callers keep compiling. Prefer [`rpc_url`] for new code.
112+ // ---------------------------------------------------------------------------
113+
114+ /// Backwards-compatible alias for [`RpcUrlResult`].
115+ pub type AlchemyResult < T > = RpcUrlResult < T > ;
116+ /// Backwards-compatible alias for [`RpcUrlError`].
117+ pub type AlchemyError = RpcUrlError ;
118+
119+ /// Returns the Alchemy RPC URL for the given chain.
120+ pub fn alchemy_url ( chain : & NamedChain ) -> RpcUrlResult < Url > {
121+ dotenvy:: dotenv ( ) . ok ( ) ;
122+ RpcProvider :: Alchemy . build_url ( alchemy_subdomain ( chain) ?)
123+ }
124+
125+ /// Returns the Alchemy subdomain for the given chain.
126+ pub fn alchemy_subdomain ( chain : & NamedChain ) -> RpcUrlResult < & ' static str > {
127+ match rpc_provider ( chain) ? {
128+ ( RpcProvider :: Alchemy , subdomain) => Ok ( subdomain) ,
129+ _ => Err ( RpcUrlError :: SubdomainNotFound ) ,
130+ }
131+ }
132+
133+ /// Returns the NowNodes RPC URL for the given chain.
134+ pub fn nownodes_url ( chain : & NamedChain ) -> RpcUrlResult < Url > {
135+ dotenvy:: dotenv ( ) . ok ( ) ;
136+ RpcProvider :: NowNodes . build_url ( nownodes_subdomain ( chain) ?)
137+ }
56138
57- _ => Err ( AlchemyError :: SubdomainNotFound ) ,
139+ /// Returns the NowNodes subdomain for the given chain.
140+ pub fn nownodes_subdomain ( chain : & NamedChain ) -> RpcUrlResult < & ' static str > {
141+ match rpc_provider ( chain) ? {
142+ ( RpcProvider :: NowNodes , subdomain) => Ok ( subdomain) ,
143+ _ => Err ( RpcUrlError :: SubdomainNotFound ) ,
58144 }
59145}
0 commit comments