Skip to main content

x402_types/proto/
mod.rs

1//! Protocol types for x402 payment messages.
2//!
3//! This module defines the wire format types used in the x402 protocol for
4//! communication between buyers, sellers, and facilitators. It supports both
5//! protocol version 1 (V1) and version 2 (V2).
6//!
7//! # Protocol Versions
8//!
9//! - **V1** ([`v1`]): Original protocol with network names and simpler structure
10//! - **V2** ([`v2`]): Enhanced protocol with CAIP-2 chain IDs and richer metadata
11//!
12//! # Key Types
13//!
14//! - [`SupportedPaymentKind`] - Describes a payment method supported by a facilitator
15//! - [`SupportedResponse`] - Response from facilitator's `/supported` endpoint
16//! - [`VerifyRequest`] / [`VerifyResponse`] - Payment verification messages
17//! - [`SettleRequest`] / [`SettleResponse`] - Payment settlement messages
18//! - [`PaymentVerificationError`] - Errors that can occur during verification
19//! - [`PaymentProblem`] - Structured error response for payment failures
20//!
21//! # Wire Format
22//!
23//! All types serialize to JSON using camelCase field names. The protocol version
24//! is indicated by the `x402Version` field in payment payloads.
25
26use serde::{Deserialize, Serialize};
27use serde_with::{VecSkipError, serde_as};
28use std::collections::HashMap;
29
30use crate::chain::ChainId;
31use crate::scheme::SchemeHandlerSlug;
32
33pub mod util;
34pub mod v1;
35pub mod v2;
36
37/// Trait for types that have both V1 and V2 protocol variants.
38///
39/// This trait enables generic handling of protocol-versioned types through
40/// the [`ProtocolVersioned`] enum.
41pub trait ProtocolV {
42    /// The V1 protocol variant of this type.
43    type V1;
44    /// The V2 protocol variant of this type.
45    type V2;
46}
47
48/// A versioned protocol type that can be either V1 or V2.
49///
50/// This enum wraps protocol-specific types to allow handling both versions
51/// in a unified way.
52pub enum ProtocolVersioned<T>
53where
54    T: ProtocolV,
55{
56    /// Protocol version 1 variant.
57    #[allow(dead_code)]
58    V1(T::V1),
59    /// Protocol version 2 variant.
60    #[allow(dead_code)]
61    V2(T::V2),
62}
63
64/// Describes a payment method supported by a facilitator.
65///
66/// This type is returned in the [`SupportedResponse`] to indicate what
67/// payment schemes, networks, and protocol versions a facilitator can handle.
68///
69/// # Example
70///
71/// ```json
72/// {
73///   "x402Version": 2,
74///   "scheme": "exact",
75///   "network": "eip155:8453"
76/// }
77/// ```
78#[derive(Clone, Debug, Serialize, Deserialize)]
79#[serde(rename_all = "camelCase")]
80pub struct SupportedPaymentKind {
81    /// The x402 protocol version (1 or 2).
82    pub x402_version: u8,
83    /// The payment scheme identifier (e.g., "exact").
84    pub scheme: String,
85    /// The network identifier (CAIP-2 chain ID for V2, network name for V1).
86    pub network: String,
87    /// Optional scheme-specific extra data.
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub extra: Option<serde_json::Value>,
90}
91
92/// Response from a facilitator's `/supported` endpoint.
93///
94/// This response tells clients what payment methods the facilitator supports,
95/// including protocol versions, schemes, networks, and signer addresses.
96///
97/// # Example
98///
99/// ```json
100/// {
101///   "kinds": [
102///     { "x402Version": 2, "scheme": "exact", "network": "eip155:8453" }
103///   ],
104///   "extensions": [],
105///   "signers": {
106///     "eip155:8453": ["0x1234..."]
107///   }
108/// }
109/// ```
110#[serde_as]
111#[derive(Clone, Default, Debug, Serialize, Deserialize)]
112#[serde(rename_all = "camelCase")]
113#[allow(dead_code)] // Public for consumption by downstream crates.
114pub struct SupportedResponse {
115    /// List of supported payment kinds.
116    #[serde_as(as = "VecSkipError<_>")]
117    pub kinds: Vec<SupportedPaymentKind>,
118    /// List of supported protocol extensions.
119    #[serde(default)]
120    pub extensions: Vec<String>,
121    /// Map of chain IDs to signer addresses for that chain.
122    #[serde(default)]
123    pub signers: HashMap<ChainId, Vec<String>>,
124}
125
126/// Request to verify a payment before settlement.
127///
128/// This wrapper contains the payment payload and requirements sent by a client
129/// to a facilitator for verification. The facilitator checks that the payment
130/// authorization is valid, properly signed, and matches the requirements.
131///
132/// The inner JSON structure varies by protocol version and scheme.
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct VerifyRequest(Box<serde_json::value::RawValue>);
135
136/// Request to settle a verified payment on-chain.
137///
138/// This is the same structure as [`VerifyRequest`], containing the payment
139/// payload that was previously verified.
140pub type SettleRequest = VerifyRequest;
141
142impl From<Box<serde_json::value::RawValue>> for VerifyRequest {
143    fn from(value: Box<serde_json::value::RawValue>) -> Self {
144        Self(value)
145    }
146}
147
148impl VerifyRequest {
149    pub fn as_str(&self) -> &str {
150        self.0.get()
151    }
152
153    /// Extracts the scheme handler slug from the request.
154    ///
155    /// This determines which scheme handler should process this payment
156    /// based on the protocol version, chain ID, and scheme name.
157    ///
158    /// Returns `None` if the request format is invalid or the scheme is unknown.
159    pub fn scheme_handler_slug(&self) -> Option<SchemeHandlerSlug> {
160        #[derive(Debug, Deserialize, Serialize)]
161        #[serde(untagged)]
162        enum VerifyRequestWire {
163            #[serde(rename_all = "camelCase")]
164            V1 {
165                x402_version: v1::X402Version1,
166                payment_payload: PaymentPayloadV1,
167            },
168            #[serde(rename_all = "camelCase")]
169            V2 {
170                x402_version: v2::X402Version2,
171                payment_payload: PaymentPayloadV2,
172            },
173        }
174
175        #[derive(Debug, Deserialize, Serialize)]
176        #[serde(rename_all = "camelCase")]
177        struct PaymentPayloadV1 {
178            pub network: String,
179            pub scheme: String,
180        }
181
182        #[derive(Debug, Deserialize, Serialize)]
183        #[serde(rename_all = "camelCase")]
184        struct PaymentPayloadV2 {
185            pub accepted: PaymentPayloadV2Accepted,
186        }
187
188        #[derive(Debug, Deserialize, Serialize)]
189        #[serde(rename_all = "camelCase")]
190        struct PaymentPayloadV2Accepted {
191            pub network: ChainId,
192            pub scheme: String,
193        }
194
195        let wire = serde_json::from_str::<VerifyRequestWire>(self.as_str()).ok()?;
196        match wire {
197            VerifyRequestWire::V1 {
198                payment_payload,
199                x402_version,
200            } => {
201                let network_name = payment_payload.network;
202                let chain_id = ChainId::from_network_name(&network_name)?;
203                let scheme = payment_payload.scheme;
204                let slug = SchemeHandlerSlug::new(chain_id, x402_version.into(), scheme);
205                Some(slug)
206            }
207            VerifyRequestWire::V2 {
208                payment_payload,
209                x402_version,
210            } => {
211                let chain_id = payment_payload.accepted.network;
212                let scheme = payment_payload.accepted.scheme;
213                let slug = SchemeHandlerSlug::new(chain_id, x402_version.into(), scheme);
214                Some(slug)
215            }
216        }
217    }
218}
219
220/// Response from a payment verification request.
221///
222/// Contains the verification result as JSON. The structure varies by
223/// protocol version and scheme.
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct VerifyResponse(pub serde_json::Value);
226
227/// Response from a payment settlement request.
228///
229/// Contains the settlement result as JSON, typically including the
230/// transaction hash if settlement was successful.
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct SettleResponse(pub serde_json::Value);
233
234/// Errors that can occur during payment verification.
235///
236/// These errors are returned when a payment fails validation checks
237/// performed by the facilitator before settlement.
238#[derive(Debug, thiserror::Error)]
239pub enum PaymentVerificationError {
240    /// The payment payload format is invalid or malformed.
241    #[error("Invalid format: {0}")]
242    InvalidFormat(String),
243    /// The payment amount doesn't match the requirements.
244    #[error("Payment amount is invalid with respect to the payment requirements")]
245    InvalidPaymentAmount,
246    /// The payment authorization's `validAfter` timestamp is in the future.
247    #[error("Payment authorization is not yet valid")]
248    Early,
249    /// The payment authorization's `validBefore` timestamp has passed.
250    #[error("Payment authorization is expired")]
251    Expired,
252    /// The payment's chain ID doesn't match the requirements.
253    #[error("Payment chain id is invalid with respect to the payment requirements")]
254    ChainIdMismatch,
255    /// The payment recipient doesn't match the requirements.
256    #[error("Payment recipient is invalid with respect to the payment requirements")]
257    RecipientMismatch,
258    /// The payment asset (token) doesn't match the requirements.
259    #[error("Payment asset is invalid with respect to the payment requirements")]
260    AssetMismatch,
261    /// The payer's on-chain balance is insufficient.
262    #[error("Onchain balance is not enough to cover the payment amount")]
263    InsufficientFunds,
264    #[error("Allowance is not enough to cover the payment amount")]
265    InsufficientAllowance,
266    /// The payment signature is invalid.
267    #[error("{0}")]
268    InvalidSignature(String),
269    /// Transaction simulation failed.
270    #[error("{0}")]
271    TransactionSimulation(String),
272    /// The chain is not supported by this facilitator.
273    #[error("Unsupported chain")]
274    UnsupportedChain,
275    /// The payment scheme is not supported by this facilitator.
276    #[error("Unsupported scheme")]
277    UnsupportedScheme,
278    /// The accepted payment details don't match the requirements.
279    #[error("Accepted does not match payment requirements")]
280    AcceptedRequirementsMismatch,
281}
282
283impl AsPaymentProblem for PaymentVerificationError {
284    fn as_payment_problem(&self) -> PaymentProblem {
285        let error_reason = match self {
286            PaymentVerificationError::InvalidFormat(_) => ErrorReason::InvalidFormat,
287            PaymentVerificationError::InvalidPaymentAmount => ErrorReason::InvalidPaymentAmount,
288            PaymentVerificationError::InsufficientFunds => ErrorReason::InsufficientFunds,
289            PaymentVerificationError::InsufficientAllowance => {
290                ErrorReason::Permit2AllowanceRequired
291            }
292            PaymentVerificationError::Early => ErrorReason::InvalidPaymentEarly,
293            PaymentVerificationError::Expired => ErrorReason::InvalidPaymentExpired,
294            PaymentVerificationError::ChainIdMismatch => ErrorReason::ChainIdMismatch,
295            PaymentVerificationError::RecipientMismatch => ErrorReason::RecipientMismatch,
296            PaymentVerificationError::AssetMismatch => ErrorReason::AssetMismatch,
297            PaymentVerificationError::InvalidSignature(_) => ErrorReason::InvalidSignature,
298            PaymentVerificationError::TransactionSimulation(_) => {
299                ErrorReason::TransactionSimulation
300            }
301            PaymentVerificationError::UnsupportedChain => ErrorReason::UnsupportedChain,
302            PaymentVerificationError::UnsupportedScheme => ErrorReason::UnsupportedScheme,
303            PaymentVerificationError::AcceptedRequirementsMismatch => {
304                ErrorReason::AcceptedRequirementsMismatch
305            }
306        };
307        PaymentProblem::new(error_reason, self.to_string())
308    }
309}
310
311impl From<serde_json::Error> for PaymentVerificationError {
312    fn from(value: serde_json::Error) -> Self {
313        Self::InvalidFormat(value.to_string())
314    }
315}
316
317/// Machine-readable error reason codes for payment failures.
318///
319/// These codes are used in error responses to allow clients to
320/// programmatically handle different failure scenarios.
321#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
322#[serde(rename_all = "snake_case")]
323pub enum ErrorReason {
324    /// The payment payload format is invalid.
325    InvalidFormat,
326    /// The payment amount is incorrect.
327    InvalidPaymentAmount,
328    /// The payment authorization is not yet valid.
329    InvalidPaymentEarly,
330    /// The payment authorization has expired.
331    InvalidPaymentExpired,
332    /// The chain ID doesn't match.
333    ChainIdMismatch,
334    /// The recipient address doesn't match.
335    RecipientMismatch,
336    /// The token asset doesn't match.
337    AssetMismatch,
338    /// The accepted details don't match requirements.
339    AcceptedRequirementsMismatch,
340    /// The signature is invalid.
341    InvalidSignature,
342    /// Transaction simulation failed.
343    TransactionSimulation,
344    /// Insufficient on-chain balance.
345    InsufficientFunds,
346    /// Insufficient allowance.
347    Permit2AllowanceRequired,
348    /// The chain is not supported.
349    UnsupportedChain,
350    /// The scheme is not supported.
351    UnsupportedScheme,
352    /// An unexpected error occurred.
353    UnexpectedError,
354}
355
356/// Trait for converting errors into structured payment problems.
357pub trait AsPaymentProblem {
358    /// Converts this error into a [`PaymentProblem`].
359    fn as_payment_problem(&self) -> PaymentProblem;
360}
361
362/// A structured payment error with reason code and details.
363///
364/// This type is used to return detailed error information to clients
365/// when a payment fails verification or settlement.
366pub struct PaymentProblem {
367    /// The machine-readable error reason.
368    reason: ErrorReason,
369    /// Human-readable error details.
370    details: String,
371}
372
373impl PaymentProblem {
374    /// Creates a new payment problem with the given reason and details.
375    pub fn new(reason: ErrorReason, details: String) -> Self {
376        Self { reason, details }
377    }
378
379    /// Returns the error reason code.
380    pub fn reason(&self) -> ErrorReason {
381        self.reason
382    }
383
384    /// Returns the human-readable error details.
385    pub fn details(&self) -> &str {
386        &self.details
387    }
388}
389
390/// Protocol version marker for [`PaymentRequired`] responses.
391pub struct PaymentRequiredV;
392
393impl ProtocolV for PaymentRequiredV {
394    type V1 = v1::PaymentRequired<OriginalJson>;
395    type V2 = v2::PaymentRequired<OriginalJson>;
396}
397
398/// A payment required response that can be either V1 or V2.
399///
400/// This is returned with HTTP 402 status to indicate that payment is required.
401pub type PaymentRequired = ProtocolVersioned<PaymentRequiredV>;
402
403/// Verbatim JSON for PaymentRequirements and other places.
404#[derive(Debug, Serialize, Deserialize, Clone)]
405pub struct OriginalJson(pub Box<serde_json::value::RawValue>);