Skip to main content

x402_types/
timestamp.rs

1//! Unix timestamp utilities for x402 payment authorization windows.
2//!
3//! This module provides the [`UnixTimestamp`] type used throughout the x402 protocol
4//! to represent time-bounded payment authorizations. Timestamps are used in ERC-3009
5//! `transferWithAuthorization` messages and Solana payment instructions to specify
6//! when a payment authorization becomes valid and when it expires.
7
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use std::fmt::{Display, Formatter};
10use std::ops::Add;
11use std::time::SystemTime;
12
13/// A Unix timestamp representing seconds since the Unix epoch (1970-01-01T00:00:00Z).
14///
15/// This type is used throughout the x402 protocol for time-bounded payment authorizations:
16///
17/// - **`validAfter`**: The earliest time a payment authorization can be executed
18/// - **`validBefore`**: The latest time a payment authorization remains valid
19///
20/// # Serialization
21///
22/// Serialized as a stringified integer to avoid loss of precision in JSON, since
23/// JavaScript's `Number` type cannot safely represent all 64-bit integers.
24///
25/// ```json
26/// "1699999999"
27/// ```
28///
29/// # Example
30///
31/// ```
32/// use x402_types::timestamp::UnixTimestamp;
33///
34/// // Create a timestamp for "now"
35/// let now = UnixTimestamp::now();
36///
37/// // Create a timestamp 1 hour in the future
38/// let expires = now + 3600;
39///
40/// // Create from a specific value
41/// let specific = UnixTimestamp::from_secs(1699999999);
42/// assert_eq!(specific.as_secs(), 1699999999);
43/// ```
44#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq)]
45pub struct UnixTimestamp(u64);
46
47impl Serialize for UnixTimestamp {
48    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
49        serializer.serialize_str(&self.0.to_string())
50    }
51}
52
53impl<'de> Deserialize<'de> for UnixTimestamp {
54    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
55    where
56        D: Deserializer<'de>,
57    {
58        let s = String::deserialize(deserializer)?;
59        let ts = s
60            .parse::<u64>()
61            .map_err(|_| serde::de::Error::custom("timestamp must be a non-negative integer"))?;
62        Ok(UnixTimestamp(ts))
63    }
64}
65
66impl Display for UnixTimestamp {
67    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
68        write!(f, "{}", self.0)
69    }
70}
71
72impl Add<u64> for UnixTimestamp {
73    type Output = Self;
74
75    fn add(self, rhs: u64) -> Self::Output {
76        UnixTimestamp(self.0 + rhs)
77    }
78}
79
80impl UnixTimestamp {
81    /// Creates a new [`UnixTimestamp`] from a raw seconds value.
82    ///
83    /// # Example
84    ///
85    /// ```
86    /// use x402_types::timestamp::UnixTimestamp;
87    ///
88    /// let ts = UnixTimestamp::from_secs(1699999999);
89    /// assert_eq!(ts.as_secs(), 1699999999);
90    /// ```
91    pub fn from_secs(secs: u64) -> Self {
92        Self(secs)
93    }
94
95    /// Returns the current system time as a [`UnixTimestamp`].
96    ///
97    /// # Panics
98    ///
99    /// Panics if the system clock is set to a time before the Unix epoch,
100    /// which should never happen on properly configured systems.
101    ///
102    /// # Example
103    ///
104    /// ```
105    /// use x402_types::timestamp::UnixTimestamp;
106    ///
107    /// let now = UnixTimestamp::now();
108    /// // Timestamp should be after year 2020
109    /// assert!(now.as_secs() > 1577836800);
110    /// ```
111    pub fn now() -> Self {
112        let now = SystemTime::now()
113            .duration_since(SystemTime::UNIX_EPOCH)
114            .expect("SystemTime before UNIX epoch?!?")
115            .as_secs();
116        Self(now)
117    }
118
119    /// Returns the timestamp as raw seconds since the Unix epoch.
120    ///
121    /// # Example
122    ///
123    /// ```
124    /// use x402_types::timestamp::UnixTimestamp;
125    ///
126    /// let ts = UnixTimestamp::from_secs(1699999999);
127    /// assert_eq!(ts.as_secs(), 1699999999);
128    /// ```
129    pub fn as_secs(&self) -> u64 {
130        self.0
131    }
132}