use serde::{Deserialize, Serialize};
use serde_with::{VecSkipError, serde_as};
use std::collections::HashMap;
use crate::chain::ChainId;
use crate::scheme::SchemeHandlerSlug;
pub mod util;
pub mod v1;
pub mod v2;
pub trait ProtocolV {
type V1;
type V2;
}
pub enum ProtocolVersioned<T>
where
T: ProtocolV,
{
#[allow(dead_code)]
V1(T::V1),
#[allow(dead_code)]
V2(T::V2),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SupportedPaymentKind {
pub x402_version: u8,
pub scheme: String,
pub network: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub extra: Option<serde_json::Value>,
}
#[serde_as]
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(dead_code)] pub struct SupportedResponse {
#[serde_as(as = "VecSkipError<_>")]
pub kinds: Vec<SupportedPaymentKind>,
#[serde(default)]
pub extensions: Vec<String>,
#[serde(default)]
pub signers: HashMap<ChainId, Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerifyRequest(Box<serde_json::value::RawValue>);
pub type SettleRequest = VerifyRequest;
impl From<Box<serde_json::value::RawValue>> for VerifyRequest {
fn from(value: Box<serde_json::value::RawValue>) -> Self {
Self(value)
}
}
impl VerifyRequest {
pub fn as_str(&self) -> &str {
self.0.get()
}
pub fn scheme_handler_slug(&self) -> Option<SchemeHandlerSlug> {
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
enum VerifyRequestWire {
#[serde(rename_all = "camelCase")]
V1 {
x402_version: v1::X402Version1,
payment_payload: PaymentPayloadV1,
},
#[serde(rename_all = "camelCase")]
V2 {
x402_version: v2::X402Version2,
payment_payload: PaymentPayloadV2,
},
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct PaymentPayloadV1 {
pub network: String,
pub scheme: String,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct PaymentPayloadV2 {
pub accepted: PaymentPayloadV2Accepted,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct PaymentPayloadV2Accepted {
pub network: ChainId,
pub scheme: String,
}
let wire = serde_json::from_str::<VerifyRequestWire>(self.as_str()).ok()?;
match wire {
VerifyRequestWire::V1 {
payment_payload,
x402_version,
} => {
let network_name = payment_payload.network;
let chain_id = ChainId::from_network_name(&network_name)?;
let scheme = payment_payload.scheme;
let slug = SchemeHandlerSlug::new(chain_id, x402_version.into(), scheme);
Some(slug)
}
VerifyRequestWire::V2 {
payment_payload,
x402_version,
} => {
let chain_id = payment_payload.accepted.network;
let scheme = payment_payload.accepted.scheme;
let slug = SchemeHandlerSlug::new(chain_id, x402_version.into(), scheme);
Some(slug)
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerifyResponse(pub serde_json::Value);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SettleResponse(pub serde_json::Value);
#[derive(Debug, thiserror::Error)]
pub enum PaymentVerificationError {
#[error("Invalid format: {0}")]
InvalidFormat(String),
#[error("Payment amount is invalid with respect to the payment requirements")]
InvalidPaymentAmount,
#[error("Payment authorization is not yet valid")]
Early,
#[error("Payment authorization is expired")]
Expired,
#[error("Payment chain id is invalid with respect to the payment requirements")]
ChainIdMismatch,
#[error("Payment recipient is invalid with respect to the payment requirements")]
RecipientMismatch,
#[error("Payment asset is invalid with respect to the payment requirements")]
AssetMismatch,
#[error("Onchain balance is not enough to cover the payment amount")]
InsufficientFunds,
#[error("Allowance is not enough to cover the payment amount")]
InsufficientAllowance,
#[error("{0}")]
InvalidSignature(String),
#[error("{0}")]
TransactionSimulation(String),
#[error("Unsupported chain")]
UnsupportedChain,
#[error("Unsupported scheme")]
UnsupportedScheme,
#[error("Accepted does not match payment requirements")]
AcceptedRequirementsMismatch,
}
impl AsPaymentProblem for PaymentVerificationError {
fn as_payment_problem(&self) -> PaymentProblem {
let error_reason = match self {
PaymentVerificationError::InvalidFormat(_) => ErrorReason::InvalidFormat,
PaymentVerificationError::InvalidPaymentAmount => ErrorReason::InvalidPaymentAmount,
PaymentVerificationError::InsufficientFunds => ErrorReason::InsufficientFunds,
PaymentVerificationError::InsufficientAllowance => {
ErrorReason::Permit2AllowanceRequired
}
PaymentVerificationError::Early => ErrorReason::InvalidPaymentEarly,
PaymentVerificationError::Expired => ErrorReason::InvalidPaymentExpired,
PaymentVerificationError::ChainIdMismatch => ErrorReason::ChainIdMismatch,
PaymentVerificationError::RecipientMismatch => ErrorReason::RecipientMismatch,
PaymentVerificationError::AssetMismatch => ErrorReason::AssetMismatch,
PaymentVerificationError::InvalidSignature(_) => ErrorReason::InvalidSignature,
PaymentVerificationError::TransactionSimulation(_) => {
ErrorReason::TransactionSimulation
}
PaymentVerificationError::UnsupportedChain => ErrorReason::UnsupportedChain,
PaymentVerificationError::UnsupportedScheme => ErrorReason::UnsupportedScheme,
PaymentVerificationError::AcceptedRequirementsMismatch => {
ErrorReason::AcceptedRequirementsMismatch
}
};
PaymentProblem::new(error_reason, self.to_string())
}
}
impl From<serde_json::Error> for PaymentVerificationError {
fn from(value: serde_json::Error) -> Self {
Self::InvalidFormat(value.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ErrorReason {
InvalidFormat,
InvalidPaymentAmount,
InvalidPaymentEarly,
InvalidPaymentExpired,
ChainIdMismatch,
RecipientMismatch,
AssetMismatch,
AcceptedRequirementsMismatch,
InvalidSignature,
TransactionSimulation,
InsufficientFunds,
Permit2AllowanceRequired,
UnsupportedChain,
UnsupportedScheme,
UnexpectedError,
}
pub trait AsPaymentProblem {
fn as_payment_problem(&self) -> PaymentProblem;
}
pub struct PaymentProblem {
reason: ErrorReason,
details: String,
}
impl PaymentProblem {
pub fn new(reason: ErrorReason, details: String) -> Self {
Self { reason, details }
}
pub fn reason(&self) -> ErrorReason {
self.reason
}
pub fn details(&self) -> &str {
&self.details
}
}
pub struct PaymentRequiredV;
impl ProtocolV for PaymentRequiredV {
type V1 = v1::PaymentRequired<OriginalJson>;
type V2 = v2::PaymentRequired<OriginalJson>;
}
pub type PaymentRequired = ProtocolVersioned<PaymentRequiredV>;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct OriginalJson(pub Box<serde_json::value::RawValue>);