1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use strum::{Display, EnumString};
9use uuid::Uuid;
10
11use super::x402::{X402Asset, X402Network};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct AgentCard {
26 pub id: Uuid,
28
29 pub name: String,
31
32 pub description: Option<String>,
34
35 pub wallet_address: String,
40
41 pub public_key: String,
43
44 pub supported_networks: Vec<X402Network>,
49
50 pub supported_assets: Vec<X402Asset>,
52
53 pub a2a_skills: Vec<A2ASkill>,
58
59 pub trust_level: TrustLevel,
64
65 pub verified_at: Option<DateTime<Utc>>,
67
68 pub verification_method: Option<String>,
70
71 pub endpoint_url: Option<String>,
76
77 pub endpoint_protocol: Option<String>,
79
80 pub merchant_id: Option<String>,
85
86 pub merchant_name: Option<String>,
88
89 pub business_category: Option<String>,
91
92 pub max_transaction_amount: Option<u64>,
97
98 pub daily_volume_limit: Option<u64>,
100
101 pub requires_kyc: bool,
103
104 pub active: bool,
109
110 pub suspended_at: Option<DateTime<Utc>>,
112
113 pub suspension_reason: Option<String>,
115
116 pub metadata: Option<String>,
121
122 pub created_at: DateTime<Utc>,
124
125 pub updated_at: DateTime<Utc>,
127}
128
129impl AgentCard {
130 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 #[must_use]
168 pub fn with_networks(mut self, networks: Vec<X402Network>) -> Self {
169 self.supported_networks = networks;
170 self
171 }
172
173 #[must_use]
175 pub fn with_assets(mut self, assets: Vec<X402Asset>) -> Self {
176 self.supported_assets = assets;
177 self
178 }
179
180 #[must_use]
182 pub fn with_skills(mut self, skills: Vec<A2ASkill>) -> Self {
183 self.a2a_skills = skills;
184 self
185 }
186
187 #[must_use]
189 pub const fn with_trust_level(mut self, level: TrustLevel) -> Self {
190 self.trust_level = level;
191 self
192 }
193
194 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 #[must_use]
203 pub fn supports_network(&self, network: X402Network) -> bool {
204 self.supported_networks.contains(&network)
205 }
206
207 #[must_use]
209 pub fn supports_asset(&self, asset: X402Asset) -> bool {
210 self.supported_assets.contains(&asset)
211 }
212
213 #[must_use]
215 pub fn has_skill(&self, skill: &A2ASkill) -> bool {
216 self.a2a_skills.contains(skill)
217 }
218
219 #[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 #[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#[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 #[strum(serialize = "sandbox", serialize = "test")]
248 Sandbox,
249 #[default]
251 #[strum(serialize = "standard", serialize = "default")]
252 Standard,
253 Verified,
255 #[strum(serialize = "enterprise", serialize = "business")]
257 Enterprise,
258}
259
260impl TrustLevel {
261 #[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 #[must_use]
274 pub const fn default_transaction_limit(&self) -> u64 {
275 match self {
276 Self::Sandbox => 100_000_000, Self::Standard => 1_000_000_000, Self::Verified => 10_000_000_000, Self::Enterprise => 100_000_000_000, }
281 }
282
283 #[must_use]
285 pub const fn default_daily_limit(&self) -> u64 {
286 match self {
287 Self::Sandbox => 1_000_000_000, Self::Standard => 10_000_000_000, Self::Verified => 100_000_000_000, Self::Enterprise => 1_000_000_000_000, }
292 }
293}
294
295#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display, EnumString, Serialize, Deserialize)]
301#[serde(rename_all = "snake_case")]
302#[non_exhaustive]
303pub enum A2ASkill {
304 #[strum(serialize = "commerce.sell", serialize = "sell")]
306 Sell,
307 #[strum(serialize = "commerce.buy", serialize = "buy")]
309 Buy,
310 #[strum(serialize = "commerce.quote", serialize = "quote")]
312 Quote,
313 #[strum(serialize = "commerce.request_quote", serialize = "request_quote")]
315 RequestQuote,
316 #[strum(serialize = "commerce.fulfill", serialize = "fulfill")]
318 Fulfill,
319 #[strum(serialize = "commerce.ship", serialize = "ship")]
321 Ship,
322 #[strum(serialize = "commerce.digital_deliver", serialize = "digital_deliver")]
324 DigitalDeliver,
325 #[strum(serialize = "commerce.process_return", serialize = "process_return")]
327 ProcessReturn,
328 #[strum(serialize = "commerce.refund", serialize = "refund")]
330 Refund,
331 #[strum(serialize = "commerce.support", serialize = "support")]
333 Support,
334}
335
336#[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#[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#[derive(Debug, Clone, Default, Serialize, Deserialize)]
385pub struct AgentCardFilter {
386 pub wallet_address: Option<String>,
388 pub trust_level: Option<TrustLevel>,
390 pub min_trust_level: Option<TrustLevel>,
392 pub network: Option<X402Network>,
394 pub asset: Option<X402Asset>,
396 pub skill: Option<A2ASkill>,
398 pub active: Option<bool>,
400 pub merchant_id: Option<String>,
402 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}