Skip to main content

r402_core/wire/
codec.rs

1//! JSON-safe primitives used by x402 wire types.
2//!
3//! These are encoding helpers, not protocol nouns. Domain envelopes
4//! (`PaymentRequired`, `VerifyRequest`, …) are siblings in [`crate::wire`].
5
6use std::fmt::{self, Display, Formatter};
7use std::ops::Add;
8use std::str::FromStr;
9use std::time::SystemTime;
10
11use base64::Engine;
12use base64::engine::general_purpose::STANDARD as B64;
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use serde_with::{DisplayFromStr, serde_as};
15
16/// A compile-time protocol-version marker that serializes as a bare integer.
17///
18/// The const parameter `N` encodes the version. Deserialization rejects
19/// values other than `N`, giving compile-time and runtime certainty that
20/// a message carries the expected protocol version.
21#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
22pub struct Version<const N: u8>;
23
24impl<const N: u8> Version<N> {
25    /// The numeric value of this version marker.
26    pub const VALUE: u8 = N;
27}
28
29impl<const N: u8> PartialEq<u8> for Version<N> {
30    fn eq(&self, other: &u8) -> bool {
31        *other == N
32    }
33}
34
35impl<const N: u8> From<Version<N>> for u8 {
36    fn from(_: Version<N>) -> Self {
37        N
38    }
39}
40
41impl<const N: u8> Display for Version<N> {
42    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
43        write!(f, "{N}")
44    }
45}
46
47impl<const N: u8> Serialize for Version<N> {
48    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
49        serializer.serialize_u8(N)
50    }
51}
52
53impl<'de, const N: u8> Deserialize<'de> for Version<N> {
54    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
55        let v = u8::deserialize(deserializer)?;
56        if v == N {
57            Ok(Self)
58        } else {
59            Err(serde::de::Error::custom(format!(
60                "expected protocol version {N}, got {v}"
61            )))
62        }
63    }
64}
65
66/// Type alias for the x402 v2 version marker.
67pub type Version2 = Version<2>;
68
69/// Singleton value for [`Version2`].
70pub const V2: Version2 = Version;
71
72/// Raw bytes holding the base64-encoded ASCII representation of some payload.
73///
74/// Useful as a field type for x402 wire messages that transport arbitrary
75/// binary data (for example, the raw Solana transaction wrapped in
76/// [`PaymentPayload`](super::PaymentPayload)). Encoding is eager, decoding
77/// is deferred.
78///
79/// # Examples
80///
81/// ```
82/// use r402_core::wire::Base64Bytes;
83///
84/// let encoded = Base64Bytes::encode(b"hello world");
85/// let decoded = encoded.decode().unwrap();
86/// assert_eq!(decoded, b"hello world");
87/// ```
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct Base64Bytes(pub Vec<u8>);
90
91impl Base64Bytes {
92    /// Decodes the inner base64 bytes into their raw binary form.
93    ///
94    /// # Errors
95    ///
96    /// Returns a [`base64::DecodeError`] when the stored bytes are not
97    /// valid base64.
98    pub fn decode(&self) -> Result<Vec<u8>, base64::DecodeError> {
99        B64.decode(&self.0)
100    }
101
102    /// Encodes arbitrary bytes into a [`Base64Bytes`] wrapper.
103    #[must_use]
104    pub fn encode<T: AsRef<[u8]>>(input: T) -> Self {
105        Self(B64.encode(input.as_ref()).into_bytes())
106    }
107}
108
109impl AsRef<[u8]> for Base64Bytes {
110    fn as_ref(&self) -> &[u8] {
111        &self.0
112    }
113}
114
115impl From<&[u8]> for Base64Bytes {
116    fn from(slice: &[u8]) -> Self {
117        Self(slice.to_vec())
118    }
119}
120
121impl Display for Base64Bytes {
122    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
123        f.write_str(&String::from_utf8_lossy(&self.0))
124    }
125}
126
127/// A `u64` that serializes as a JSON string.
128///
129/// JSON integer parsing in the browser (JavaScript `Number`) is limited
130/// to 2⁵³ bits. x402 wraps `u64`-sized fields in strings to guarantee
131/// exact precision across runtimes.
132///
133/// # Examples
134///
135/// ```
136/// use r402_core::wire::U64String;
137///
138/// let value = U64String::from(42_u64);
139/// assert_eq!(value.inner(), 42);
140/// assert_eq!(serde_json::to_string(&value).unwrap(), r#""42""#);
141/// let parsed: U64String = serde_json::from_str(r#""42""#).unwrap();
142/// assert_eq!(parsed.inner(), 42);
143/// ```
144#[serde_as]
145#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
146#[repr(transparent)]
147pub struct U64String(#[serde_as(as = "DisplayFromStr")] u64);
148
149impl U64String {
150    /// Returns the wrapped `u64`.
151    #[must_use]
152    pub const fn inner(self) -> u64 {
153        self.0
154    }
155}
156
157impl Display for U64String {
158    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
159        Display::fmt(&self.0, f)
160    }
161}
162
163impl FromStr for U64String {
164    type Err = <u64 as FromStr>::Err;
165    fn from_str(s: &str) -> Result<Self, Self::Err> {
166        s.parse::<u64>().map(Self)
167    }
168}
169
170impl From<u64> for U64String {
171    fn from(value: u64) -> Self {
172        Self(value)
173    }
174}
175
176impl From<U64String> for u64 {
177    fn from(value: U64String) -> Self {
178        value.0
179    }
180}
181
182/// A Unix timestamp in seconds since the epoch (1970-01-01T00:00:00Z).
183///
184/// The x402 wire format stringifies integer timestamps to guarantee
185/// cross-language precision (JavaScript's `Number` type cannot precisely
186/// represent all `u64` values). [`UnixTimestamp`] serializes as a JSON
187/// string and deserializes from either a JSON string or number.
188///
189/// # Examples
190///
191/// ```
192/// use r402_core::wire::UnixTimestamp;
193///
194/// let ts = UnixTimestamp::from_secs(1_700_000_000);
195/// assert_eq!(ts.as_secs(), 1_700_000_000);
196/// assert_eq!((ts + 3600).as_secs(), 1_700_003_600);
197/// assert_eq!(serde_json::to_string(&ts).unwrap(), r#""1700000000""#);
198/// ```
199#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
200pub struct UnixTimestamp(u64);
201
202impl UnixTimestamp {
203    /// Constructs a timestamp from raw Unix seconds.
204    #[must_use]
205    pub const fn from_secs(secs: u64) -> Self {
206        Self(secs)
207    }
208
209    /// Returns the current system clock as a [`UnixTimestamp`].
210    ///
211    /// Falls back to the epoch if the clock is before 1970.
212    #[must_use]
213    pub fn now() -> Self {
214        let secs = SystemTime::now()
215            .duration_since(SystemTime::UNIX_EPOCH)
216            .map(|d| d.as_secs())
217            .unwrap_or_default();
218        Self(secs)
219    }
220
221    /// Returns the timestamp as raw Unix seconds.
222    #[must_use]
223    pub const fn as_secs(self) -> u64 {
224        self.0
225    }
226}
227
228impl Display for UnixTimestamp {
229    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
230        Display::fmt(&self.0, f)
231    }
232}
233
234impl Add<u64> for UnixTimestamp {
235    type Output = Self;
236    fn add(self, rhs: u64) -> Self::Output {
237        Self(self.0.saturating_add(rhs))
238    }
239}
240
241impl Serialize for UnixTimestamp {
242    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
243        serializer.collect_str(&self.0)
244    }
245}
246
247impl<'de> Deserialize<'de> for UnixTimestamp {
248    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
249        struct Visitor;
250
251        impl serde::de::Visitor<'_> for Visitor {
252            type Value = UnixTimestamp;
253
254            fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result {
255                f.write_str("a non-negative integer or its string representation")
256            }
257
258            fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Self::Value, E> {
259                Ok(UnixTimestamp(v))
260            }
261
262            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
263                v.parse::<u64>()
264                    .map(UnixTimestamp)
265                    .map_err(|_| E::custom("expected non-negative integer"))
266            }
267        }
268
269        deserializer.deserialize_any(Visitor)
270    }
271}