x402_types/scheme/mod.rs
1//! Payment scheme implementations for x402.
2//!
3//! This module provides the extensible scheme system that allows different
4//! payment methods to be plugged into the x402 protocol. Each scheme defines
5//! how payments are authorized, verified, and settled.
6//!
7//! # Architecture
8//!
9//! The scheme system has three main components:
10//!
11//! 1. **Blueprints** ([`SchemeBlueprints`]) - Factories that create scheme handlers
12//! 2. **Handlers** ([`X402SchemeFacilitator`]) - Process verify/settle requests
13//! 3. **Registry** ([`SchemeRegistry`]) - Maps chain+scheme combinations to handlers
14//!
15//! # Available Schemes
16//!
17//! Scheme implementations are provided by chain-specific crates:
18//!
19//! - **EVM chains** (`x402-chain-eip155`): `v1-eip155-exact`, `v2-eip155-exact`
20//! - **Solana** (`x402-chain-solana`): `v1-solana-exact`, `v2-solana-exact`
21//! - **Aptos** (`x402-chain-aptos`): `v2-aptos-exact`
22//!
23//! # Implementing a Custom Scheme
24//!
25//! To implement a custom scheme:
26//!
27//! 1. Implement [`X402SchemeId`] to identify your scheme
28//! 2. Implement [`X402SchemeFacilitatorBuilder`] to create handlers
29//! 3. Implement [`X402SchemeFacilitator`] for the actual verification/settlement logic
30//! 4. Register your scheme with [`SchemeBlueprints::register`]
31//!
32//! See the `docs/how-to-write-a-scheme.md` guide in the repository for details.
33
34pub mod client;
35
36use crate::chain::{ChainId, ChainIdPattern, ChainProviderOps, ChainRegistry};
37use crate::proto;
38use crate::proto::{AsPaymentProblem, ErrorReason, PaymentProblem, PaymentVerificationError};
39use serde::{Deserialize, Serialize};
40use std::collections::HashMap;
41use std::fmt::{Debug, Display, Formatter};
42use std::marker::PhantomData;
43use std::ops::Deref;
44
45/// Trait for scheme handlers that process payment verification and settlement.
46///
47/// Implementations of this trait handle the core payment processing logic:
48/// verifying that payments are valid and settling them on-chain.
49#[async_trait::async_trait]
50pub trait X402SchemeFacilitator: Send + Sync {
51 /// Verifies a payment authorization without settling it.
52 ///
53 /// This checks that the payment is properly signed, matches the requirements,
54 /// and the payer has sufficient funds.
55 async fn verify(
56 &self,
57 request: &proto::VerifyRequest,
58 ) -> Result<proto::VerifyResponse, X402SchemeFacilitatorError>;
59
60 /// Settles a verified payment on-chain.
61 ///
62 /// This submits the payment transaction to the blockchain and waits
63 /// for confirmation.
64 async fn settle(
65 &self,
66 request: &proto::SettleRequest,
67 ) -> Result<proto::SettleResponse, X402SchemeFacilitatorError>;
68
69 /// Returns the payment methods supported by this handler.
70 async fn supported(&self) -> Result<proto::SupportedResponse, X402SchemeFacilitatorError>;
71}
72
73/// Marker trait for types that are both identifiable and buildable.
74///
75/// This combines [`X402SchemeId`] and [`X402SchemeFacilitatorBuilder`] for
76/// use in the blueprint registry.
77pub trait X402SchemeBlueprint<P>:
78 X402SchemeId + for<'a> X402SchemeFacilitatorBuilder<&'a P>
79{
80}
81impl<T, P> X402SchemeBlueprint<P> for T where
82 T: X402SchemeId + for<'a> X402SchemeFacilitatorBuilder<&'a P>
83{
84}
85
86/// Trait for identifying a payment scheme.
87///
88/// Each scheme has a unique identifier composed of the protocol version,
89/// chain namespace, and scheme name.
90pub trait X402SchemeId {
91 /// Returns the x402 protocol version (1 or 2).
92 fn x402_version(&self) -> u8 {
93 2
94 }
95 /// Returns the chain namespace (e.g., "eip155", "solana").
96 fn namespace(&self) -> &str;
97 /// Returns the scheme name (e.g., "exact").
98 fn scheme(&self) -> &str;
99 /// Returns the full scheme identifier (e.g., "v2-eip155-exact").
100 fn id(&self) -> String {
101 format!(
102 "v{}-{}-{}",
103 self.x402_version(),
104 self.namespace(),
105 self.scheme(),
106 )
107 }
108}
109
110/// Trait for building scheme handlers from chain providers.
111///
112/// The type parameter `P` represents the chain provider type.
113/// Implementations use ['FromChainProvider'] trait to get the specific provider they need.
114pub trait X402SchemeFacilitatorBuilder<P> {
115 /// Creates a new scheme handler for the given chain provider.
116 ///
117 /// # Arguments
118 ///
119 /// * `provider` - The chain provider to use for on-chain operations
120 /// * `config` - Optional scheme-specific configuration
121 fn build(
122 &self,
123 provider: P,
124 config: Option<serde_json::Value>,
125 ) -> Result<Box<dyn X402SchemeFacilitator>, Box<dyn std::error::Error>>;
126}
127
128/// Errors that can occur during scheme operations.
129#[derive(Debug, thiserror::Error)]
130pub enum X402SchemeFacilitatorError {
131 /// Payment verification failed.
132 #[error(transparent)]
133 PaymentVerification(#[from] PaymentVerificationError),
134 /// On-chain operation failed.
135 #[error("Onchain error: {0}")]
136 OnchainFailure(String),
137}
138
139impl AsPaymentProblem for X402SchemeFacilitatorError {
140 fn as_payment_problem(&self) -> PaymentProblem {
141 match self {
142 X402SchemeFacilitatorError::PaymentVerification(e) => e.as_payment_problem(),
143 X402SchemeFacilitatorError::OnchainFailure(e) => {
144 PaymentProblem::new(ErrorReason::UnexpectedError, e.to_string())
145 }
146 }
147 }
148}
149
150/// Registry of scheme blueprints (factories).
151///
152/// Blueprints are used to create scheme handlers for specific chain providers.
153/// Register blueprints at startup, then use them to build handlers.
154///
155/// # Type Parameters
156///
157/// - `P` - The chain provider type that blueprints can extract from using [`FromChainProvider`]
158#[derive(Default)]
159pub struct SchemeBlueprints<P>(
160 HashMap<String, Box<dyn X402SchemeBlueprint<P>>>,
161 PhantomData<P>,
162);
163
164impl<P> Debug for SchemeBlueprints<P> {
165 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
166 let slugs: Vec<String> = self.0.keys().map(|s| s.to_string()).collect();
167 f.debug_tuple("SchemeBlueprints").field(&slugs).finish()
168 }
169}
170
171impl<P> SchemeBlueprints<P> {
172 /// Creates an empty blueprint registry.
173 pub fn new() -> Self {
174 Self(HashMap::new(), PhantomData)
175 }
176
177 /// Registers a blueprint and returns self for chaining.
178 pub fn and_register<B: X402SchemeBlueprint<P> + 'static>(mut self, blueprint: B) -> Self {
179 self.register(blueprint);
180 self
181 }
182
183 /// Registers a scheme blueprint.
184 pub fn register<B: X402SchemeBlueprint<P> + 'static>(&mut self, blueprint: B) {
185 self.0.insert(blueprint.id(), Box::new(blueprint));
186 }
187
188 /// Gets a blueprint by its ID.
189 pub fn get(&self, id: &str) -> Option<&dyn X402SchemeBlueprint<P>> {
190 self.0.get(id).map(|v| v.deref())
191 }
192}
193
194/// Unique identifier for a scheme handler instance.
195///
196/// Combines the chain ID, protocol version, and scheme name to uniquely
197/// identify a handler that can process payments for a specific combination.
198#[derive(Debug, PartialEq, Eq, Hash, Clone)]
199pub struct SchemeHandlerSlug {
200 /// The chain this handler operates on.
201 pub chain_id: ChainId,
202 /// The x402 protocol version.
203 pub x402_version: u8,
204 /// The scheme name (e.g., "exact").
205 pub name: String,
206}
207
208impl SchemeHandlerSlug {
209 /// Creates a new scheme handler slug.
210 pub fn new(chain_id: ChainId, x402_version: u8, name: String) -> Self {
211 Self {
212 chain_id,
213 x402_version,
214 name,
215 }
216 }
217}
218
219impl Display for SchemeHandlerSlug {
220 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
221 write!(
222 f,
223 "{}:{}:v{}:{}",
224 self.chain_id.namespace, self.chain_id.reference, self.x402_version, self.name
225 )
226 }
227}
228
229/// Registry of active scheme handlers.
230///
231/// Maps chain+scheme combinations to their handlers. Built from blueprints
232/// and chain providers based on configuration.
233#[derive(Default)]
234pub struct SchemeRegistry(HashMap<SchemeHandlerSlug, Box<dyn X402SchemeFacilitator>>);
235
236impl Debug for SchemeRegistry {
237 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
238 let slugs: Vec<String> = self.0.keys().map(|s| s.to_string()).collect();
239 f.debug_tuple("SchemeRegistry").field(&slugs).finish()
240 }
241}
242
243impl SchemeRegistry {
244 /// Builds a scheme registry from blueprints and configuration.
245 ///
246 /// For each enabled scheme in the config, this finds the matching blueprint
247 /// and chain provider, then builds a handler.
248 pub fn build<P: ChainProviderOps>(
249 chains: ChainRegistry<P>,
250 blueprints: SchemeBlueprints<P>,
251 config: &Vec<SchemeConfig>,
252 ) -> Self {
253 let mut handlers = HashMap::with_capacity(config.len());
254 for config in config {
255 if !config.enabled {
256 #[cfg(feature = "telemetry")]
257 tracing::info!(
258 "Skipping disabled scheme {} for chains {}",
259 config.id,
260 config.chains
261 );
262 continue;
263 }
264 let blueprint = match blueprints.get(&config.id) {
265 Some(blueprint) => blueprint,
266 None => {
267 #[cfg(feature = "telemetry")]
268 tracing::warn!("No scheme registered: {}", config.id);
269 continue;
270 }
271 };
272 let chain_providers = chains.by_chain_id_pattern(&config.chains);
273 if chain_providers.is_empty() {
274 #[cfg(feature = "telemetry")]
275 tracing::warn!("No chain provider found for {}", config.chains);
276 continue;
277 }
278
279 for chain_provider in chain_providers {
280 let chain_id = chain_provider.chain_id();
281 let handler = match blueprint.build(chain_provider, config.config.clone()) {
282 Ok(handler) => handler,
283 Err(_err) => {
284 #[cfg(feature = "telemetry")]
285 tracing::error!(
286 "Error building scheme handler for {}: {}",
287 config.id,
288 _err
289 );
290 continue;
291 }
292 };
293 let slug = SchemeHandlerSlug::new(
294 chain_id.clone(),
295 blueprint.x402_version(),
296 blueprint.scheme().to_string(),
297 );
298 #[cfg(feature = "telemetry")]
299 tracing::info!(chain_id = %chain_id, scheme = %blueprint.scheme(), id=blueprint.id(), "Registered scheme handler");
300 handlers.insert(slug, handler);
301 }
302 }
303 Self(handlers)
304 }
305
306 /// Gets a handler by its slug.
307 pub fn by_slug(&self, slug: &SchemeHandlerSlug) -> Option<&dyn X402SchemeFacilitator> {
308 let handler = self.0.get(slug)?.deref();
309 Some(handler)
310 }
311
312 /// Returns an iterator over all registered handlers.
313 pub fn values(&self) -> impl Iterator<Item = &dyn X402SchemeFacilitator> {
314 self.0.values().map(|v| v.deref())
315 }
316}
317
318/// Configuration for a specific scheme.
319///
320/// Each scheme entry specifies which scheme to use and which chains it applies to.
321#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct SchemeConfig {
323 /// Whether this scheme is enabled (defaults to true).
324 #[serde(default = "scheme_config_defaults::default_enabled")]
325 pub enabled: bool,
326 /// The scheme id (e.g., "v1-eip155-exact").
327 pub id: String,
328 /// The chain pattern this scheme applies to (e.g., "eip155:84532", "eip155:*", "eip155:{1,8453}").
329 pub chains: ChainIdPattern,
330 /// Scheme-specific configuration (optional).
331 #[serde(default, skip_serializing_if = "Option::is_none")]
332 pub config: Option<serde_json::Value>,
333}
334
335mod scheme_config_defaults {
336 pub fn default_enabled() -> bool {
337 true
338 }
339}