use alloy_primitives::U256;
use async_trait::async_trait;
use crate::chain::{ChainId, ChainIdPattern};
use crate::proto;
use crate::scheme::X402SchemeId;
#[allow(dead_code)] pub struct PaymentCandidate {
pub chain_id: ChainId,
pub asset: String,
pub amount: U256,
pub scheme: String,
pub x402_version: u8,
pub pay_to: String,
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 {
#[allow(dead_code)] pub async fn sign(&self) -> Result<String, X402Error> {
self.signer.sign_payment().await
}
}
#[async_trait]
#[allow(dead_code)] pub trait X402SchemeClient: X402SchemeId + Send + Sync {
fn accept(&self, payment_required: &proto::PaymentRequired) -> Vec<PaymentCandidate>;
}
#[async_trait]
#[allow(dead_code)] pub trait PaymentCandidateSigner {
async fn sign_payment(&self) -> Result<String, X402Error>;
}
#[derive(Debug, thiserror::Error)]
#[allow(dead_code)] pub enum X402Error {
#[error("No matching payment option found")]
NoMatchingPaymentOption,
#[error("Request is not cloneable (streaming body?)")]
RequestNotCloneable,
#[error("Failed to parse 402 response: {0}")]
ParseError(String),
#[error("Failed to sign payment: {0}")]
SigningError(String),
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error),
}
#[allow(dead_code)] pub trait PaymentSelector: Send + Sync {
fn select<'a>(&self, candidates: &'a [PaymentCandidate]) -> Option<&'a PaymentCandidate>;
}
#[allow(dead_code)] pub struct FirstMatch;
impl PaymentSelector for FirstMatch {
fn select<'a>(&self, candidates: &'a [PaymentCandidate]) -> Option<&'a PaymentCandidate> {
candidates.first()
}
}
#[allow(dead_code)] pub struct PreferChain(Vec<ChainIdPattern>);
#[allow(dead_code)] impl PreferChain {
pub fn new<P: Into<Vec<ChainIdPattern>>>(patterns: P) -> Self {
Self(patterns.into())
}
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> {
for pattern in &self.0 {
if let Some(candidate) = candidates.iter().find(|c| pattern.matches(&c.chain_id)) {
return Some(candidate);
}
}
candidates.first()
}
}
#[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)
}
}