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}