tap_msg/message/
transfer.rs1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use tap_caip::AssetId;
9
10use crate::error::{Error, Result};
11use crate::message::agent::TapParticipant;
12use crate::message::tap_message_trait::{TapMessage as TapMessageTrait, TapMessageBody};
13use crate::message::{Agent, Party};
14use crate::TapMessage;
15
16#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
18#[tap(
19 message_type = "https://tap.rsvp/schema/1.0#Transfer",
20 initiator,
21 authorizable,
22 transactable
23)]
24pub struct Transfer {
25 pub asset: AssetId,
27
28 #[serde(rename = "originator")]
30 #[tap(participant)]
31 pub originator: Party,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
35 #[tap(participant)]
36 pub beneficiary: Option<Party>,
37
38 pub amount: String,
40
41 #[serde(default)]
43 #[tap(participant_list)]
44 pub agents: Vec<Agent>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub memo: Option<String>,
49
50 #[serde(rename = "settlementId", skip_serializing_if = "Option::is_none")]
52 pub settlement_id: Option<String>,
53
54 #[tap(transaction_id)]
56 pub transaction_id: String,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
60 #[tap(connection_id)]
61 pub connection_id: Option<String>,
62
63 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
65 pub metadata: HashMap<String, serde_json::Value>,
66}
67
68impl Transfer {
69 pub fn builder() -> TransferBuilder {
92 TransferBuilder::default()
93 }
94
95 pub fn message_id(&self) -> String {
97 uuid::Uuid::new_v4().to_string()
98 }
99}
100
101#[derive(Default)]
103pub struct TransferBuilder {
104 asset: Option<AssetId>,
105 originator: Option<Party>,
106 amount: Option<String>,
107 beneficiary: Option<Party>,
108 settlement_id: Option<String>,
109 memo: Option<String>,
110 transaction_id: Option<String>,
111 agents: Vec<Agent>,
112 metadata: HashMap<String, serde_json::Value>,
113}
114
115impl TransferBuilder {
116 pub fn asset(mut self, asset: AssetId) -> Self {
118 self.asset = Some(asset);
119 self
120 }
121
122 pub fn originator(mut self, originator: Party) -> Self {
124 self.originator = Some(originator);
125 self
126 }
127
128 pub fn amount(mut self, amount: String) -> Self {
130 self.amount = Some(amount);
131 self
132 }
133
134 pub fn beneficiary(mut self, beneficiary: Party) -> Self {
136 self.beneficiary = Some(beneficiary);
137 self
138 }
139
140 pub fn settlement_id(mut self, settlement_id: String) -> Self {
142 self.settlement_id = Some(settlement_id);
143 self
144 }
145
146 pub fn memo(mut self, memo: String) -> Self {
148 self.memo = Some(memo);
149 self
150 }
151
152 pub fn transaction_id(mut self, transaction_id: String) -> Self {
154 self.transaction_id = Some(transaction_id);
155 self
156 }
157
158 pub fn add_agent(mut self, agent: Agent) -> Self {
160 self.agents.push(agent);
161 self
162 }
163
164 pub fn agents(mut self, agents: Vec<Agent>) -> Self {
166 self.agents = agents;
167 self
168 }
169
170 pub fn add_metadata(mut self, key: String, value: serde_json::Value) -> Self {
172 self.metadata.insert(key, value);
173 self
174 }
175
176 pub fn metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
178 self.metadata = metadata;
179 self
180 }
181
182 pub fn build(self) -> Transfer {
188 Transfer {
189 asset: self.asset.expect("Asset is required"),
190 originator: self.originator.expect("Originator is required"),
191 amount: self.amount.expect("Amount is required"),
192 beneficiary: self.beneficiary,
193 settlement_id: self.settlement_id,
194 memo: self.memo,
195 transaction_id: self
196 .transaction_id
197 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
198 agents: self.agents,
199 connection_id: None,
200 metadata: self.metadata,
201 }
202 }
203
204 pub fn try_build(self) -> Result<Transfer> {
206 let asset = self
207 .asset
208 .ok_or_else(|| Error::Validation("Asset is required".to_string()))?;
209 let originator = self
210 .originator
211 .ok_or_else(|| Error::Validation("Originator is required".to_string()))?;
212 let amount = self
213 .amount
214 .ok_or_else(|| Error::Validation("Amount is required".to_string()))?;
215
216 let transfer = Transfer {
217 transaction_id: self
218 .transaction_id
219 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
220 asset,
221 originator,
222 amount,
223 beneficiary: self.beneficiary,
224 settlement_id: self.settlement_id,
225 memo: self.memo,
226 agents: self.agents,
227 connection_id: None,
228 metadata: self.metadata,
229 };
230
231 transfer.validate()?;
233
234 Ok(transfer)
235 }
236}
237
238impl Transfer {
239 pub fn validate(&self) -> Result<()> {
241 if self.asset.namespace().is_empty() || self.asset.reference().is_empty() {
243 return Err(Error::Validation("Asset ID is invalid".to_string()));
244 }
245
246 if self.originator.id().is_empty() {
248 return Err(Error::Validation("Originator ID is required".to_string()));
249 }
250
251 if self.amount.is_empty() {
253 return Err(Error::Validation("Amount is required".to_string()));
254 }
255
256 match self.amount.parse::<f64>() {
258 Ok(amount) if amount <= 0.0 => {
259 return Err(Error::Validation("Amount must be positive".to_string()));
260 }
261 Err(_) => {
262 return Err(Error::Validation(
263 "Amount must be a valid number".to_string(),
264 ));
265 }
266 _ => {}
267 }
268
269 Ok(())
270 }
271}