use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::fmt::{Display, Formatter};
use std::sync::Arc;
use crate::chain::ChainId;
use crate::proto;
use crate::proto::v1;
use crate::proto::{OriginalJson, SupportedResponse};
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
pub struct X402Version2;
impl X402Version2 {
pub const VALUE: u8 = 2;
}
impl PartialEq<u8> for X402Version2 {
fn eq(&self, other: &u8) -> bool {
*other == Self::VALUE
}
}
impl From<X402Version2> for u8 {
fn from(_: X402Version2) -> Self {
X402Version2::VALUE
}
}
impl Serialize for X402Version2 {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_u8(Self::VALUE)
}
}
impl<'de> Deserialize<'de> for X402Version2 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let num = u8::deserialize(deserializer)?;
if num == Self::VALUE {
Ok(X402Version2)
} else {
Err(serde::de::Error::custom(format!(
"expected version {}, got {}",
Self::VALUE,
num
)))
}
}
}
impl Display for X402Version2 {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", Self::VALUE)
}
}
pub type VerifyResponse = v1::VerifyResponse;
pub type SettleResponse = v1::SettleResponse;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourceInfo {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VerifyRequest<TPayload, TRequirements> {
pub x402_version: X402Version2,
pub payment_payload: TPayload,
pub payment_requirements: TRequirements,
}
impl<TPayload, TRequirements> TryFrom<&VerifyRequest<TPayload, TRequirements>>
for proto::VerifyRequest
where
TPayload: Serialize,
TRequirements: Serialize,
{
type Error = serde_json::Error;
fn try_from(value: &VerifyRequest<TPayload, TRequirements>) -> Result<Self, Self::Error> {
let json = serde_json::to_string(value)?;
let raw = serde_json::value::RawValue::from_string(json)?;
Ok(Self(raw))
}
}
impl<TPayload, TRequirements> TryFrom<&proto::VerifyRequest>
for VerifyRequest<TPayload, TRequirements>
where
TPayload: DeserializeOwned,
TRequirements: DeserializeOwned,
{
type Error = proto::PaymentVerificationError;
fn try_from(value: &proto::VerifyRequest) -> Result<Self, Self::Error> {
let deserialized = serde_json::from_str(value.as_str())?;
Ok(deserialized)
}
}
impl<TPayload, TRequirements> VerifyRequest<TPayload, TRequirements>
where
Self: DeserializeOwned,
{
pub fn from_proto(
request: proto::VerifyRequest,
) -> Result<Self, proto::PaymentVerificationError> {
let value = serde_json::from_str(request.as_str())?;
Ok(value)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentPayload<TPaymentRequirements, TPayload> {
pub accepted: TPaymentRequirements,
pub payload: TPayload,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resource: Option<ResourceInfo>,
pub x402_version: X402Version2,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extensions: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PaymentRequirements<
TScheme = String,
TAmount = String,
TAddress = String,
TExtra = Option<serde_json::Value>,
> {
pub scheme: TScheme,
pub network: ChainId,
pub amount: TAmount,
pub pay_to: TAddress,
pub max_timeout_seconds: u64,
pub asset: TAddress,
pub extra: TExtra,
}
impl<TScheme, TAmount, TAddress, TExtra> TryFrom<&OriginalJson>
for PaymentRequirements<TScheme, TAmount, TAddress, TExtra>
where
TScheme: for<'a> serde::Deserialize<'a>,
TAmount: for<'a> serde::Deserialize<'a>,
TAddress: for<'a> serde::Deserialize<'a>,
TExtra: for<'a> serde::Deserialize<'a>,
{
type Error = serde_json::Error;
fn try_from(value: &OriginalJson) -> Result<Self, Self::Error> {
let payment_requirements = serde_json::from_str(value.0.get())?;
Ok(payment_requirements)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentRequired<TAccepts = PaymentRequirements> {
pub x402_version: X402Version2,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub resource: Option<ResourceInfo>,
#[serde(default = "Vec::default")]
pub accepts: Vec<TAccepts>,
}
#[derive(Clone)]
#[allow(dead_code)] pub struct PriceTag {
pub requirements: PaymentRequirements,
#[doc(hidden)]
pub enricher: Option<Enricher>,
}
impl fmt::Debug for PriceTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PriceTag")
.field("requirements", &self.requirements)
.finish()
}
}
pub type Enricher = Arc<dyn Fn(&mut PriceTag, &SupportedResponse) + Send + Sync>;
impl PriceTag {
#[allow(dead_code)]
pub fn enrich(&mut self, capabilities: &SupportedResponse) {
if let Some(enricher) = self.enricher.clone() {
enricher(self, capabilities);
}
}
#[allow(dead_code)]
pub fn with_timeout(mut self, seconds: u64) -> Self {
self.requirements.max_timeout_seconds = seconds;
self
}
}
impl PartialEq<PaymentRequirements> for PriceTag {
fn eq(&self, b: &PaymentRequirements) -> bool {
let a = &self.requirements;
a == b
}
}