use std::collections::HashMap;
use std::fmt::{self, Display, Formatter};
use std::ops::Add;
use std::str::FromStr;
use std::time::SystemTime;
use base64::Engine;
use base64::engine::general_purpose::STANDARD as b64;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::{VecSkipError, serde_as};
use crate::chain::ChainId;
use crate::scheme::SchemeSlug;
mod error;
pub mod v2;
pub use error::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Base64Bytes(pub Vec<u8>);
impl Base64Bytes {
pub fn decode(&self) -> Result<Vec<u8>, base64::DecodeError> {
b64.decode(&self.0)
}
pub fn encode<T: AsRef<[u8]>>(input: T) -> Self {
let encoded = b64.encode(input.as_ref());
Self(encoded.into_bytes())
}
}
impl AsRef<[u8]> for Base64Bytes {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<&[u8]> for Base64Bytes {
fn from(slice: &[u8]) -> Self {
Self(slice.to_vec())
}
}
impl Display for Base64Bytes {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", String::from_utf8_lossy(&self.0))
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq)]
pub struct UnixTimestamp(u64);
impl Serialize for UnixTimestamp {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.0.to_string())
}
}
impl<'de> Deserialize<'de> for UnixTimestamp {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let ts = s
.parse::<u64>()
.map_err(|_| serde::de::Error::custom("timestamp must be a non-negative integer"))?;
Ok(Self(ts))
}
}
impl Display for UnixTimestamp {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Add<u64> for UnixTimestamp {
type Output = Self;
fn add(self, rhs: u64) -> Self::Output {
Self(self.0 + rhs)
}
}
impl UnixTimestamp {
#[must_use]
pub const fn from_secs(secs: u64) -> Self {
Self(secs)
}
#[must_use]
pub fn now() -> Self {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("SystemTime before UNIX epoch?!?")
.as_secs();
Self(now)
}
#[must_use]
pub const fn as_secs(&self) -> u64 {
self.0
}
}
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
pub struct Version<const N: u8>;
impl<const N: u8> Version<N> {
pub const VALUE: u8 = N;
}
impl<const N: u8> PartialEq<u8> for Version<N> {
fn eq(&self, other: &u8) -> bool {
*other == N
}
}
impl<const N: u8> From<Version<N>> for u8 {
fn from(_: Version<N>) -> Self {
N
}
}
impl<const N: u8> Display for Version<N> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{N}")
}
}
impl<const N: u8> Serialize for Version<N> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_u8(N)
}
}
impl<'de, const N: u8> Deserialize<'de> for Version<N> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let v = u8::deserialize(deserializer)?;
if v == N {
Ok(Self)
} else {
Err(serde::de::Error::custom(format!(
"expected version {N}, got {v}"
)))
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TypedVerifyRequest<const V: u8, TPayload, TRequirements> {
pub x402_version: Version<V>,
pub payment_payload: TPayload,
pub payment_requirements: TRequirements,
}
impl<const V: u8, TPayload, TRequirements> TypedVerifyRequest<V, TPayload, TRequirements>
where
Self: serde::de::DeserializeOwned,
{
pub fn from_proto(request: VerifyRequest) -> Result<Self, PaymentVerificationError> {
let deserialized: Self = serde_json::from_value(request.into_json())?;
Ok(deserialized)
}
pub fn from_settle(request: SettleRequest) -> Result<Self, PaymentVerificationError> {
let deserialized: Self = serde_json::from_value(request.into_json())?;
Ok(deserialized)
}
}
impl<const V: u8, TPayload, TRequirements> TryInto<VerifyRequest>
for TypedVerifyRequest<V, TPayload, TRequirements>
where
TPayload: Serialize,
TRequirements: Serialize,
{
type Error = serde_json::Error;
fn try_into(self) -> Result<VerifyRequest, Self::Error> {
let json = serde_json::to_value(self)?;
Ok(VerifyRequest(json))
}
}
pub type Extensions = HashMap<String, serde_json::Value>;
#[serde_as]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct U64String(#[serde_as(as = "serde_with::DisplayFromStr")] u64);
impl U64String {
#[must_use]
pub const fn inner(&self) -> u64 {
self.0
}
}
impl FromStr for U64String {
type Err = <u64 as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<u64>().map(Self)
}
}
impl From<u64> for U64String {
fn from(value: u64) -> Self {
Self(value)
}
}
impl From<U64String> for u64 {
fn from(value: U64String) -> Self {
value.0
}
}
#[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")]
pub struct SupportedResponse {
#[serde_as(as = "VecSkipError<_>")]
pub kinds: Vec<SupportedPaymentKind>,
#[serde(default)]
pub extensions: Vec<String>,
#[serde(default)]
pub signers: HashMap<String, Vec<String>>,
}
impl SupportedResponse {
#[must_use]
pub fn signers_for_chain(&self, chain_id: &ChainId) -> Vec<&str> {
let exact_key = chain_id.to_string();
let wildcard_key = format!("{}:*", chain_id.namespace());
let mut result = Vec::new();
if let Some(addrs) = self.signers.get(&exact_key) {
result.extend(addrs.iter().map(String::as_str));
}
if let Some(addrs) = self.signers.get(&wildcard_key) {
result.extend(addrs.iter().map(String::as_str));
}
result
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerifyRequest(serde_json::Value);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SettleRequest(serde_json::Value);
impl SettleRequest {
#[must_use]
pub fn into_json(self) -> serde_json::Value {
self.0
}
#[must_use]
pub fn scheme_slug(&self) -> Option<SchemeSlug> {
scheme_slug_from_json(&self.0)
}
#[must_use]
pub fn network(&self) -> &str {
self.0
.get("paymentRequirements")
.and_then(|r| r.get("network"))
.and_then(serde_json::Value::as_str)
.unwrap_or_default()
}
}
impl From<serde_json::Value> for SettleRequest {
fn from(value: serde_json::Value) -> Self {
Self(value)
}
}
impl From<VerifyRequest> for SettleRequest {
fn from(request: VerifyRequest) -> Self {
Self(request.into_json())
}
}
impl From<serde_json::Value> for VerifyRequest {
fn from(value: serde_json::Value) -> Self {
Self(value)
}
}
impl VerifyRequest {
#[must_use]
pub fn into_json(self) -> serde_json::Value {
self.0
}
#[must_use]
pub fn scheme_slug(&self) -> Option<SchemeSlug> {
scheme_slug_from_json(&self.0)
}
}
fn scheme_slug_from_json(json: &serde_json::Value) -> Option<SchemeSlug> {
let x402_version: u8 = json.get("x402Version")?.as_u64()?.try_into().ok()?;
if x402_version != v2::Version2::VALUE {
return None;
}
let accepted = json.get("paymentPayload")?.get("accepted")?;
let chain_id = ChainId::from_str(accepted.get("network")?.as_str()?).ok()?;
let scheme = accepted.get("scheme")?.as_str()?;
Some(SchemeSlug::new(chain_id, scheme.into()))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(into = "VerifyResponseWire", try_from = "VerifyResponseWire")]
#[non_exhaustive]
pub enum VerifyResponse {
Valid {
payer: String,
},
Invalid {
reason: String,
message: Option<String>,
payer: Option<String>,
},
}
impl VerifyResponse {
#[must_use]
pub const fn valid(payer: String) -> Self {
Self::Valid { payer }
}
#[must_use]
pub const fn invalid(payer: Option<String>, reason: String) -> Self {
Self::Invalid {
reason,
message: None,
payer,
}
}
#[must_use]
pub const fn invalid_with_message(
payer: Option<String>,
reason: String,
message: String,
) -> Self {
Self::Invalid {
reason,
message: Some(message),
payer,
}
}
#[must_use]
pub const fn is_valid(&self) -> bool {
matches!(self, Self::Valid { .. })
}
#[must_use]
pub fn from_facilitator_error(error: &crate::facilitator::FacilitatorError) -> Self {
let problem = error.as_payment_problem();
Self::Invalid {
reason: problem.reason().to_string(),
message: Some(problem.details().to_owned()),
payer: None,
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct VerifyResponseWire {
is_valid: bool,
#[serde(skip_serializing_if = "Option::is_none")]
payer: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
invalid_reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
invalid_message: Option<String>,
}
impl From<VerifyResponse> for VerifyResponseWire {
fn from(resp: VerifyResponse) -> Self {
match resp {
VerifyResponse::Valid { payer } => Self {
is_valid: true,
payer: Some(payer),
invalid_reason: None,
invalid_message: None,
},
VerifyResponse::Invalid {
reason,
message,
payer,
} => Self {
is_valid: false,
payer,
invalid_reason: Some(reason),
invalid_message: message,
},
}
}
}
impl TryFrom<VerifyResponseWire> for VerifyResponse {
type Error = String;
fn try_from(wire: VerifyResponseWire) -> Result<Self, Self::Error> {
if wire.is_valid {
let payer = wire.payer.ok_or("missing field: payer")?;
Ok(Self::Valid { payer })
} else {
let reason = wire.invalid_reason.ok_or("missing field: invalidReason")?;
Ok(Self::Invalid {
reason,
message: wire.invalid_message,
payer: wire.payer,
})
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(into = "SettleResponseWire", try_from = "SettleResponseWire")]
#[non_exhaustive]
pub enum SettleResponse {
Success {
payer: String,
transaction: String,
network: String,
extensions: Option<Extensions>,
},
Error {
reason: String,
message: Option<String>,
payer: Option<String>,
network: String,
},
}
impl SettleResponse {
#[must_use]
pub const fn is_success(&self) -> bool {
matches!(self, Self::Success { .. })
}
#[must_use]
pub fn encode_base64(&self) -> Option<Base64Bytes> {
if !self.is_success() {
return None;
}
let json = serde_json::to_vec(self).ok()?;
Some(Base64Bytes::encode(json))
}
#[must_use]
pub fn from_facilitator_error(
error: &crate::facilitator::FacilitatorError,
network: String,
) -> Self {
let problem = error.as_payment_problem();
Self::Error {
reason: problem.reason().to_string(),
message: Some(problem.details().to_owned()),
payer: None,
network,
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SettleResponseWire {
success: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
error_reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
error_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
payer: Option<String>,
#[serde(default)]
transaction: String,
network: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
extensions: Option<Extensions>,
}
impl From<SettleResponse> for SettleResponseWire {
fn from(resp: SettleResponse) -> Self {
match resp {
SettleResponse::Success {
payer,
transaction,
network,
extensions,
} => Self {
success: true,
error_reason: None,
error_message: None,
payer: Some(payer),
transaction,
network,
extensions,
},
SettleResponse::Error {
reason,
message,
payer,
network,
} => Self {
success: false,
error_reason: Some(reason),
error_message: message,
payer,
transaction: String::new(),
network,
extensions: None,
},
}
}
}
impl TryFrom<SettleResponseWire> for SettleResponse {
type Error = String;
fn try_from(
wire: SettleResponseWire,
) -> Result<Self, <Self as TryFrom<SettleResponseWire>>::Error> {
if wire.success {
let payer = wire.payer.ok_or("missing field: payer")?;
if wire.transaction.is_empty() {
return Err("missing field: transaction".to_owned());
}
Ok(Self::Success {
payer,
transaction: wire.transaction,
network: wire.network,
extensions: wire.extensions,
})
} else {
let reason = wire.error_reason.ok_or("missing field: errorReason")?;
Ok(Self::Error {
reason,
message: wire.error_message,
payer: wire.payer,
network: wire.network,
})
}
}
}
pub type PaymentRequired = v2::PaymentRequired;