x402_rs/chain/mod.rs
1//! Blockchain-specific types and providers for x402 payment processing.
2//!
3//! This module provides abstractions for interacting with different blockchain networks
4//! in the x402 protocol. It supports two major blockchain families:
5//!
6//! - **EIP-155 (EVM)**: Ethereum and EVM-compatible chains like Base, Polygon, Avalanche
7//! - **Solana**: The Solana blockchain
8//!
9//! # Architecture
10//!
11//! The module is organized around the concept of chain providers and chain identifiers:
12//!
13//! - [`ChainId`] - A CAIP-2 compliant chain identifier (e.g., `eip155:8453` for Base)
14//! - [`ChainIdPattern`] - Pattern matching for chain IDs (exact, wildcard, or set)
15//! - [`ChainProvider`] - Enum wrapping chain-specific providers
16//! - [`ChainRegistry`] - Registry of configured chain providers
17//!
18//! # Submodules
19//!
20//! - [`eip155`] - EVM chain support including transaction signing and ERC-3009 transfers
21//! - [`solana`] - Solana chain support including SPL token transfers
22//!
23//! # Example
24//!
25//! ```ignore
26//! use x402_rs::chain::{ChainId, ChainIdPattern};
27//!
28//! // Create a specific chain ID
29//! let base = ChainId::new("eip155", "8453");
30//!
31//! // Create a pattern that matches all EVM chains
32//! let all_evm = ChainIdPattern::wildcard("eip155");
33//! assert!(all_evm.matches(&base));
34//!
35//! // Create a pattern for specific chains
36//! let mainnet_chains = ChainIdPattern::set("eip155", ["1", "8453", "137"].into_iter().map(String::from).collect());
37//! assert!(mainnet_chains.matches(&base));
38//! ```
39
40mod chain_id;
41pub mod eip155;
42pub mod solana;
43
44pub use chain_id::*;
45
46use crate::config::{ChainConfig, ChainsConfig};
47use std::collections::HashMap;
48use std::sync::Arc;
49
50// FIXME doc comments
51#[async_trait::async_trait]
52pub trait FromConfig<TConfig>
53where
54 Self: Sized,
55{
56 async fn from_config(config: &TConfig) -> Result<Self, Box<dyn std::error::Error>>;
57}
58
59/// A blockchain provider that can interact with either EVM or Solana chains.
60///
61/// This enum wraps chain-specific providers and provides a unified interface
62/// for the facilitator to interact with different blockchain networks.
63///
64/// # Variants
65///
66/// - `Eip155` - Provider for EVM-compatible chains (Ethereum, Base, Polygon, etc.)
67/// - `Solana` - Provider for the Solana blockchain
68#[derive(Debug, Clone)]
69pub enum ChainProvider {
70 /// EVM chain provider for EIP-155 compatible networks.
71 Eip155(Arc<eip155::Eip155ChainProvider>),
72 /// Solana chain provider.
73 Solana(Arc<solana::SolanaChainProvider>),
74}
75
76/// Creates a new chain provider from configuration.
77///
78/// This factory method inspects the configuration type and creates the appropriate
79/// chain-specific provider (EVM or Solana).
80///
81/// # Errors
82///
83/// Returns an error if:
84/// - RPC connection fails
85/// - Signer configuration is invalid
86/// - Required configuration is missing
87#[async_trait::async_trait]
88impl FromConfig<ChainConfig> for ChainProvider {
89 async fn from_config(chains: &ChainConfig) -> Result<Self, Box<dyn std::error::Error>> {
90 let provider = match chains {
91 ChainConfig::Eip155(config) => {
92 let provider = eip155::Eip155ChainProvider::from_config(config).await?;
93 ChainProvider::Eip155(Arc::new(provider))
94 }
95 ChainConfig::Solana(config) => {
96 let provider = solana::SolanaChainProvider::from_config(config).await?;
97 ChainProvider::Solana(Arc::new(provider))
98 }
99 };
100 Ok(provider)
101 }
102}
103
104/// Common operations available on all chain providers.
105///
106/// This trait provides a unified interface for querying chain provider metadata
107/// regardless of the underlying blockchain type.
108pub trait ChainProviderOps {
109 /// Returns the addresses of all configured signers for this chain.
110 ///
111 /// For EVM chains, these are Ethereum addresses (0x-prefixed hex).
112 /// For Solana, these are base58-encoded public keys.
113 fn signer_addresses(&self) -> Vec<String>;
114
115 /// Returns the CAIP-2 chain identifier for this provider.
116 fn chain_id(&self) -> ChainId;
117}
118
119impl<T: ChainProviderOps> ChainProviderOps for Arc<T> {
120 fn signer_addresses(&self) -> Vec<String> {
121 (**self).signer_addresses()
122 }
123 fn chain_id(&self) -> ChainId {
124 (**self).chain_id()
125 }
126}
127
128impl ChainProviderOps for ChainProvider {
129 fn signer_addresses(&self) -> Vec<String> {
130 match self {
131 ChainProvider::Eip155(provider) => provider.signer_addresses(),
132 ChainProvider::Solana(provider) => provider.signer_addresses(),
133 }
134 }
135
136 fn chain_id(&self) -> ChainId {
137 match self {
138 ChainProvider::Eip155(provider) => provider.chain_id(),
139 ChainProvider::Solana(provider) => provider.chain_id(),
140 }
141 }
142}
143
144/// Registry of configured chain providers indexed by chain ID.
145///
146/// The registry is built from configuration and provides lookup methods
147/// for finding providers by exact chain ID or by pattern matching.
148///
149/// # Type Parameters
150///
151/// - `P` - The chain provider type (e.g., [`ChainProvider`] or a custom provider type)
152///
153/// # Example
154///
155/// ```ignore
156/// use x402_rs::chain::{ChainRegistry, ChainIdPattern, ChainProvider};
157/// use x402_rs::config::Config;
158///
159/// let config = Config::load()?;
160/// let registry = ChainRegistry::from_config(config.chains()).await?;
161///
162/// // Find provider for a specific chain
163/// let base_provider = registry.by_chain_id(ChainId::new("eip155", "8453"));
164///
165/// // Find provider matching a pattern
166/// let any_evm = registry.by_chain_id_pattern(&ChainIdPattern::wildcard("eip155"));
167/// ```
168#[derive(Debug)]
169pub struct ChainRegistry<P>(HashMap<ChainId, P>);
170
171impl<P> ChainRegistry<P> {
172 pub fn new(providers: HashMap<ChainId, P>) -> Self {
173 Self(providers)
174 }
175}
176
177/// Creates a new chain registry from configuration.
178///
179/// Initializes providers for all configured chains. Each chain configuration
180/// is processed and a corresponding provider is created and stored.
181///
182/// # Errors
183///
184/// Returns an error if any chain provider fails to initialize.
185#[async_trait::async_trait]
186impl FromConfig<ChainsConfig> for ChainRegistry<ChainProvider> {
187 async fn from_config(chains: &ChainsConfig) -> Result<Self, Box<dyn std::error::Error>> {
188 let mut providers = HashMap::new();
189 for chain in chains.iter() {
190 let chain_provider = ChainProvider::from_config(chain).await?;
191 providers.insert(chain_provider.chain_id(), chain_provider);
192 }
193 Ok(Self::new(providers))
194 }
195}
196
197impl<P> ChainRegistry<P> {
198 /// Looks up a provider by exact chain ID.
199 ///
200 /// Returns `None` if no provider is configured for the given chain.
201 #[allow(dead_code)]
202 pub fn by_chain_id(&self, chain_id: ChainId) -> Option<&P> {
203 self.0.get(&chain_id)
204 }
205
206 /// Looks up providers by chain ID pattern matching.
207 ///
208 /// Returns all providers whose chain IDs match the given pattern.
209 /// The pattern can be:
210 /// - Wildcard: Matches any chain within a namespace (e.g., `eip155:*`)
211 /// - Exact: Matches a specific chain (e.g., `eip155:8453`)
212 /// - Set: Matches any chain from a set of references (e.g., `eip155:{1,8453,137}`)
213 ///
214 /// # Example
215 ///
216 /// ```ignore
217 /// use x402_rs::chain::{ChainRegistry, ChainIdPattern};
218 /// use x402_rs::config::Config;
219 ///
220 /// let config = Config::load()?;
221 /// let registry = ChainRegistry::from_config(config.chains()).await?;
222 ///
223 /// // Find all EVM chain providers
224 /// let evm_providers = registry.by_chain_id_pattern(&ChainIdPattern::wildcard("eip155"));
225 /// assert!(!evm_providers.is_empty());
226 ///
227 /// // Find providers for specific chains
228 /// let mainnet_chains = ChainIdPattern::set("eip155", ["1", "8453", "137"].into_iter().map(String::from).collect());
229 /// let mainnet_providers = registry.by_chain_id_pattern(&mainnet_chains);
230 /// ```
231 pub fn by_chain_id_pattern(&self, pattern: &ChainIdPattern) -> Vec<&P> {
232 self.0
233 .iter()
234 .filter_map(|(chain_id, provider)| pattern.matches(chain_id).then_some(provider))
235 .collect()
236 }
237}
238
239/// A token amount paired with its deployment information.
240///
241/// This type associates a numeric amount with the token deployment it refers to,
242/// enabling type-safe handling of token amounts across different chains and tokens.
243///
244/// # Type Parameters
245///
246/// - `TAmount` - The numeric type for the amount (e.g., `U256` for EVM, `u64` for Solana)
247/// - `TToken` - The token deployment type containing chain and address information
248#[derive(Debug, Clone)]
249#[allow(dead_code)] // Public for consumption by downstream crates.
250pub struct DeployedTokenAmount<TAmount, TToken> {
251 /// The token amount in the token's smallest unit (e.g., wei for ETH, lamports for SOL).
252 pub amount: TAmount,
253 /// The token deployment information including chain, address, and decimals.
254 pub token: TToken,
255}