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 (REQUIRED per TAIP-5).
90    /// Examples: "SettlementAddress", "SourceAddress", etc.
91    pub role: String,
92
93    /// DID or IRI of another Agent or Party that this agent acts on behalf of (REQUIRED per TAIP-5).
94    /// Can be a single party or multiple parties.
95    #[serde(rename = "for")]
96    pub for_parties: ForParties,
97
98    /// Policies of the agent according to TAIP-7 (optional).
99    #[serde(skip_serializing_if = "Option::is_none")]
100    #[serde(default)]
101    pub policies: Option<Vec<Policy>>,
102
103    /// Additional JSON-LD metadata for the agent.
104    /// This allows for extensible metadata beyond the core fields.
105    #[serde(flatten)]
106    pub metadata: HashMap<String, serde_json::Value>,
107}
108
109impl TapParticipant for Agent {
110    fn id(&self) -> &str {
111        &self.id
112    }
113}
114
115impl Agent {
116    /// Create a new agent with the given DID, role, and for_party.
117    pub fn new(id: &str, role: &str, for_party: &str) -> Self {
118        Self {
119            id: id.to_string(),
120            role: role.to_string(),
121            for_parties: ForParties(vec![for_party.to_string()]),
122            policies: None,
123            metadata: HashMap::new(),
124        }
125    }
126
127    /// Create a new agent with multiple parties.
128    pub fn new_for_parties(id: &str, role: &str, for_parties: Vec<String>) -> Self {
129        Self {
130            id: id.to_string(),
131            role: role.to_string(),
132            for_parties: ForParties(for_parties),
133            policies: None,
134            metadata: HashMap::new(),
135        }
136    }
137
138    /// Create a new agent with metadata.
139    pub fn with_metadata(
140        id: &str,
141        role: &str,
142        for_party: &str,
143        metadata: HashMap<String, serde_json::Value>,
144    ) -> Self {
145        Self {
146            id: id.to_string(),
147            role: role.to_string(),
148            for_parties: ForParties(vec![for_party.to_string()]),
149            policies: None,
150            metadata,
151        }
152    }
153
154    /// Add policies to this agent.
155    pub fn with_policies(mut self, policies: Vec<Policy>) -> Self {
156        self.policies = Some(policies);
157        self
158    }
159
160    /// Add a single policy to this agent.
161    pub fn add_policy(mut self, policy: Policy) -> Self {
162        match &mut self.policies {
163            Some(policies) => policies.push(policy),
164            None => self.policies = Some(vec![policy]),
165        }
166        self
167    }
168
169    /// Add a metadata field to the agent.
170    pub fn add_metadata(&mut self, key: String, value: serde_json::Value) {
171        self.metadata.insert(key, value);
172    }
173
174    /// Add metadata using the builder pattern.
175    pub fn with_metadata_field(mut self, key: String, value: serde_json::Value) -> Self {
176        self.metadata.insert(key, value);
177        self
178    }
179
180    /// Get a metadata value by key.
181    pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
182        self.metadata.get(key)
183    }
184
185    /// Check if this agent has a specific role.
186    pub fn has_role(&self, role: &str) -> bool {
187        self.role == role
188    }
189
190    /// Check if this agent acts for a specific party.
191    pub fn acts_for(&self, party_id: &str) -> bool {
192        self.for_parties.0.contains(&party_id.to_string())
193    }
194
195    /// Get all parties this agent acts for.
196    pub fn for_parties(&self) -> &[String] {
197        &self.for_parties.0
198    }
199
200    /// Get the first party this agent acts for (for backward compatibility).
201    pub fn primary_party(&self) -> Option<&str> {
202        self.for_parties.0.first().map(|s| s.as_str())
203    }
204
205    /// Add a party this agent acts for.
206    pub fn add_for_party(&mut self, party_id: &str) {
207        if !self.for_parties.0.contains(&party_id.to_string()) {
208            self.for_parties.0.push(party_id.to_string());
209        }
210    }
211
212    /// Set all parties this agent acts for.
213    pub fn set_for_parties(&mut self, parties: Vec<String>) {
214        self.for_parties.0 = parties;
215    }
216}
217
218/// Common agent roles used in TAP transactions.
219pub mod roles {
220    /// Settlement address role for blockchain transactions.
221    pub const SETTLEMENT_ADDRESS: &str = "SettlementAddress";
222
223    /// Source address role for originating transactions.
224    pub const SOURCE_ADDRESS: &str = "SourceAddress";
225
226    /// Custodial service role.
227    pub const CUSTODIAL_SERVICE: &str = "CustodialService";
228
229    /// Wallet service role.
230    pub const WALLET_SERVICE: &str = "WalletService";
231
232    /// Exchange service role.
233    pub const EXCHANGE: &str = "Exchange";
234
235    /// Bridge service role for cross-chain transactions.
236    pub const BRIDGE: &str = "Bridge";
237
238    /// DeFi protocol role.
239    pub const DEFI_PROTOCOL: &str = "DeFiProtocol";
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245    use serde_json;
246
247    #[test]
248    fn test_agent_creation() {
249        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
250
251        assert_eq!(agent.id, "did:web:example.com");
252        assert_eq!(agent.role, "Exchange");
253        assert_eq!(agent.for_parties.0, vec!["did:example:alice"]);
254        assert!(agent.policies.is_none());
255        assert!(agent.metadata.is_empty());
256    }
257
258    #[test]
259    fn test_agent_with_metadata() {
260        let mut metadata = HashMap::new();
261        metadata.insert(
262            "name".to_string(),
263            serde_json::Value::String("Example Exchange".to_string()),
264        );
265
266        let agent = Agent::with_metadata(
267            "did:web:example.com",
268            "Exchange",
269            "did:example:alice",
270            metadata,
271        );
272
273        assert_eq!(
274            agent.get_metadata("name").unwrap().as_str().unwrap(),
275            "Example Exchange"
276        );
277    }
278
279    #[test]
280    fn test_agent_with_policies() {
281        use crate::message::policy::{Policy, RequireAuthorization};
282
283        let auth_req = RequireAuthorization {
284            from: Some(vec!["did:example:kyc".to_string()]),
285            from_role: None,
286            from_agent: None,
287            purpose: Some("KYC verification".to_string()),
288        };
289        let policy = Policy::RequireAuthorization(auth_req);
290
291        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice")
292            .with_policies(vec![policy]);
293
294        assert!(agent.policies.is_some());
295        assert_eq!(agent.policies.as_ref().unwrap().len(), 1);
296    }
297
298    #[test]
299    fn test_agent_serialization() {
300        let agent = Agent::new(
301            "did:web:example.com",
302            "SettlementAddress",
303            "did:example:alice",
304        )
305        .with_metadata_field(
306            "name".to_string(),
307            serde_json::Value::String("Test Agent".to_string()),
308        );
309
310        let json = serde_json::to_string(&agent).unwrap();
311        let deserialized: Agent = serde_json::from_str(&json).unwrap();
312
313        assert_eq!(agent, deserialized);
314        assert_eq!(deserialized.role, "SettlementAddress");
315        assert_eq!(deserialized.for_parties.0, vec!["did:example:alice"]);
316    }
317
318    #[test]
319    fn test_agent_json_ld_format() {
320        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
321        let json = serde_json::to_value(&agent).unwrap();
322
323        assert_eq!(json["@id"], "did:web:example.com");
324        assert_eq!(json["role"], "Exchange");
325        assert_eq!(json["for"], "did:example:alice"); // Should serialize as string for single party
326    }
327
328    #[test]
329    fn test_agent_helper_methods() {
330        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
331
332        assert!(agent.has_role("Exchange"));
333        assert!(!agent.has_role("Wallet"));
334        assert!(agent.acts_for("did:example:alice"));
335        assert!(!agent.acts_for("did:example:bob"));
336    }
337
338    #[test]
339    fn test_agent_roles_constants() {
340        assert_eq!(roles::SETTLEMENT_ADDRESS, "SettlementAddress");
341        assert_eq!(roles::SOURCE_ADDRESS, "SourceAddress");
342        assert_eq!(roles::EXCHANGE, "Exchange");
343    }
344
345    #[test]
346    fn test_agent_multiple_parties() {
347        let parties = vec![
348            "did:example:alice".to_string(),
349            "did:example:bob".to_string(),
350        ];
351        let agent = Agent::new_for_parties("did:web:example.com", "Exchange", parties.clone());
352
353        assert_eq!(agent.for_parties.0, parties);
354        assert!(agent.acts_for("did:example:alice"));
355        assert!(agent.acts_for("did:example:bob"));
356        assert!(!agent.acts_for("did:example:charlie"));
357    }
358
359    #[test]
360    fn test_agent_for_parties_serialization_single() {
361        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
362        let json = serde_json::to_value(&agent).unwrap();
363
364        // Single party should serialize as string
365        assert_eq!(json["for"], "did:example:alice");
366
367        // Test deserialization
368        let deserialized: Agent = serde_json::from_value(json).unwrap();
369        assert_eq!(deserialized.for_parties.0, vec!["did:example:alice"]);
370    }
371
372    #[test]
373    fn test_agent_for_parties_serialization_multiple() {
374        let parties = vec![
375            "did:example:alice".to_string(),
376            "did:example:bob".to_string(),
377        ];
378        let agent = Agent::new_for_parties("did:web:example.com", "Exchange", parties.clone());
379        let json = serde_json::to_value(&agent).unwrap();
380
381        // Multiple parties should serialize as array
382        assert_eq!(
383            json["for"],
384            serde_json::Value::Array(vec![
385                serde_json::Value::String("did:example:alice".to_string()),
386                serde_json::Value::String("did:example:bob".to_string())
387            ])
388        );
389
390        // Test deserialization
391        let deserialized: Agent = serde_json::from_value(json).unwrap();
392        assert_eq!(deserialized.for_parties.0, parties);
393    }
394
395    #[test]
396    fn test_agent_for_parties_deserialization_from_string() {
397        let json = serde_json::json!({
398            "@id": "did:web:example.com",
399            "role": "Exchange",
400            "for": "did:example:alice"
401        });
402
403        let agent: Agent = serde_json::from_value(json).unwrap();
404        assert_eq!(agent.for_parties.0, vec!["did:example:alice"]);
405    }
406
407    #[test]
408    fn test_agent_for_parties_deserialization_from_array() {
409        let json = serde_json::json!({
410            "@id": "did:web:example.com",
411            "role": "Exchange",
412            "for": ["did:example:alice", "did:example:bob"]
413        });
414
415        let agent: Agent = serde_json::from_value(json).unwrap();
416        assert_eq!(
417            agent.for_parties.0,
418            vec!["did:example:alice", "did:example:bob"]
419        );
420    }
421
422    #[test]
423    fn test_agent_for_parties_methods() {
424        let mut agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
425
426        assert_eq!(agent.for_parties(), &["did:example:alice"]);
427        assert_eq!(agent.primary_party(), Some("did:example:alice"));
428
429        agent.add_for_party("did:example:bob");
430        assert_eq!(
431            agent.for_parties(),
432            &["did:example:alice", "did:example:bob"]
433        );
434
435        agent.set_for_parties(vec!["did:example:charlie".to_string()]);
436        assert_eq!(agent.for_parties(), &["did:example:charlie"]);
437        assert_eq!(agent.primary_party(), Some("did:example:charlie"));
438    }
439}