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
230/// Common agent roles used in TAP transactions.
231pub mod roles {
232    /// Settlement address role for blockchain transactions.
233    pub const SETTLEMENT_ADDRESS: &str = "SettlementAddress";
234
235    /// Source address role for originating transactions.
236    pub const SOURCE_ADDRESS: &str = "SourceAddress";
237
238    /// Custodial service role.
239    pub const CUSTODIAL_SERVICE: &str = "CustodialService";
240
241    /// Wallet service role.
242    pub const WALLET_SERVICE: &str = "WalletService";
243
244    /// Exchange service role.
245    pub const EXCHANGE: &str = "Exchange";
246
247    /// Bridge service role for cross-chain transactions.
248    pub const BRIDGE: &str = "Bridge";
249
250    /// DeFi protocol role.
251    pub const DEFI_PROTOCOL: &str = "DeFiProtocol";
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257    use serde_json;
258
259    #[test]
260    fn test_agent_creation() {
261        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
262
263        assert_eq!(agent.id, "did:web:example.com");
264        assert_eq!(agent.role, Some("Exchange".to_string()));
265        assert_eq!(agent.for_parties.0, vec!["did:example:alice"]);
266        assert!(agent.policies.is_none());
267        assert!(agent.metadata.is_empty());
268    }
269
270    #[test]
271    fn test_agent_with_metadata() {
272        let mut metadata = HashMap::new();
273        metadata.insert(
274            "name".to_string(),
275            serde_json::Value::String("Example Exchange".to_string()),
276        );
277
278        let agent = Agent::with_metadata(
279            "did:web:example.com",
280            "Exchange",
281            "did:example:alice",
282            metadata,
283        );
284
285        assert_eq!(
286            agent.get_metadata("name").unwrap().as_str().unwrap(),
287            "Example Exchange"
288        );
289    }
290
291    #[test]
292    fn test_agent_with_policies() {
293        use crate::message::policy::{Policy, RequireAuthorization};
294
295        let auth_req = RequireAuthorization {
296            from: Some(vec!["did:example:kyc".to_string()]),
297            from_role: None,
298            from_agent: None,
299            purpose: Some("KYC verification".to_string()),
300        };
301        let policy = Policy::RequireAuthorization(auth_req);
302
303        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice")
304            .with_policies(vec![policy]);
305
306        assert!(agent.policies.is_some());
307        assert_eq!(agent.policies.as_ref().unwrap().len(), 1);
308    }
309
310    #[test]
311    fn test_agent_serialization() {
312        let agent = Agent::new(
313            "did:web:example.com",
314            "SettlementAddress",
315            "did:example:alice",
316        )
317        .with_metadata_field(
318            "name".to_string(),
319            serde_json::Value::String("Test Agent".to_string()),
320        );
321
322        let json = serde_json::to_string(&agent).unwrap();
323        let deserialized: Agent = serde_json::from_str(&json).unwrap();
324
325        assert_eq!(agent, deserialized);
326        assert_eq!(deserialized.role, Some("SettlementAddress".to_string()));
327        assert_eq!(deserialized.for_parties.0, vec!["did:example:alice"]);
328    }
329
330    #[test]
331    fn test_agent_json_ld_format() {
332        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
333        let json = serde_json::to_value(&agent).unwrap();
334
335        assert_eq!(json["@id"], "did:web:example.com");
336        assert_eq!(json["role"], "Exchange");
337        assert_eq!(json["for"], "did:example:alice"); // Should serialize as string for single party
338    }
339
340    #[test]
341    fn test_agent_helper_methods() {
342        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
343
344        assert!(agent.has_role("Exchange"));
345        assert!(!agent.has_role("Wallet"));
346        assert!(agent.acts_for("did:example:alice"));
347        assert!(!agent.acts_for("did:example:bob"));
348    }
349
350    #[test]
351    fn test_agent_roles_constants() {
352        assert_eq!(roles::SETTLEMENT_ADDRESS, "SettlementAddress");
353        assert_eq!(roles::SOURCE_ADDRESS, "SourceAddress");
354        assert_eq!(roles::EXCHANGE, "Exchange");
355    }
356
357    #[test]
358    fn test_agent_multiple_parties() {
359        let parties = vec![
360            "did:example:alice".to_string(),
361            "did:example:bob".to_string(),
362        ];
363        let agent = Agent::new_for_parties("did:web:example.com", "Exchange", parties.clone());
364
365        assert_eq!(agent.for_parties.0, parties);
366        assert!(agent.acts_for("did:example:alice"));
367        assert!(agent.acts_for("did:example:bob"));
368        assert!(!agent.acts_for("did:example:charlie"));
369    }
370
371    #[test]
372    fn test_agent_for_parties_serialization_single() {
373        let agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
374        let json = serde_json::to_value(&agent).unwrap();
375
376        // Single party should serialize as string
377        assert_eq!(json["for"], "did:example:alice");
378
379        // Test deserialization
380        let deserialized: Agent = serde_json::from_value(json).unwrap();
381        assert_eq!(deserialized.for_parties.0, vec!["did:example:alice"]);
382    }
383
384    #[test]
385    fn test_agent_for_parties_serialization_multiple() {
386        let parties = vec![
387            "did:example:alice".to_string(),
388            "did:example:bob".to_string(),
389        ];
390        let agent = Agent::new_for_parties("did:web:example.com", "Exchange", parties.clone());
391        let json = serde_json::to_value(&agent).unwrap();
392
393        // Multiple parties should serialize as array
394        assert_eq!(
395            json["for"],
396            serde_json::Value::Array(vec![
397                serde_json::Value::String("did:example:alice".to_string()),
398                serde_json::Value::String("did:example:bob".to_string())
399            ])
400        );
401
402        // Test deserialization
403        let deserialized: Agent = serde_json::from_value(json).unwrap();
404        assert_eq!(deserialized.for_parties.0, parties);
405    }
406
407    #[test]
408    fn test_agent_for_parties_deserialization_from_string() {
409        let json = serde_json::json!({
410            "@id": "did:web:example.com",
411            "role": "Exchange",
412            "for": "did:example:alice"
413        });
414
415        let agent: Agent = serde_json::from_value(json).unwrap();
416        assert_eq!(agent.for_parties.0, vec!["did:example:alice"]);
417    }
418
419    #[test]
420    fn test_agent_for_parties_deserialization_from_array() {
421        let json = serde_json::json!({
422            "@id": "did:web:example.com",
423            "role": "Exchange",
424            "for": ["did:example:alice", "did:example:bob"]
425        });
426
427        let agent: Agent = serde_json::from_value(json).unwrap();
428        assert_eq!(
429            agent.for_parties.0,
430            vec!["did:example:alice", "did:example:bob"]
431        );
432    }
433
434    #[test]
435    fn test_agent_for_parties_methods() {
436        let mut agent = Agent::new("did:web:example.com", "Exchange", "did:example:alice");
437
438        assert_eq!(agent.for_parties(), &["did:example:alice"]);
439        assert_eq!(agent.primary_party(), Some("did:example:alice"));
440
441        agent.add_for_party("did:example:bob");
442        assert_eq!(
443            agent.for_parties(),
444            &["did:example:alice", "did:example:bob"]
445        );
446
447        agent.set_for_parties(vec!["did:example:charlie".to_string()]);
448        assert_eq!(agent.for_parties(), &["did:example:charlie"]);
449        assert_eq!(agent.primary_party(), Some("did:example:charlie"));
450    }
451}