Skip to main content

tap_msg/message/
agent.rs

1//! Agent types for TAP messages (TAIP-5).
2//!
3//! This module defines the structure of agent information used in TAP messages.
4//! Agents are services involved in executing transactions such as exchanges,
5//! custodial wallet services, wallets, blockchain addresses, DeFi protocols, and bridges.
6
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10use crate::message::policy::Policy;
11
12/// Common trait for TAP participants (agents and parties)
13pub trait TapParticipant {
14    /// Get the identifier of this participant
15    fn id(&self) -> &str;
16}
17
18/// Helper for serializing/deserializing the `for` field that can be either a string or an array
19#[derive(Debug, Clone, PartialEq)]
20pub struct ForParties(pub Vec<String>);
21
22impl Serialize for ForParties {
23    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
24    where
25        S: serde::Serializer,
26    {
27        if self.0.len() == 1 {
28            // Serialize as a single string if there's only one party
29            self.0[0].serialize(serializer)
30        } else {
31            // Serialize as an array if there are multiple parties
32            self.0.serialize(serializer)
33        }
34    }
35}
36
37impl<'de> Deserialize<'de> for ForParties {
38    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39    where
40        D: serde::Deserializer<'de>,
41    {
42        use serde::de::{self, Visitor};
43        use std::fmt;
44
45        struct ForPartiesVisitor;
46
47        impl<'de> Visitor<'de> for ForPartiesVisitor {
48            type Value = ForParties;
49
50            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
51                formatter.write_str("a string or an array of strings")
52            }
53
54            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
55            where
56                E: de::Error,
57            {
58                Ok(ForParties(vec![value.to_string()]))
59            }
60
61            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
62            where
63                A: de::SeqAccess<'de>,
64            {
65                let mut parties = Vec::new();
66                while let Some(party) = seq.next_element::<String>()? {
67                    parties.push(party);
68                }
69                Ok(ForParties(parties))
70            }
71        }
72
73        deserializer.deserialize_any(ForPartiesVisitor)
74    }
75}
76
77/// Agent in a transaction (TAIP-5).
78///
79/// Agents are identified using Decentralized Identifiers (DIDs) and can be:
80/// - Centralized services (exchanges, custodial wallets)
81/// - End-user software (self-hosted wallets)  
82/// - Decentralized protocols (DeFi protocols, bridges)
83#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
84pub struct Agent {
85    /// DID of the agent.
86    #[serde(rename = "@id")]
87    pub id: String,
88
89    /// Role of the agent in this transaction (optional).
90    /// Examples: "SettlementAddress", "SourceAddress", etc.
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub role: Option<String>,
93
94    /// DID or IRI of another Agent or Party that this agent acts on behalf of (REQUIRED per TAIP-5).
95    /// Can be a single party or multiple parties.
96    #[serde(rename = "for")]
97    pub for_parties: ForParties,
98
99    /// Policies of the agent according to TAIP-7 (optional).
100    #[serde(skip_serializing_if = "Option::is_none")]
101    #[serde(default)]
102    pub policies: Option<Vec<Policy>>,
103
104    /// Additional JSON-LD metadata for the agent.
105    /// This allows for extensible metadata beyond the core fields.
106    #[serde(flatten)]
107    pub metadata: HashMap<String, serde_json::Value>,
108}
109
110impl TapParticipant for Agent {
111    fn id(&self) -> &str {
112        &self.id
113    }
114}
115
116impl Agent {
117    /// Create a new agent with the given DID, role, and for_party.
118    pub fn new(id: &str, role: &str, for_party: &str) -> Self {
119        Self {
120            id: id.to_string(),
121            role: Some(role.to_string()),
122            for_parties: ForParties(vec![for_party.to_string()]),
123            policies: None,
124            metadata: HashMap::new(),
125        }
126    }
127
128    /// Create a new agent with multiple parties.
129    pub fn new_for_parties(id: &str, role: &str, for_parties: Vec<String>) -> Self {
130        Self {
131            id: id.to_string(),
132            role: Some(role.to_string()),
133            for_parties: ForParties(for_parties),
134            policies: None,
135            metadata: HashMap::new(),
136        }
137    }
138
139    /// Create a new agent without a role.
140    pub fn new_without_role(id: &str, for_party: &str) -> Self {
141        Self {
142            id: id.to_string(),
143            role: None,
144            for_parties: ForParties(vec![for_party.to_string()]),
145            policies: None,
146            metadata: HashMap::new(),
147        }
148    }
149
150    /// Create a new agent with metadata.
151    pub fn with_metadata(
152        id: &str,
153        role: &str,
154        for_party: &str,
155        metadata: HashMap<String, serde_json::Value>,
156    ) -> Self {
157        Self {
158            id: id.to_string(),
159            role: Some(role.to_string()),
160            for_parties: ForParties(vec![for_party.to_string()]),
161            policies: None,
162            metadata,
163        }
164    }
165
166    /// Add policies to this agent.
167    pub fn with_policies(mut self, policies: Vec<Policy>) -> Self {
168        self.policies = Some(policies);
169        self
170    }
171
172    /// Add a single policy to this agent.
173    pub fn add_policy(mut self, policy: Policy) -> Self {
174        match &mut self.policies {
175            Some(policies) => policies.push(policy),
176            None => self.policies = Some(vec![policy]),
177        }
178        self
179    }
180
181    /// Add a metadata field to the agent.
182    pub fn add_metadata(&mut self, key: String, value: serde_json::Value) {
183        self.metadata.insert(key, value);
184    }
185
186    /// Add metadata using the builder pattern.
187    pub fn with_metadata_field(mut self, key: String, value: serde_json::Value) -> Self {
188        self.metadata.insert(key, value);
189        self
190    }
191
192    /// Get a metadata value by key.
193    pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
194        self.metadata.get(key)
195    }
196
197    /// Check if this agent has a specific role.
198    pub fn has_role(&self, role: &str) -> bool {
199        self.role.as_ref().is_some_and(|r| r == role)
200    }
201
202    /// Check if this agent acts for a specific party.
203    pub fn acts_for(&self, party_id: &str) -> bool {
204        self.for_parties.0.contains(&party_id.to_string())
205    }
206
207    /// Get all parties this agent acts for.
208    pub fn for_parties(&self) -> &[String] {
209        &self.for_parties.0
210    }
211
212    /// Get the first party this agent acts for (for backward compatibility).
213    pub fn primary_party(&self) -> Option<&str> {
214        self.for_parties.0.first().map(|s| s.as_str())
215    }
216
217    /// Add a party this agent acts for.
218    pub fn add_for_party(&mut self, party_id: &str) {
219        if !self.for_parties.0.contains(&party_id.to_string()) {
220            self.for_parties.0.push(party_id.to_string());
221        }
222    }
223
224    /// Set all parties this agent acts for.
225    pub fn set_for_parties(&mut self, parties: Vec<String>) {
226        self.for_parties.0 = parties;
227    }
228
229    // Schema.org Organization field accessors and builders
230
231    /// Add a name field (schema.org/Organization).
232    pub fn with_name(mut self, name: &str) -> Self {
233        self.metadata.insert(
234            "name".to_string(),
235            serde_json::Value::String(name.to_string()),
236        );
237        self
238    }
239
240    /// Get the name field if present.
241    pub fn name(&self) -> Option<&str> {
242        self.metadata.get("name").and_then(|v| v.as_str())
243    }
244
245    /// Add a URL field (schema.org/Organization).
246    pub fn with_url(mut self, url: &str) -> Self {
247        self.metadata.insert(
248            "url".to_string(),
249            serde_json::Value::String(url.to_string()),
250        );
251        self
252    }
253
254    /// Get the URL field if present.
255    pub fn url(&self) -> Option<&str> {
256        self.metadata.get("url").and_then(|v| v.as_str())
257    }
258
259    /// Add a logo field (schema.org/Organization).
260    pub fn with_logo(mut self, logo: &str) -> Self {
261        self.metadata.insert(
262            "logo".to_string(),
263            serde_json::Value::String(logo.to_string()),
264        );
265        self
266    }
267
268    /// Get the logo field if present.
269    pub fn logo(&self) -> Option<&str> {
270        self.metadata.get("logo").and_then(|v| v.as_str())
271    }
272
273    /// Add a description field (schema.org/Organization).
274    pub fn with_description(mut self, description: &str) -> Self {
275        self.metadata.insert(
276            "description".to_string(),
277            serde_json::Value::String(description.to_string()),
278        );
279        self
280    }
281
282    /// Get the description field if present.
283    pub fn description(&self) -> Option<&str> {
284        self.metadata.get("description").and_then(|v| v.as_str())
285    }
286
287    /// Add an email field (schema.org/Organization).
288    pub fn with_email(mut self, email: &str) -> Self {
289        self.metadata.insert(
290            "email".to_string(),
291            serde_json::Value::String(email.to_string()),
292        );
293        self
294    }
295
296    /// Get the email field if present.
297    pub fn email(&self) -> Option<&str> {
298        self.metadata.get("email").and_then(|v| v.as_str())
299    }
300
301    /// Add a telephone field (schema.org/Organization).
302    pub fn with_telephone(mut self, telephone: &str) -> Self {
303        self.metadata.insert(
304            "telephone".to_string(),
305            serde_json::Value::String(telephone.to_string()),
306        );
307        self
308    }
309
310    /// Get the telephone field if present.
311    pub fn telephone(&self) -> Option<&str> {
312        self.metadata.get("telephone").and_then(|v| v.as_str())
313    }
314
315    /// Add a serviceUrl field for DIDComm endpoint fallback (TAIP-5).
316    pub fn with_service_url(mut self, service_url: &str) -> Self {
317        self.metadata.insert(
318            "serviceUrl".to_string(),
319            serde_json::Value::String(service_url.to_string()),
320        );
321        self
322    }
323
324    /// Get the serviceUrl field if present.
325    pub fn service_url(&self) -> Option<&str> {
326        self.metadata.get("serviceUrl").and_then(|v| v.as_str())
327    }
328}
329
330/// Common agent roles used in TAP transactions.
331pub mod roles {
332    /// Settlement address role for blockchain transactions.
333    pub const SETTLEMENT_ADDRESS: &str = "SettlementAddress";
334
335    /// Source address role for originating transactions.
336    pub const SOURCE_ADDRESS: &str = "SourceAddress";
337
338    /// Custodial service role.
339    pub const CUSTODIAL_SERVICE: &str = "CustodialService";
340
341    /// Wallet service role.
342    pub const WALLET_SERVICE: &str = "WalletService";
343
344    /// Exchange service role.
345    pub const EXCHANGE: &str = "Exchange";
346
347    /// Bridge service role for cross-chain transactions.
348    pub const BRIDGE: &str = "Bridge";
349
350    /// DeFi protocol role.
351    pub const DEFI_PROTOCOL: &str = "DeFiProtocol";
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357    use serde_json;
358
359    #[test]
360    fn test_agent_creation() {
361        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
362
363        assert_eq!(agent.id, "did:web:example.com");
364        assert_eq!(agent.role, Some("Exchange".to_string()));
365        assert_eq!(agent.for_parties.0, vec!["did:example:alice"]);
366        assert!(agent.policies.is_none());
367        assert!(agent.metadata.is_empty());
368    }
369
370    #[test]
371    fn test_agent_with_metadata() {
372        let mut metadata = HashMap::new();
373        metadata.insert(
374            "name".to_string(),
375            serde_json::Value::String("Example Exchange".to_string()),
376        );
377
378        let agent = Agent::with_metadata(
379            "did:web:example.com",
380            "Exchange",
381            "did:example:alice",
382            metadata,
383        );
384
385        assert_eq!(
386            agent.get_metadata("name").unwrap().as_str().unwrap(),
387            "Example Exchange"
388        );
389    }
390
391    #[test]
392    fn test_agent_with_policies() {
393        use crate::message::policy::{Policy, RequireAuthorization};
394
395        let auth_req = RequireAuthorization {
396            from: Some(vec!["did:example:kyc".to_string()]),
397            from_role: None,
398            from_agent: None,
399            purpose: Some("KYC verification".to_string()),
400        };
401        let policy = Policy::RequireAuthorization(auth_req);
402
403        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice")
404            .with_policies(vec![policy]);
405
406        assert!(agent.policies.is_some());
407        assert_eq!(agent.policies.as_ref().unwrap().len(), 1);
408    }
409
410    #[test]
411    fn test_agent_serialization() {
412        let agent = Agent::new(
413            "did:web:example.com",
414            "SettlementAddress",
415            "did:example:alice",
416        )
417        .with_metadata_field(
418            "name".to_string(),
419            serde_json::Value::String("Test Agent".to_string()),
420        );
421
422        let json = serde_json::to_string(&agent).unwrap();
423        let deserialized: Agent = serde_json::from_str(&json).unwrap();
424
425        assert_eq!(agent, deserialized);
426        assert_eq!(deserialized.role, Some("SettlementAddress".to_string()));
427        assert_eq!(deserialized.for_parties.0, vec!["did:example:alice"]);
428    }
429
430    #[test]
431    fn test_agent_json_ld_format() {
432        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
433        let json = serde_json::to_value(&agent).unwrap();
434
435        assert_eq!(json["@id"], "did:web:example.com");
436        assert_eq!(json["role"], "Exchange");
437        assert_eq!(json["for"], "did:example:alice"); // Should serialize as string for single party
438    }
439
440    #[test]
441    fn test_agent_helper_methods() {
442        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
443
444        assert!(agent.has_role("Exchange"));
445        assert!(!agent.has_role("Wallet"));
446        assert!(agent.acts_for("did:example:alice"));
447        assert!(!agent.acts_for("did:example:bob"));
448    }
449
450    #[test]
451    fn test_agent_roles_constants() {
452        assert_eq!(roles::SETTLEMENT_ADDRESS, "SettlementAddress");
453        assert_eq!(roles::SOURCE_ADDRESS, "SourceAddress");
454        assert_eq!(roles::EXCHANGE, "Exchange");
455    }
456
457    #[test]
458    fn test_agent_multiple_parties() {
459        let parties = vec![
460            "did:example:alice".to_string(),
461            "did:example:bob".to_string(),
462        ];
463        let agent = Agent::new_for_parties("did:web:example.com", "Exchange", parties.clone());
464
465        assert_eq!(agent.for_parties.0, parties);
466        assert!(agent.acts_for("did:example:alice"));
467        assert!(agent.acts_for("did:example:bob"));
468        assert!(!agent.acts_for("did:example:charlie"));
469    }
470
471    #[test]
472    fn test_agent_for_parties_serialization_single() {
473        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
474        let json = serde_json::to_value(&agent).unwrap();
475
476        // Single party should serialize as string
477        assert_eq!(json["for"], "did:example:alice");
478
479        // Test deserialization
480        let deserialized: Agent = serde_json::from_value(json).unwrap();
481        assert_eq!(deserialized.for_parties.0, vec!["did:example:alice"]);
482    }
483
484    #[test]
485    fn test_agent_for_parties_serialization_multiple() {
486        let parties = vec![
487            "did:example:alice".to_string(),
488            "did:example:bob".to_string(),
489        ];
490        let agent = Agent::new_for_parties("did:web:example.com", "Exchange", parties.clone());
491        let json = serde_json::to_value(&agent).unwrap();
492
493        // Multiple parties should serialize as array
494        assert_eq!(
495            json["for"],
496            serde_json::Value::Array(vec![
497                serde_json::Value::String("did:example:alice".to_string()),
498                serde_json::Value::String("did:example:bob".to_string())
499            ])
500        );
501
502        // Test deserialization
503        let deserialized: Agent = serde_json::from_value(json).unwrap();
504        assert_eq!(deserialized.for_parties.0, parties);
505    }
506
507    #[test]
508    fn test_agent_for_parties_deserialization_from_string() {
509        let json = serde_json::json!({
510            "@id": "did:web:example.com",
511            "role": "Exchange",
512            "for": "did:example:alice"
513        });
514
515        let agent: Agent = serde_json::from_value(json).unwrap();
516        assert_eq!(agent.for_parties.0, vec!["did:example:alice"]);
517    }
518
519    #[test]
520    fn test_agent_for_parties_deserialization_from_array() {
521        let json = serde_json::json!({
522            "@id": "did:web:example.com",
523            "role": "Exchange",
524            "for": ["did:example:alice", "did:example:bob"]
525        });
526
527        let agent: Agent = serde_json::from_value(json).unwrap();
528        assert_eq!(
529            agent.for_parties.0,
530            vec!["did:example:alice", "did:example:bob"]
531        );
532    }
533
534    #[test]
535    fn test_agent_for_parties_methods() {
536        let mut agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
537
538        assert_eq!(agent.for_parties(), &["did:example:alice"]);
539        assert_eq!(agent.primary_party(), Some("did:example:alice"));
540
541        agent.add_for_party("did:example:bob");
542        assert_eq!(
543            agent.for_parties(),
544            &["did:example:alice", "did:example:bob"]
545        );
546
547        agent.set_for_parties(vec!["did:example:charlie".to_string()]);
548        assert_eq!(agent.for_parties(), &["did:example:charlie"]);
549        assert_eq!(agent.primary_party(), Some("did:example:charlie"));
550    }
551
552    // Schema.org Organization field tests
553
554    #[test]
555    fn test_agent_with_name_field() {
556        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice")
557            .with_name("Example Exchange Inc.");
558
559        assert_eq!(agent.name(), Some("Example Exchange Inc."));
560
561        // Test serialization
562        let json = serde_json::to_value(&agent).unwrap();
563        assert_eq!(json["name"], "Example Exchange Inc.");
564
565        // Test deserialization
566        let deserialized: Agent = serde_json::from_value(json).unwrap();
567        assert_eq!(deserialized.name(), Some("Example Exchange Inc."));
568    }
569
570    #[test]
571    fn test_agent_with_url_field() {
572        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice")
573            .with_url("https://example.com");
574
575        assert_eq!(agent.url(), Some("https://example.com"));
576
577        let json = serde_json::to_value(&agent).unwrap();
578        assert_eq!(json["url"], "https://example.com");
579    }
580
581    #[test]
582    fn test_agent_with_logo_field() {
583        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice")
584            .with_logo("https://example.com/logo.png");
585
586        assert_eq!(agent.logo(), Some("https://example.com/logo.png"));
587
588        let json = serde_json::to_value(&agent).unwrap();
589        assert_eq!(json["logo"], "https://example.com/logo.png");
590    }
591
592    #[test]
593    fn test_agent_with_description_field() {
594        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice")
595            .with_description("A leading cryptocurrency exchange");
596
597        assert_eq!(
598            agent.description(),
599            Some("A leading cryptocurrency exchange")
600        );
601
602        let json = serde_json::to_value(&agent).unwrap();
603        assert_eq!(json["description"], "A leading cryptocurrency exchange");
604    }
605
606    #[test]
607    fn test_agent_with_email_field() {
608        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice")
609            .with_email("support@example.com");
610
611        assert_eq!(agent.email(), Some("support@example.com"));
612
613        let json = serde_json::to_value(&agent).unwrap();
614        assert_eq!(json["email"], "support@example.com");
615    }
616
617    #[test]
618    fn test_agent_with_telephone_field() {
619        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice")
620            .with_telephone("+1-555-0100");
621
622        assert_eq!(agent.telephone(), Some("+1-555-0100"));
623
624        let json = serde_json::to_value(&agent).unwrap();
625        assert_eq!(json["telephone"], "+1-555-0100");
626    }
627
628    #[test]
629    fn test_agent_with_service_url_field() {
630        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice")
631            .with_service_url("https://example.com/didcomm");
632
633        assert_eq!(agent.service_url(), Some("https://example.com/didcomm"));
634
635        let json = serde_json::to_value(&agent).unwrap();
636        assert_eq!(json["serviceUrl"], "https://example.com/didcomm");
637    }
638
639    #[test]
640    fn test_agent_with_multiple_organization_fields() {
641        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice")
642            .with_name("Example Exchange Inc.")
643            .with_url("https://example.com")
644            .with_logo("https://example.com/logo.png")
645            .with_description("A leading cryptocurrency exchange")
646            .with_email("support@example.com")
647            .with_telephone("+1-555-0100")
648            .with_service_url("https://example.com/didcomm");
649
650        assert_eq!(agent.name(), Some("Example Exchange Inc."));
651        assert_eq!(agent.url(), Some("https://example.com"));
652        assert_eq!(agent.logo(), Some("https://example.com/logo.png"));
653        assert_eq!(
654            agent.description(),
655            Some("A leading cryptocurrency exchange")
656        );
657        assert_eq!(agent.email(), Some("support@example.com"));
658        assert_eq!(agent.telephone(), Some("+1-555-0100"));
659        assert_eq!(agent.service_url(), Some("https://example.com/didcomm"));
660
661        // Test JSON serialization includes all fields
662        let json = serde_json::to_value(&agent).unwrap();
663        assert_eq!(json["@id"], "did:web:example.com");
664        assert_eq!(json["role"], "Exchange");
665        assert_eq!(json["for"], "did:example:alice");
666        assert_eq!(json["name"], "Example Exchange Inc.");
667        assert_eq!(json["url"], "https://example.com");
668        assert_eq!(json["logo"], "https://example.com/logo.png");
669        assert_eq!(json["description"], "A leading cryptocurrency exchange");
670        assert_eq!(json["email"], "support@example.com");
671        assert_eq!(json["telephone"], "+1-555-0100");
672        assert_eq!(json["serviceUrl"], "https://example.com/didcomm");
673
674        // Test deserialization preserves all fields
675        let deserialized: Agent = serde_json::from_value(json).unwrap();
676        assert_eq!(deserialized.name(), Some("Example Exchange Inc."));
677        assert_eq!(deserialized.url(), Some("https://example.com"));
678        assert_eq!(
679            deserialized.service_url(),
680            Some("https://example.com/didcomm")
681        );
682    }
683
684    #[test]
685    fn test_agent_json_ld_compliance_with_organization_fields() {
686        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice")
687            .with_name("Example Exchange")
688            .with_metadata_field(
689                "lei:leiCode".to_string(),
690                serde_json::Value::String("123456789012345678".to_string()),
691            );
692
693        let json = serde_json::to_value(&agent).unwrap();
694
695        // Verify JSON-LD structure
696        assert_eq!(json["@id"], "did:web:example.com");
697        assert_eq!(json["name"], "Example Exchange");
698        assert_eq!(json["lei:leiCode"], "123456789012345678");
699
700        // Fields should be at root level, not nested
701        assert!(json.get("metadata").is_none());
702    }
703}