use super::network::Network;
use chrono::Utc;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::time::Duration;
pub const X402_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentRequirements {
pub scheme: String,
pub network: String,
#[serde(rename = "maxAmountRequired")]
pub max_amount_required: String,
pub asset: String,
#[serde(rename = "payTo")]
pub pay_to: String,
pub resource: String,
pub description: String,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(rename = "outputSchema", skip_serializing_if = "Option::is_none")]
pub output_schema: Option<Value>,
#[serde(rename = "maxTimeoutSeconds")]
pub max_timeout_seconds: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub extra: Option<Value>,
}
impl PaymentRequirements {
pub fn new(
scheme: impl Into<String>,
network: impl Into<String>,
max_amount_required: impl Into<String>,
asset: impl Into<String>,
pay_to: impl Into<String>,
resource: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
scheme: scheme.into(),
network: network.into(),
max_amount_required: max_amount_required.into(),
asset: asset.into(),
pay_to: pay_to.into(),
resource: resource.into(),
description: description.into(),
mime_type: None,
output_schema: None,
max_timeout_seconds: 60,
extra: None,
}
}
pub fn set_usdc_info(&mut self, network: Network) -> crate::Result<()> {
let mut usdc_info = HashMap::new();
usdc_info.insert("name".to_string(), network.usdc_name().to_string());
usdc_info.insert("version".to_string(), "2".to_string());
self.extra = Some(serde_json::to_value(usdc_info)?);
Ok(())
}
pub fn amount_as_decimal(&self) -> crate::Result<Decimal> {
self.max_amount_required
.parse()
.map_err(|_| crate::X402Error::invalid_payment_requirements("Invalid amount format"))
}
pub fn amount_in_decimal_units(&self, decimals: u8) -> crate::Result<Decimal> {
let amount = self.amount_as_decimal()?;
let divisor = Decimal::from(10u64.pow(decimals as u32));
Ok(amount / divisor)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentPayload {
#[serde(rename = "x402Version")]
pub x402_version: u32,
pub scheme: String,
pub network: String,
pub payload: ExactEvmPayload,
}
impl PaymentPayload {
pub fn new(
scheme: impl Into<String>,
network: impl Into<String>,
payload: ExactEvmPayload,
) -> Self {
Self {
x402_version: X402_VERSION,
scheme: scheme.into(),
network: network.into(),
payload,
}
}
pub fn from_base64(encoded: &str) -> crate::Result<Self> {
use base64::{engine::general_purpose, Engine as _};
let decoded = general_purpose::STANDARD.decode(encoded)?;
let payload: PaymentPayload = serde_json::from_slice(&decoded)?;
Ok(payload)
}
pub fn to_base64(&self) -> crate::Result<String> {
use base64::{engine::general_purpose, Engine as _};
let json = serde_json::to_string(self)?;
Ok(general_purpose::STANDARD.encode(json))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExactEvmPayload {
pub signature: String,
pub authorization: ExactEvmPayloadAuthorization,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExactEvmPayloadAuthorization {
pub from: String,
pub to: String,
pub value: String,
#[serde(rename = "validAfter")]
pub valid_after: String,
#[serde(rename = "validBefore")]
pub valid_before: String,
pub nonce: String,
}
impl ExactEvmPayloadAuthorization {
pub fn new(
from: impl Into<String>,
to: impl Into<String>,
value: impl Into<String>,
valid_after: impl Into<String>,
valid_before: impl Into<String>,
nonce: impl Into<String>,
) -> Self {
Self {
from: from.into(),
to: to.into(),
value: value.into(),
valid_after: valid_after.into(),
valid_before: valid_before.into(),
nonce: nonce.into(),
}
}
pub fn is_valid_now(&self) -> crate::Result<bool> {
let now = Utc::now().timestamp();
let valid_after: i64 = self.valid_after.parse().map_err(|_| {
crate::X402Error::invalid_authorization("Invalid valid_after timestamp")
})?;
let valid_before: i64 = self.valid_before.parse().map_err(|_| {
crate::X402Error::invalid_authorization("Invalid valid_before timestamp")
})?;
Ok(now >= valid_after && now <= valid_before)
}
pub fn validity_duration(&self) -> crate::Result<Duration> {
let valid_after: i64 = self.valid_after.parse().map_err(|_| {
crate::X402Error::invalid_authorization("Invalid valid_after timestamp")
})?;
let valid_before: i64 = self.valid_before.parse().map_err(|_| {
crate::X402Error::invalid_authorization("Invalid valid_before timestamp")
})?;
Ok(Duration::from_secs((valid_before - valid_after) as u64))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentRequirementsResponse {
#[serde(rename = "x402Version")]
pub x402_version: u32,
pub error: String,
pub accepts: Vec<PaymentRequirements>,
}
impl PaymentRequirementsResponse {
pub fn new(error: impl Into<String>, accepts: Vec<PaymentRequirements>) -> Self {
Self {
x402_version: X402_VERSION,
error: error.into(),
accepts,
}
}
}