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