Skip to main content

r402_protocol/payment/
supported.rs

1//! Facilitator `/supported` response.
2
3use std::collections::HashMap;
4
5use compact_str::CompactString;
6use serde::{Deserialize, Serialize};
7use serde_with::{VecSkipError, serde_as};
8
9use crate::network::ChainId;
10
11/// A single payment kind advertised by a facilitator.
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase", deny_unknown_fields)]
14#[non_exhaustive]
15pub struct SupportedPaymentKind {
16    /// x402 protocol version (`2`).
17    pub x402_version: u8,
18    /// Scheme name (`"exact"`, `"upto"`, …).
19    pub scheme: CompactString,
20    /// CAIP-2 network identifier.
21    pub network: CompactString,
22    /// Optional scheme-specific extras (fee payer, memo, …).
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub extra: Option<serde_json::Value>,
25}
26
27impl SupportedPaymentKind {
28    /// Constructs a kind from the three required fields.
29    #[must_use]
30    pub fn new(
31        x402_version: u8,
32        scheme: impl Into<CompactString>,
33        network: impl Into<CompactString>,
34    ) -> Self {
35        Self {
36            x402_version,
37            scheme: scheme.into(),
38            network: network.into(),
39            extra: None,
40        }
41    }
42
43    /// Attaches an `extra` JSON blob.
44    #[must_use]
45    pub fn with_extra(mut self, extra: serde_json::Value) -> Self {
46        self.extra = Some(extra);
47        self
48    }
49
50    /// Attaches an optional `extra` blob.
51    #[must_use]
52    pub fn with_optional_extra(mut self, extra: Option<serde_json::Value>) -> Self {
53        self.extra = extra;
54        self
55    }
56}
57
58/// Response body of a facilitator's `/supported` endpoint.
59///
60/// `extensions` is a JSON array of extension identifiers.
61#[serde_as]
62#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase", deny_unknown_fields)]
64#[non_exhaustive]
65pub struct SupportedResponse {
66    /// Supported payment kinds. Invalid entries are silently skipped.
67    #[serde_as(as = "VecSkipError<_>")]
68    pub kinds: Vec<SupportedPaymentKind>,
69    /// Supported extension identifiers.
70    #[serde(default)]
71    pub extensions: Vec<CompactString>,
72    /// Signer addresses indexed by CAIP-2 pattern (`"eip155:8453"`, `"solana:*"`).
73    #[serde(default)]
74    pub signers: HashMap<CompactString, Vec<CompactString>>,
75}
76
77impl SupportedResponse {
78    /// Empty response.
79    #[must_use]
80    pub fn new() -> Self {
81        Self::default()
82    }
83
84    /// Replaces the `kinds` list.
85    #[must_use]
86    pub fn with_kinds(mut self, kinds: Vec<SupportedPaymentKind>) -> Self {
87        self.kinds = kinds;
88        self
89    }
90
91    /// Replaces the `extensions` identifier list.
92    #[must_use]
93    pub fn with_extensions(mut self, extensions: Vec<CompactString>) -> Self {
94        self.extensions = extensions;
95        self
96    }
97
98    /// Replaces the per-pattern signer map.
99    #[must_use]
100    #[allow(
101        clippy::implicit_hasher,
102        reason = "wire map is a JSON object with no hasher contract"
103    )]
104    pub fn with_signers(mut self, signers: HashMap<CompactString, Vec<CompactString>>) -> Self {
105        self.signers = signers;
106        self
107    }
108
109    /// Signer addresses matching the given chain (exact and `namespace:*`).
110    #[must_use]
111    pub fn signers_for_chain(&self, chain_id: &ChainId) -> Vec<&str> {
112        let exact = CompactString::from(chain_id.to_string());
113        let wildcard = CompactString::from(format!("{}:*", chain_id.namespace()));
114        let mut out = Vec::new();
115        if let Some(list) = self.signers.get(&exact) {
116            out.extend(list.iter().map(CompactString::as_str));
117        }
118        if let Some(list) = self.signers.get(&wildcard) {
119            out.extend(list.iter().map(CompactString::as_str));
120        }
121        out
122    }
123}