Skip to main content

stateset_core/models/
agent_card.rs

1//! Agent Card domain models for AI agent identity and capabilities
2//!
3//! Agent cards are used to advertise an AI agent's commerce capabilities,
4//! payment methods, and trust level for agent-to-agent (A2A) commerce.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use strum::{Display, EnumString};
9use uuid::Uuid;
10
11use super::x402::{X402Asset, X402Network};
12
13// =============================================================================
14// Agent Card
15// =============================================================================
16
17/// Agent Card - Identity and capability advertisement for AI agents
18///
19/// Agent cards enable discovery and verification of AI agents in the
20/// agent-to-agent commerce ecosystem. They contain:
21/// - Identity: wallet address, public key for verification
22/// - Capabilities: supported payment networks, assets, A2A skills
23/// - Trust: verification level and limits
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct AgentCard {
26    /// Unique agent card ID
27    pub id: Uuid,
28
29    /// Human-readable agent name
30    pub name: String,
31
32    /// Description of the agent's purpose/capabilities
33    pub description: Option<String>,
34
35    // =========================================================================
36    // Identity & Authentication
37    // =========================================================================
38    /// Wallet address for receiving/sending payments
39    pub wallet_address: String,
40
41    /// Ed25519 public key for signature verification (hex-encoded)
42    pub public_key: String,
43
44    // =========================================================================
45    // Payment Capabilities
46    // =========================================================================
47    /// Supported blockchain networks
48    pub supported_networks: Vec<X402Network>,
49
50    /// Supported payment assets (stablecoins)
51    pub supported_assets: Vec<X402Asset>,
52
53    // =========================================================================
54    // A2A Commerce Capabilities
55    // =========================================================================
56    /// List of A2A skills this agent supports
57    pub a2a_skills: Vec<A2ASkill>,
58
59    // =========================================================================
60    // Trust & Verification
61    // =========================================================================
62    /// Trust level determines transaction limits and capabilities
63    pub trust_level: TrustLevel,
64
65    /// When the agent was verified (if applicable)
66    pub verified_at: Option<DateTime<Utc>>,
67
68    /// Method used for verification
69    pub verification_method: Option<String>,
70
71    // =========================================================================
72    // Endpoint
73    // =========================================================================
74    /// URL for A2A communication
75    pub endpoint_url: Option<String>,
76
77    /// Protocol for endpoint (https, grpc, websocket)
78    pub endpoint_protocol: Option<String>,
79
80    // =========================================================================
81    // Merchant/Business Info
82    // =========================================================================
83    /// Associated merchant ID
84    pub merchant_id: Option<String>,
85
86    /// Merchant/business name
87    pub merchant_name: Option<String>,
88
89    /// Business category
90    pub business_category: Option<String>,
91
92    // =========================================================================
93    // Limits & Policies
94    // =========================================================================
95    /// Maximum single transaction amount (in smallest unit)
96    pub max_transaction_amount: Option<u64>,
97
98    /// Daily volume limit (in smallest unit)
99    pub daily_volume_limit: Option<u64>,
100
101    /// Whether KYC is required for transactions
102    pub requires_kyc: bool,
103
104    // =========================================================================
105    // Status
106    // =========================================================================
107    /// Whether the agent card is active
108    pub active: bool,
109
110    /// When the agent was suspended (if applicable)
111    pub suspended_at: Option<DateTime<Utc>>,
112
113    /// Reason for suspension
114    pub suspension_reason: Option<String>,
115
116    // =========================================================================
117    // Metadata
118    // =========================================================================
119    /// Additional metadata (JSON)
120    pub metadata: Option<String>,
121
122    /// When the agent card was created
123    pub created_at: DateTime<Utc>,
124
125    /// When the agent card was last updated
126    pub updated_at: DateTime<Utc>,
127}
128
129impl AgentCard {
130    /// Create a new agent card
131    pub fn new(
132        name: impl Into<String>,
133        wallet_address: impl Into<String>,
134        public_key: impl Into<String>,
135    ) -> Self {
136        let now = Utc::now();
137        Self {
138            id: Uuid::new_v4(),
139            name: name.into(),
140            description: None,
141            wallet_address: wallet_address.into(),
142            public_key: public_key.into(),
143            supported_networks: vec![X402Network::SetChain],
144            supported_assets: vec![X402Asset::Usdc],
145            a2a_skills: Vec::new(),
146            trust_level: TrustLevel::Standard,
147            verified_at: None,
148            verification_method: None,
149            endpoint_url: None,
150            endpoint_protocol: None,
151            merchant_id: None,
152            merchant_name: None,
153            business_category: None,
154            max_transaction_amount: None,
155            daily_volume_limit: None,
156            requires_kyc: false,
157            active: true,
158            suspended_at: None,
159            suspension_reason: None,
160            metadata: None,
161            created_at: now,
162            updated_at: now,
163        }
164    }
165
166    /// Add supported networks
167    #[must_use]
168    pub fn with_networks(mut self, networks: Vec<X402Network>) -> Self {
169        self.supported_networks = networks;
170        self
171    }
172
173    /// Add supported assets
174    #[must_use]
175    pub fn with_assets(mut self, assets: Vec<X402Asset>) -> Self {
176        self.supported_assets = assets;
177        self
178    }
179
180    /// Add A2A skills
181    #[must_use]
182    pub fn with_skills(mut self, skills: Vec<A2ASkill>) -> Self {
183        self.a2a_skills = skills;
184        self
185    }
186
187    /// Set trust level
188    #[must_use]
189    pub const fn with_trust_level(mut self, level: TrustLevel) -> Self {
190        self.trust_level = level;
191        self
192    }
193
194    /// Set endpoint
195    pub fn with_endpoint(mut self, url: impl Into<String>, protocol: impl Into<String>) -> Self {
196        self.endpoint_url = Some(url.into());
197        self.endpoint_protocol = Some(protocol.into());
198        self
199    }
200
201    /// Check if agent supports a specific network
202    #[must_use]
203    pub fn supports_network(&self, network: X402Network) -> bool {
204        self.supported_networks.contains(&network)
205    }
206
207    /// Check if agent supports a specific asset
208    #[must_use]
209    pub fn supports_asset(&self, asset: X402Asset) -> bool {
210        self.supported_assets.contains(&asset)
211    }
212
213    /// Check if agent has a specific skill
214    #[must_use]
215    pub fn has_skill(&self, skill: &A2ASkill) -> bool {
216        self.a2a_skills.contains(skill)
217    }
218
219    /// Check if agent can sell
220    #[must_use]
221    pub fn can_sell(&self) -> bool {
222        self.a2a_skills.iter().any(|s| matches!(s, A2ASkill::Sell | A2ASkill::Quote))
223    }
224
225    /// Check if agent can buy
226    #[must_use]
227    pub fn can_buy(&self) -> bool {
228        self.a2a_skills.iter().any(|s| matches!(s, A2ASkill::Buy | A2ASkill::RequestQuote))
229    }
230}
231
232// =============================================================================
233// Trust Level
234// =============================================================================
235
236/// Trust level for agent cards
237///
238/// Higher trust levels enable higher transaction limits and more capabilities.
239#[derive(
240    Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, Serialize, Deserialize, Default,
241)]
242#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
243#[serde(rename_all = "snake_case")]
244#[non_exhaustive]
245pub enum TrustLevel {
246    /// Sandbox - for testing only, no real transactions
247    #[strum(serialize = "sandbox", serialize = "test")]
248    Sandbox,
249    /// Standard - default level for new agents
250    #[default]
251    #[strum(serialize = "standard", serialize = "default")]
252    Standard,
253    /// Verified - identity verified, higher limits
254    Verified,
255    /// Enterprise - business verified, highest limits
256    #[strum(serialize = "enterprise", serialize = "business")]
257    Enterprise,
258}
259
260impl TrustLevel {
261    /// Numeric rank for trust comparison (higher is more trusted).
262    #[must_use]
263    pub const fn rank(&self) -> u8 {
264        match self {
265            Self::Sandbox => 0,
266            Self::Standard => 1,
267            Self::Verified => 2,
268            Self::Enterprise => 3,
269        }
270    }
271
272    /// Get the default transaction limit for this trust level (in USDC cents)
273    #[must_use]
274    pub const fn default_transaction_limit(&self) -> u64 {
275        match self {
276            Self::Sandbox => 100_000_000,        // $100 (for testing)
277            Self::Standard => 1_000_000_000,     // $1,000
278            Self::Verified => 10_000_000_000,    // $10,000
279            Self::Enterprise => 100_000_000_000, // $100,000
280        }
281    }
282
283    /// Get the default daily volume limit for this trust level
284    #[must_use]
285    pub const fn default_daily_limit(&self) -> u64 {
286        match self {
287            Self::Sandbox => 1_000_000_000,        // $1,000/day
288            Self::Standard => 10_000_000_000,      // $10,000/day
289            Self::Verified => 100_000_000_000,     // $100,000/day
290            Self::Enterprise => 1_000_000_000_000, // $1,000,000/day
291        }
292    }
293}
294
295// =============================================================================
296// A2A Skills
297// =============================================================================
298
299/// A2A commerce skills that an agent can advertise
300#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display, EnumString, Serialize, Deserialize)]
301#[serde(rename_all = "snake_case")]
302#[non_exhaustive]
303pub enum A2ASkill {
304    /// Can sell products/services
305    #[strum(serialize = "commerce.sell", serialize = "sell")]
306    Sell,
307    /// Can buy products/services
308    #[strum(serialize = "commerce.buy", serialize = "buy")]
309    Buy,
310    /// Can provide price quotes
311    #[strum(serialize = "commerce.quote", serialize = "quote")]
312    Quote,
313    /// Can request price quotes
314    #[strum(serialize = "commerce.request_quote", serialize = "request_quote")]
315    RequestQuote,
316    /// Can fulfill orders
317    #[strum(serialize = "commerce.fulfill", serialize = "fulfill")]
318    Fulfill,
319    /// Can ship physical goods
320    #[strum(serialize = "commerce.ship", serialize = "ship")]
321    Ship,
322    /// Can provide digital delivery
323    #[strum(serialize = "commerce.digital_deliver", serialize = "digital_deliver")]
324    DigitalDeliver,
325    /// Can process returns
326    #[strum(serialize = "commerce.process_return", serialize = "process_return")]
327    ProcessReturn,
328    /// Can issue refunds
329    #[strum(serialize = "commerce.refund", serialize = "refund")]
330    Refund,
331    /// Can provide customer support
332    #[strum(serialize = "commerce.support", serialize = "support")]
333    Support,
334}
335
336// =============================================================================
337// Input/Filter Types
338// =============================================================================
339
340/// Input for creating an agent card
341#[derive(Debug, Clone, Default, Serialize, Deserialize)]
342pub struct CreateAgentCard {
343    pub name: String,
344    pub description: Option<String>,
345    pub wallet_address: String,
346    pub public_key: String,
347    pub supported_networks: Option<Vec<X402Network>>,
348    pub supported_assets: Option<Vec<X402Asset>>,
349    pub a2a_skills: Option<Vec<A2ASkill>>,
350    pub trust_level: Option<TrustLevel>,
351    pub endpoint_url: Option<String>,
352    pub endpoint_protocol: Option<String>,
353    pub merchant_id: Option<String>,
354    pub merchant_name: Option<String>,
355    pub business_category: Option<String>,
356    pub max_transaction_amount: Option<u64>,
357    pub daily_volume_limit: Option<u64>,
358    pub requires_kyc: Option<bool>,
359    pub metadata: Option<String>,
360}
361
362/// Input for updating an agent card
363#[derive(Debug, Clone, Default, Serialize, Deserialize)]
364pub struct UpdateAgentCard {
365    pub name: Option<String>,
366    pub description: Option<String>,
367    pub supported_networks: Option<Vec<X402Network>>,
368    pub supported_assets: Option<Vec<X402Asset>>,
369    pub a2a_skills: Option<Vec<A2ASkill>>,
370    pub trust_level: Option<TrustLevel>,
371    pub endpoint_url: Option<String>,
372    pub endpoint_protocol: Option<String>,
373    pub merchant_id: Option<String>,
374    pub merchant_name: Option<String>,
375    pub business_category: Option<String>,
376    pub max_transaction_amount: Option<u64>,
377    pub daily_volume_limit: Option<u64>,
378    pub requires_kyc: Option<bool>,
379    pub active: Option<bool>,
380    pub metadata: Option<String>,
381}
382
383/// Filter for listing agent cards
384#[derive(Debug, Clone, Default, Serialize, Deserialize)]
385pub struct AgentCardFilter {
386    /// Filter by wallet address
387    pub wallet_address: Option<String>,
388    /// Filter by trust level
389    pub trust_level: Option<TrustLevel>,
390    /// Filter by minimum trust level (inclusive)
391    pub min_trust_level: Option<TrustLevel>,
392    /// Filter by supported network
393    pub network: Option<X402Network>,
394    /// Filter by supported asset
395    pub asset: Option<X402Asset>,
396    /// Filter by skill
397    pub skill: Option<A2ASkill>,
398    /// Filter by active status
399    pub active: Option<bool>,
400    /// Filter by merchant ID
401    pub merchant_id: Option<String>,
402    /// Pagination
403    pub limit: Option<u32>,
404    pub offset: Option<u32>,
405}
406
407#[cfg(test)]
408mod tests {
409    use super::*;
410
411    #[test]
412    fn test_agent_card_creation() {
413        let card = AgentCard::new("TestAgent", "0x1234567890abcdef", "0xpubkey1234")
414            .with_networks(vec![X402Network::SetChain, X402Network::Base])
415            .with_assets(vec![X402Asset::Usdc, X402Asset::SsUsd])
416            .with_skills(vec![A2ASkill::Sell, A2ASkill::Quote]);
417
418        assert_eq!(card.name, "TestAgent");
419        assert!(card.supports_network(X402Network::SetChain));
420        assert!(card.supports_asset(X402Asset::Usdc));
421        assert!(card.can_sell());
422    }
423
424    #[test]
425    fn test_trust_level_limits() {
426        assert!(
427            TrustLevel::Enterprise.default_transaction_limit()
428                > TrustLevel::Standard.default_transaction_limit()
429        );
430        assert!(
431            TrustLevel::Verified.default_daily_limit() > TrustLevel::Standard.default_daily_limit()
432        );
433    }
434
435    #[test]
436    fn test_a2a_skill_parsing() {
437        assert_eq!("commerce.sell".parse::<A2ASkill>().unwrap(), A2ASkill::Sell);
438        assert_eq!("buy".parse::<A2ASkill>().unwrap(), A2ASkill::Buy);
439    }
440}