x402-types 1.4.6

Core types for x402 payments: chain identifiers, protocol messages, and facilitator traits
Documentation
//! Client-side payment scheme support.
//!
//! This module provides types for client applications (buyers) to select and
//! sign payments in response to 402 Payment Required responses.
//!
//! # Payment Flow
//!
//! 1. Client receives a 402 response with payment requirements
//! 2. [`X402SchemeClient`] implementations generate [`PaymentCandidate`]s
//! 3. A [`PaymentSelector`] chooses the best candidate
//! 4. The candidate is signed and sent back to the server
//!
//! # Payment Selection
//!
//! Multiple selectors are available:
//! - [`FirstMatch`] - Takes the first available option
//! - [`PreferChain`] - Prefers specific chains in priority order
//! - [`MaxAmount`] - Only accepts payments up to a maximum amount

use alloy_primitives::U256;
use async_trait::async_trait;

use crate::chain::{ChainId, ChainIdPattern};
use crate::proto;
use crate::scheme::X402SchemeId;

/// A payment option that can be signed and submitted.
///
/// Payment candidates are generated by scheme clients when they find
/// a matching payment requirement they can fulfill.
#[allow(dead_code)] // Public for consumption by downstream crates.
pub struct PaymentCandidate {
    /// The chain where payment will be made.
    pub chain_id: ChainId,
    /// The token asset address.
    pub asset: String,
    /// The payment amount in token units.
    pub amount: U256,
    /// The payment scheme name.
    pub scheme: String,
    /// The x402 protocol version.
    pub x402_version: u8,
    /// The recipient address.
    pub pay_to: String,
    /// The signer that can authorize this payment.
    pub signer: Box<dyn PaymentCandidateSigner + Send + Sync>,
}

impl std::fmt::Debug for PaymentCandidate {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("PaymentCandidate")
            .field("chain_id", &self.chain_id)
            .field("asset", &self.asset)
            .field("amount", &self.amount)
            .field("scheme", &self.scheme)
            .field("x402_version", &self.x402_version)
            .field("pay_to", &self.pay_to)
            .field("signer", &"<dyn PaymentCandidateSigner>")
            .finish()
    }
}

impl PaymentCandidate {
    /// Signs this payment candidate, producing a payment payload.
    #[allow(dead_code)] // Public for consumption by downstream crates.
    pub async fn sign(&self) -> Result<String, X402Error> {
        self.signer.sign_payment().await
    }
}

/// Trait for scheme clients that can process payment requirements.
///
/// Implementations examine 402 responses and generate payment candidates
/// for requirements they can fulfill.
#[async_trait]
#[allow(dead_code)] // Public for consumption by downstream crates.
pub trait X402SchemeClient: X402SchemeId + Send + Sync {
    /// Generates payment candidates for the given payment requirements.
    fn accept(&self, payment_required: &proto::PaymentRequired) -> Vec<PaymentCandidate>;
}

/// Trait for signing payment authorizations.
#[async_trait]
#[allow(dead_code)] // Public for consumption by downstream crates.
pub trait PaymentCandidateSigner {
    /// Signs a payment authorization.
    async fn sign_payment(&self) -> Result<String, X402Error>;
}

/// Errors that can occur during client-side payment processing.
#[derive(Debug, thiserror::Error)]
#[allow(dead_code)] // Public for consumption by downstream crates.
pub enum X402Error {
    /// No payment option matched the client's capabilities.
    #[error("No matching payment option found")]
    NoMatchingPaymentOption,

    /// The HTTP request body cannot be cloned (e.g., streaming).
    #[error("Request is not cloneable (streaming body?)")]
    RequestNotCloneable,

    /// Failed to parse the 402 response body.
    #[error("Failed to parse 402 response: {0}")]
    ParseError(String),

    /// Failed to sign the payment authorization.
    #[error("Failed to sign payment: {0}")]
    SigningError(String),

    /// JSON serialization/deserialization error.
    #[error("JSON error: {0}")]
    JsonError(#[from] serde_json::Error),
}

// ============================================================================
// PaymentSelector - Selection strategy
// ============================================================================

/// Trait for selecting the best payment candidate from available options.
///
/// Implement this trait to customize how payments are selected when
/// multiple options are available.
#[allow(dead_code)] // Public for consumption by downstream crates.
pub trait PaymentSelector: Send + Sync {
    /// Selects a payment candidate from the available options.
    fn select<'a>(&self, candidates: &'a [PaymentCandidate]) -> Option<&'a PaymentCandidate>;
}

/// Selector that returns the first matching candidate.
///
/// This is the simplest selection strategy. The order of candidates
/// is determined by the registration order of scheme clients.
#[allow(dead_code)] // Public for consumption by downstream crates.
pub struct FirstMatch;

impl PaymentSelector for FirstMatch {
    fn select<'a>(&self, candidates: &'a [PaymentCandidate]) -> Option<&'a PaymentCandidate> {
        candidates.first()
    }
}

/// Selector that prefers specific chains in priority order.
///
/// Patterns are tried in order; the first matching candidate is returned.
/// If no patterns match, falls back to the first available candidate.
///
/// # Example
///
/// ```rust
/// use x402_types::scheme::client::PreferChain;
/// use x402_types::chain::ChainIdPattern;
///
/// // Prefer Base, then any EVM chain, then anything else
/// let selector = PreferChain::new(vec![
///     "eip155:8453".parse().unwrap(),  // Base mainnet
///     "eip155:*".parse().unwrap(),     // Any EVM chain
/// ]);
/// ```
#[allow(dead_code)] // Public for consumption by downstream crates.
pub struct PreferChain(Vec<ChainIdPattern>);

#[allow(dead_code)] // Public for consumption by downstream crates.
impl PreferChain {
    /// Creates a new chain preference selector.
    pub fn new<P: Into<Vec<ChainIdPattern>>>(patterns: P) -> Self {
        Self(patterns.into())
    }

    /// Adds additional chain patterns with lower priority.
    pub fn or_chain<P: Into<Vec<ChainIdPattern>>>(self, patterns: P) -> PreferChain {
        PreferChain(self.0.into_iter().chain(patterns.into()).collect())
    }
}

impl PaymentSelector for PreferChain {
    fn select<'a>(&self, candidates: &'a [PaymentCandidate]) -> Option<&'a PaymentCandidate> {
        // Try each pattern in priority order
        for pattern in &self.0 {
            if let Some(candidate) = candidates.iter().find(|c| pattern.matches(&c.chain_id)) {
                return Some(candidate);
            }
        }
        // Fall back to first match if no patterns matched
        candidates.first()
    }
}

/// Selector that only accepts payments up to a maximum amount.
///
/// Useful for limiting spending or implementing budget controls.
#[allow(dead_code)]
pub struct MaxAmount(pub U256);

impl PaymentSelector for MaxAmount {
    fn select<'a>(&self, candidates: &'a [PaymentCandidate]) -> Option<&'a PaymentCandidate> {
        candidates.iter().find(|c| c.amount <= self.0)
    }
}