tap_msg/examples/
thread_examples.rs

1//! Examples for thread management and messaging workflows.
2
3use crate::error::{Error, Result};
4use crate::message::policy::{Policy, RequireProofOfControl};
5use crate::message::tap_message_trait::TapMessageBody;
6use crate::message::types::{
7    AddAgents, Authorizable, Authorize, Participant, RemoveAgent, ReplaceAgent, Settle, Transfer,
8};
9
10use didcomm::Message;
11use serde_json;
12use std::collections::HashMap;
13use std::str::FromStr;
14use tap_caip::AssetId;
15
16/// This example demonstrates how to create a reply to a Transfer message
17pub fn create_reply_to_transfer_example() -> Result<Message> {
18    let alice_did = "did:example:alice";
19    let bob_did = "did:example:bob";
20
21    // Create a Transfer message
22    let transfer = Transfer {
23        transaction_id: uuid::Uuid::new_v4().to_string(),
24        asset: AssetId::from_str("eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
25            .unwrap(),
26        originator: Participant {
27            id: alice_did.to_string(),
28            role: Some("originator".to_string()),
29            policies: None,
30            leiCode: None,
31        },
32        beneficiary: Some(Participant {
33            id: bob_did.to_string(),
34            role: Some("beneficiary".to_string()),
35            policies: None,
36            leiCode: None,
37        }),
38        amount: "100.00".to_string(),
39        agents: vec![],
40        settlement_id: None,
41        memo: None,
42        metadata: HashMap::new(),
43    };
44
45    // Create the initial transfer message
46    let transfer_message =
47        transfer.to_didcomm_with_route(Some(alice_did), [bob_did].iter().copied())?;
48
49    // Create an Authorize message
50    let authorize = Authorize {
51        transaction_id: transfer_message.id.clone(),
52        note: Some("I authorize this transfer".to_string()),
53    };
54
55    // Create a reply to the transfer message
56    let mut authorize_reply = authorize.to_didcomm(Some(alice_did))?;
57
58    // Set thread ID to maintain conversation
59    authorize_reply.thid = Some(transfer_message.id.clone());
60
61    // Set recipients to all participants except the creator
62    let recipients: Vec<String> = [alice_did, bob_did]
63        .iter()
64        .filter(|&did| **did != *alice_did)
65        .map(|s| s.to_string())
66        .collect();
67
68    authorize_reply.to = Some(recipients);
69
70    Ok(authorize_reply)
71}
72
73/// This example demonstrates how to create a reply using the Message trait extension
74pub fn create_reply_using_message_trait_example(
75    original_message: &Message,
76    creator_did: &str,
77) -> Result<Message> {
78    // Create an Authorize response
79    let authorize = Authorize {
80        transaction_id: original_message.id.clone(),
81        note: Some("Transfer authorized".to_string()),
82    };
83
84    // Create a reply using the Message trait extension
85    // This will automatically gather all participants from the original message
86    let mut response_message = authorize.to_didcomm(Some(creator_did))?;
87
88    // Set thread ID to maintain conversation
89    response_message.thid = original_message.thid.clone();
90
91    // Set recipients to all participants except the creator
92    let recipients: Vec<String> = original_message
93        .to
94        .as_ref()
95        .unwrap()
96        .iter()
97        .filter(|did| **did != creator_did)
98        .map(|s| s.to_string())
99        .collect();
100
101    response_message.to = Some(recipients);
102
103    Ok(response_message)
104}
105
106/// This example demonstrates adding agents using the Authorizable trait
107pub fn create_add_agents_example() -> Result<Message> {
108    let originator_did = "did:example:originator";
109    let beneficiary_did = "did:example:beneficiary";
110    let sender_vasp_did = "did:example:sender_vasp";
111    let receiver_vasp_did = "did:example:receiver_vasp";
112    let new_agent_did = "did:example:new_agent";
113
114    // Create a Transfer
115    let transfer = Transfer {
116        transaction_id: uuid::Uuid::new_v4().to_string(),
117        asset: AssetId::from_str("eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
118            .unwrap(),
119        originator: Participant {
120            id: originator_did.to_string(),
121            role: Some("originator".to_string()),
122            policies: None,
123            leiCode: None,
124        },
125        beneficiary: Some(Participant {
126            id: beneficiary_did.to_string(),
127            role: Some("beneficiary".to_string()),
128            policies: None,
129            leiCode: None,
130        }),
131        amount: "100.00".to_string(),
132        memo: None,
133        agents: vec![
134            Participant {
135                id: sender_vasp_did.to_string(),
136                role: Some("sender_vasp".to_string()),
137                policies: None,
138                leiCode: None,
139            },
140            Participant {
141                id: receiver_vasp_did.to_string(),
142                role: Some("receiver_vasp".to_string()),
143                policies: None,
144                leiCode: None,
145            },
146        ],
147        settlement_id: None,
148        metadata: HashMap::new(),
149    };
150
151    // Create the initial transfer message
152    let transfer_message =
153        transfer.to_didcomm_with_route(Some(originator_did), [beneficiary_did].iter().copied())?;
154
155    // Create an Authorize message first
156    let authorize = Authorize {
157        transaction_id: transfer_message.id.clone(),
158        note: Some("I authorize this transfer".to_string()),
159    };
160
161    // Create a reply from the originator to the beneficiary
162    let mut authorize_message = authorize.to_didcomm(Some(originator_did))?;
163
164    // Set thread ID to maintain conversation
165    authorize_message.thid = Some(transfer_message.id.clone());
166
167    // Set recipients to all participants except the creator
168    let recipients: Vec<String> = [originator_did, beneficiary_did]
169        .iter()
170        .filter(|did| **did != originator_did)
171        .map(|s| s.to_string())
172        .collect();
173
174    authorize_message.to = Some(recipients);
175
176    // Create an AddAgents message
177    let add_agents = AddAgents {
178        transaction_id: transfer_message.id.clone(),
179        agents: vec![Participant {
180            id: new_agent_did.to_string(),
181            role: Some("compliance".to_string()),
182            policies: None,
183            leiCode: None,
184        }],
185    };
186
187    // Create a reply using the TapMessage trait
188    let mut response = add_agents.to_didcomm(Some(originator_did))?;
189
190    // Set thread ID to maintain conversation
191    response.thid = Some(transfer_message.id.clone());
192
193    // Set recipients to all participants except the creator
194    let recipients: Vec<String> = [originator_did, beneficiary_did, new_agent_did]
195        .iter()
196        .filter(|did| **did != originator_did)
197        .map(|s| s.to_string())
198        .collect();
199
200    response.to = Some(recipients);
201
202    Ok(response)
203}
204
205/// This example demonstrates replacing an agent using the Authorizable trait
206pub fn create_replace_agent_example(
207    original_message: &Message,
208    creator_did: &str,
209    original_agent_id: &str,
210    replacement_agent_id: &str,
211    replacement_agent_role: Option<&str>,
212) -> Result<Message> {
213    // Extract body and deserialize to Transfer
214    let original_body_json = original_message.body.clone();
215    let original_transfer: Transfer = serde_json::from_value(original_body_json)
216        .map_err(|e| Error::SerializationError(e.to_string()))?;
217
218    // Create a replacement participant
219    let replacement = Participant {
220        id: replacement_agent_id.to_string(),
221        role: replacement_agent_role.map(ToString::to_string),
222        policies: None, // No policies for this participant
223        leiCode: None,
224    };
225
226    // Call replace_agent on the Transfer instance
227    let replace_agent_body = original_transfer.replace_agent(
228        original_message.id.clone(),
229        original_agent_id.to_string(),
230        replacement,
231    );
232
233    // Create a reply using the TapMessage trait
234    let mut response = replace_agent_body.to_didcomm(Some(creator_did))?;
235
236    // Set thread ID to maintain conversation
237    response.thid = original_message.thid.clone();
238
239    // Set recipients to all participants except the creator
240    let recipients: Vec<String> = original_message
241        .to
242        .as_ref()
243        .unwrap()
244        .iter()
245        .filter(|did| **did != creator_did)
246        .map(|s| s.to_string())
247        .collect();
248
249    response.to = Some(recipients);
250
251    Ok(response)
252}
253
254/// This example demonstrates removing an agent using the Authorizable trait
255pub fn create_remove_agent_example(
256    original_message: &Message,
257    creator_did: &str,
258    agent_to_remove: &str,
259) -> Result<Message> {
260    // Extract body and deserialize to Transfer
261    let original_body_json = original_message.body.clone();
262    let original_transfer: Transfer = serde_json::from_value(original_body_json)
263        .map_err(|e| Error::SerializationError(e.to_string()))?;
264
265    // Call remove_agent on the Transfer instance
266    let remove_agent_body =
267        original_transfer.remove_agent(original_message.id.clone(), agent_to_remove.to_string());
268
269    // Create a reply using the TapMessage trait
270    let mut response = remove_agent_body.to_didcomm(Some(creator_did))?;
271
272    // Set thread ID to maintain conversation
273    response.thid = original_message.thid.clone();
274
275    // Set recipients to all participants except the creator
276    let recipients: Vec<String> = original_message
277        .to
278        .as_ref()
279        .unwrap()
280        .iter()
281        .filter(|did| **did != creator_did)
282        .map(|s| s.to_string())
283        .collect();
284
285    response.to = Some(recipients);
286
287    Ok(response)
288}
289
290/// This example demonstrates creating an UpdatePolicies message using the Authorizable trait
291pub fn create_update_policies_example(
292    original_message: &Message,
293    creator_did: &str,
294    _recipients: &[&str],
295) -> Result<Message> {
296    // Extract body and deserialize to Transfer
297    let original_body_json = original_message.body.clone();
298    let original_transfer: Transfer = serde_json::from_value(original_body_json)
299        .map_err(|e| Error::SerializationError(e.to_string()))?;
300
301    // Create a proof of control policy
302    let proof_policy = RequireProofOfControl {
303        from: Some(vec!["did:example:dave".to_string()]),
304        from_role: None,
305        from_agent: None,
306        address_id: "eip155:1:0x1234567890123456789012345678901234567890".to_string(),
307        purpose: Some("Please prove control of your account".to_string()),
308    };
309
310    // Call update_policies on the Transfer instance
311    let update_policies_body = original_transfer.update_policies(
312        original_message.id.clone(),
313        vec![Policy::RequireProofOfControl(proof_policy)],
314    );
315
316    // Create a reply using the TapMessage trait, which maintains thread correlation
317    let mut response = update_policies_body.to_didcomm(Some(creator_did))?;
318
319    // Set thread ID to maintain conversation
320    response.thid = original_message.thid.clone();
321
322    // Set recipients to all participants except the creator
323    let recipients: Vec<String> = original_message
324        .to
325        .as_ref()
326        .unwrap()
327        .iter()
328        .filter(|did| **did != creator_did)
329        .map(|s| s.to_string())
330        .collect();
331
332    response.to = Some(recipients);
333
334    Ok(response)
335}
336
337/// This example demonstrates creating a Settle message
338///
339/// # Arguments
340///
341/// * `transfer_id` - ID of the transfer to settle
342/// * `settlement_id` - Settlement ID
343/// * `amount` - Optional amount settled
344///
345/// # Returns
346///
347/// A DIDComm message containing the Settle message
348pub fn settle_example(
349    transaction_id: String,
350    settlement_id: String,
351    amount: Option<String>,
352) -> Result<Message> {
353    // Create a Settle message
354    let settle = Settle {
355        transaction_id,
356        settlement_id,
357        amount,
358    };
359
360    // Convert to DIDComm message
361    let settle_message = settle.to_didcomm(Some("did:example:dave"))?;
362
363    Ok(settle_message)
364}
365
366/// This example demonstrates a complete workflow for managing thread participants
367pub fn thread_participant_workflow_example() -> Result<()> {
368    // 1. Let's start with a transfer message
369    let alice_did = "did:example:alice";
370    let bob_did = "did:example:bob";
371    let charlie_did = "did:example:charlie";
372    let dave_did = "did:example:dave";
373
374    // Create a transfer from Alice to Bob
375    let transfer = Transfer {
376        transaction_id: uuid::Uuid::new_v4().to_string(),
377        asset: AssetId::from_str("eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
378            .unwrap(),
379        originator: Participant {
380            id: alice_did.to_string(),
381            role: Some("originator".to_string()),
382            policies: None,
383            leiCode: None,
384        },
385        memo: None,
386        beneficiary: Some(Participant {
387            id: bob_did.to_string(),
388            role: Some("beneficiary".to_string()),
389            policies: None,
390            leiCode: None,
391        }),
392        amount: "10.00".to_string(),
393        agents: vec![],
394        settlement_id: None,
395        metadata: HashMap::new(),
396    };
397
398    // Create the initial transfer message
399    let transfer_message =
400        transfer.to_didcomm_with_route(Some(alice_did), [bob_did].iter().copied())?;
401
402    println!("Created initial transfer message: {:?}", transfer_message);
403
404    // Let's assume we have a unique thread ID
405    let transfer_id = transfer_message.id.clone();
406
407    // 2. Now Bob wants to reply to the transfer
408    let authorize = Authorize {
409        transaction_id: transfer_message.id.clone(),
410        note: Some("Transfer approved".to_string()),
411    };
412
413    // Create a reply from Bob to Alice
414    let mut authorize_message = authorize.to_didcomm(Some(bob_did))?;
415
416    // Set thread ID to maintain conversation
417    authorize_message.thid = Some(transfer_message.id.clone());
418
419    // Set recipients to all participants except the creator
420    let recipients: Vec<String> = [alice_did, bob_did]
421        .iter()
422        .filter(|did| **did != bob_did)
423        .map(|s| s.to_string())
424        .collect();
425
426    authorize_message.to = Some(recipients);
427
428    println!("Created authorize message: {:?}", authorize_message);
429
430    // 3. Now Alice wants to add Charlie to the thread
431    // Create an AddAgents message
432    let add_agents = AddAgents {
433        transaction_id: transfer_id.clone(),
434        agents: vec![Participant {
435            id: charlie_did.to_string(),
436            role: Some("observer".to_string()),
437            policies: None,
438            leiCode: None,
439        }],
440    };
441
442    // Create the add agents message
443    let mut add_agents_message = add_agents.to_didcomm(Some(alice_did))?;
444
445    // Set thread ID to maintain conversation
446    add_agents_message.thid = Some(transfer_id.clone());
447
448    // Set recipients to all participants except the creator
449    let recipients: Vec<String> = [alice_did, bob_did, charlie_did]
450        .iter()
451        .filter(|did| **did != alice_did)
452        .map(|s| s.to_string())
453        .collect();
454
455    add_agents_message.to = Some(recipients);
456
457    println!("Created add agents message: {:?}", add_agents_message);
458
459    // 4. Now Bob wants to replace himself with Dave
460    let replace_agent = ReplaceAgent {
461        transaction_id: transfer_id.clone(),
462        original: bob_did.to_string(),
463        replacement: Participant {
464            id: dave_did.to_string(),
465            role: Some("beneficiary".to_string()),
466            policies: None,
467            leiCode: None,
468        },
469    };
470
471    // Create the replace agent message
472    let mut replace_agent_message = replace_agent.to_didcomm(Some(bob_did))?;
473
474    // Set thread ID to maintain conversation
475    replace_agent_message.thid = Some(transfer_id.clone());
476
477    // Set recipients to all participants except the creator
478    let recipients: Vec<String> = [alice_did, dave_did, charlie_did]
479        .iter()
480        .filter(|did| **did != bob_did)
481        .map(|s| s.to_string())
482        .collect();
483
484    replace_agent_message.to = Some(recipients);
485
486    println!("Created replace agent message: {:?}", replace_agent_message);
487
488    // 5. Alice wants to remove Charlie from the thread
489    let remove_agent = RemoveAgent {
490        transaction_id: transfer_id.clone(),
491        agent: charlie_did.to_string(),
492    };
493
494    // Create the remove agent message
495    let mut remove_agent_message = remove_agent.to_didcomm(Some(alice_did))?;
496
497    // Set thread ID to maintain conversation
498    remove_agent_message.thid = Some(transfer_id.clone());
499
500    // Set recipients to all participants except the creator
501    let recipients: Vec<String> = [alice_did, dave_did, charlie_did]
502        .iter()
503        .filter(|did| **did != alice_did)
504        .map(|s| s.to_string())
505        .collect();
506
507    remove_agent_message.to = Some(recipients);
508
509    println!("Created remove agent message: {:?}", remove_agent_message);
510
511    // 6. Now Dave can settle the transfer
512    println!("Step 5: Settling the transfer");
513    let settle_message = settle_example(
514        transfer_id.clone(),
515        "tx123456".to_string(),
516        Some("100.0".to_string()),
517    )?;
518
519    // Verify that the 'to' field in the settle message includes Alice
520    if let Some(to) = &settle_message.to {
521        assert!(to.contains(&alice_did.to_string()));
522        assert!(!to.contains(&dave_did.to_string())); // Dave is the sender
523        println!("Verified that the settle message is addressed correctly");
524    }
525
526    Ok(())
527}