1use 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, PartialEq)]
21pub struct TransactionValue {
22 pub amount: String,
24
25 pub currency: String,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
31#[tap(
32 message_type = "https://tap.rsvp/schema/1.0#Transfer",
33 initiator,
34 authorizable,
35 transactable
36)]
37pub struct Transfer {
38 pub asset: AssetId,
40
41 #[serde(rename = "originator", skip_serializing_if = "Option::is_none")]
43 #[tap(participant)]
44 pub originator: Option<Party>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 #[tap(participant)]
49 pub beneficiary: Option<Party>,
50
51 pub amount: String,
53
54 #[serde(default)]
56 #[tap(participant_list)]
57 pub agents: Vec<Agent>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub memo: Option<String>,
62
63 #[serde(rename = "settlementId", skip_serializing_if = "Option::is_none")]
65 pub settlement_id: Option<String>,
66
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub expiry: Option<String>,
70
71 #[serde(rename = "transactionValue", skip_serializing_if = "Option::is_none")]
73 pub transaction_value: Option<TransactionValue>,
74
75 #[serde(skip)]
77 #[tap(transaction_id)]
78 pub transaction_id: Option<String>,
79
80 #[serde(skip_serializing_if = "Option::is_none")]
82 #[tap(connection_id)]
83 pub connection_id: Option<String>,
84
85 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
87 pub metadata: HashMap<String, serde_json::Value>,
88}
89
90impl Transfer {
91 pub fn builder() -> TransferBuilder {
114 TransferBuilder::default()
115 }
116
117 pub fn message_id(&self) -> String {
119 uuid::Uuid::new_v4().to_string()
120 }
121}
122
123#[derive(Default)]
125pub struct TransferBuilder {
126 asset: Option<AssetId>,
127 originator: Option<Party>,
128 amount: Option<String>,
129 beneficiary: Option<Party>,
130 settlement_id: Option<String>,
131 expiry: Option<String>,
132 transaction_value: Option<TransactionValue>,
133 memo: Option<String>,
134 transaction_id: Option<String>,
135 agents: Vec<Agent>,
136 metadata: HashMap<String, serde_json::Value>,
137}
138
139impl TransferBuilder {
140 pub fn asset(mut self, asset: AssetId) -> Self {
142 self.asset = Some(asset);
143 self
144 }
145
146 pub fn originator(mut self, originator: Party) -> Self {
148 self.originator = Some(originator);
149 self
150 }
151
152 pub fn amount(mut self, amount: String) -> Self {
154 self.amount = Some(amount);
155 self
156 }
157
158 pub fn beneficiary(mut self, beneficiary: Party) -> Self {
160 self.beneficiary = Some(beneficiary);
161 self
162 }
163
164 pub fn settlement_id(mut self, settlement_id: String) -> Self {
166 self.settlement_id = Some(settlement_id);
167 self
168 }
169
170 pub fn expiry(mut self, expiry: String) -> Self {
172 self.expiry = Some(expiry);
173 self
174 }
175
176 pub fn transaction_value(mut self, transaction_value: TransactionValue) -> Self {
178 self.transaction_value = Some(transaction_value);
179 self
180 }
181
182 pub fn memo(mut self, memo: String) -> Self {
184 self.memo = Some(memo);
185 self
186 }
187
188 pub fn transaction_id(mut self, transaction_id: String) -> Self {
190 self.transaction_id = Some(transaction_id);
191 self
192 }
193
194 pub fn add_agent(mut self, agent: Agent) -> Self {
196 self.agents.push(agent);
197 self
198 }
199
200 pub fn agents(mut self, agents: Vec<Agent>) -> Self {
202 self.agents = agents;
203 self
204 }
205
206 pub fn add_metadata(mut self, key: String, value: serde_json::Value) -> Self {
208 self.metadata.insert(key, value);
209 self
210 }
211
212 pub fn metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
214 self.metadata = metadata;
215 self
216 }
217
218 pub fn build(self) -> Transfer {
224 Transfer {
225 asset: self.asset.expect("Asset is required"),
226 originator: self.originator,
227 amount: self.amount.expect("Amount is required"),
228 beneficiary: self.beneficiary,
229 settlement_id: self.settlement_id,
230 expiry: self.expiry,
231 transaction_value: self.transaction_value,
232 memo: self.memo,
233 transaction_id: self.transaction_id,
234 agents: self.agents,
235 connection_id: None,
236 metadata: self.metadata,
237 }
238 }
239
240 pub fn try_build(self) -> Result<Transfer> {
242 let asset = self
243 .asset
244 .ok_or_else(|| Error::Validation("Asset is required".to_string()))?;
245 let amount = self
246 .amount
247 .ok_or_else(|| Error::Validation("Amount is required".to_string()))?;
248
249 let transfer = Transfer {
250 transaction_id: self.transaction_id,
251 asset,
252 originator: self.originator,
253 amount,
254 beneficiary: self.beneficiary,
255 settlement_id: self.settlement_id,
256 expiry: self.expiry,
257 transaction_value: self.transaction_value,
258 memo: self.memo,
259 agents: self.agents,
260 connection_id: None,
261 metadata: self.metadata,
262 };
263
264 transfer.validate()?;
266
267 Ok(transfer)
268 }
269}
270
271impl Transfer {
272 pub fn validate(&self) -> Result<()> {
274 if self.asset.namespace().is_empty() || self.asset.reference().is_empty() {
276 return Err(Error::Validation("Asset ID is invalid".to_string()));
277 }
278
279 if let Some(originator) = &self.originator {
281 if originator.id().is_empty() {
282 return Err(Error::Validation(
283 "Originator ID cannot be empty".to_string(),
284 ));
285 }
286 }
287
288 if self.amount.is_empty() {
290 return Err(Error::Validation("Amount is required".to_string()));
291 }
292
293 match self.amount.parse::<f64>() {
295 Ok(amount) if !amount.is_finite() => {
296 return Err(Error::Validation(
297 "Amount must be a finite number".to_string(),
298 ));
299 }
300 Ok(amount) if amount <= 0.0 => {
301 return Err(Error::Validation("Amount must be positive".to_string()));
302 }
303 Err(_) => {
304 return Err(Error::Validation(
305 "Amount must be a valid number".to_string(),
306 ));
307 }
308 _ => {}
309 }
310
311 Ok(())
312 }
313}