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", skip_serializing_if = "Option::is_none")]
30 #[tap(participant)]
31 pub originator: Option<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 #[serde(skip)]
56 #[tap(transaction_id)]
57 pub transaction_id: Option<String>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
61 #[tap(connection_id)]
62 pub connection_id: Option<String>,
63
64 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
66 pub metadata: HashMap<String, serde_json::Value>,
67}
68
69impl Transfer {
70 pub fn builder() -> TransferBuilder {
93 TransferBuilder::default()
94 }
95
96 pub fn message_id(&self) -> String {
98 uuid::Uuid::new_v4().to_string()
99 }
100}
101
102#[derive(Default)]
104pub struct TransferBuilder {
105 asset: Option<AssetId>,
106 originator: Option<Party>,
107 amount: Option<String>,
108 beneficiary: Option<Party>,
109 settlement_id: Option<String>,
110 memo: Option<String>,
111 transaction_id: Option<String>,
112 agents: Vec<Agent>,
113 metadata: HashMap<String, serde_json::Value>,
114}
115
116impl TransferBuilder {
117 pub fn asset(mut self, asset: AssetId) -> Self {
119 self.asset = Some(asset);
120 self
121 }
122
123 pub fn originator(mut self, originator: Party) -> Self {
125 self.originator = Some(originator);
126 self
127 }
128
129 pub fn amount(mut self, amount: String) -> Self {
131 self.amount = Some(amount);
132 self
133 }
134
135 pub fn beneficiary(mut self, beneficiary: Party) -> Self {
137 self.beneficiary = Some(beneficiary);
138 self
139 }
140
141 pub fn settlement_id(mut self, settlement_id: String) -> Self {
143 self.settlement_id = Some(settlement_id);
144 self
145 }
146
147 pub fn memo(mut self, memo: String) -> Self {
149 self.memo = Some(memo);
150 self
151 }
152
153 pub fn transaction_id(mut self, transaction_id: String) -> Self {
155 self.transaction_id = Some(transaction_id);
156 self
157 }
158
159 pub fn add_agent(mut self, agent: Agent) -> Self {
161 self.agents.push(agent);
162 self
163 }
164
165 pub fn agents(mut self, agents: Vec<Agent>) -> Self {
167 self.agents = agents;
168 self
169 }
170
171 pub fn add_metadata(mut self, key: String, value: serde_json::Value) -> Self {
173 self.metadata.insert(key, value);
174 self
175 }
176
177 pub fn metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
179 self.metadata = metadata;
180 self
181 }
182
183 pub fn build(self) -> Transfer {
189 Transfer {
190 asset: self.asset.expect("Asset is required"),
191 originator: self.originator,
192 amount: self.amount.expect("Amount is required"),
193 beneficiary: self.beneficiary,
194 settlement_id: self.settlement_id,
195 memo: self.memo,
196 transaction_id: self.transaction_id,
197 agents: self.agents,
198 connection_id: None,
199 metadata: self.metadata,
200 }
201 }
202
203 pub fn try_build(self) -> Result<Transfer> {
205 let asset = self
206 .asset
207 .ok_or_else(|| Error::Validation("Asset is required".to_string()))?;
208 let amount = self
209 .amount
210 .ok_or_else(|| Error::Validation("Amount is required".to_string()))?;
211
212 let transfer = Transfer {
213 transaction_id: self.transaction_id,
214 asset,
215 originator: self.originator,
216 amount,
217 beneficiary: self.beneficiary,
218 settlement_id: self.settlement_id,
219 memo: self.memo,
220 agents: self.agents,
221 connection_id: None,
222 metadata: self.metadata,
223 };
224
225 transfer.validate()?;
227
228 Ok(transfer)
229 }
230}
231
232impl Transfer {
233 pub fn validate(&self) -> Result<()> {
235 if self.asset.namespace().is_empty() || self.asset.reference().is_empty() {
237 return Err(Error::Validation("Asset ID is invalid".to_string()));
238 }
239
240 if let Some(originator) = &self.originator {
242 if originator.id().is_empty() {
243 return Err(Error::Validation(
244 "Originator ID cannot be empty".to_string(),
245 ));
246 }
247 }
248
249 if self.amount.is_empty() {
251 return Err(Error::Validation("Amount is required".to_string()));
252 }
253
254 match self.amount.parse::<f64>() {
256 Ok(amount) if amount <= 0.0 => {
257 return Err(Error::Validation("Amount must be positive".to_string()));
258 }
259 Err(_) => {
260 return Err(Error::Validation(
261 "Amount must be a valid number".to_string(),
262 ));
263 }
264 _ => {}
265 }
266
267 Ok(())
268 }
269}