amp_rs/
model.rs

1use chrono::{DateTime, Duration, Utc};
2use secrecy::{DebugSecret, Secret, SerializableSecret};
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use zeroize::Zeroize;
5
6/// Request payload for AMP token acquisition
7#[derive(Debug, Serialize)]
8pub struct TokenRequest {
9    pub username: String,
10    pub password: String,
11}
12
13/// Response from AMP token acquisition
14#[derive(Debug, Deserialize)]
15pub struct TokenResponse {
16    pub token: String,
17}
18
19#[derive(Clone, Serialize, Deserialize)]
20pub struct Password(pub String);
21
22impl Zeroize for Password {
23    fn zeroize(&mut self) {
24        self.0.zeroize();
25    }
26}
27
28impl From<String> for Password {
29    fn from(s: String) -> Self {
30        Self(s)
31    }
32}
33
34impl SerializableSecret for Password {}
35
36impl DebugSecret for Password {}
37
38#[derive(Debug, Serialize)]
39pub struct ChangePasswordRequest {
40    pub password: Secret<Password>,
41}
42
43#[derive(Debug, Deserialize)]
44pub struct ChangePasswordResponse {
45    pub username: String,
46    pub password: Secret<Password>,
47    pub token: Secret<String>,
48}
49
50#[derive(Debug, Deserialize)]
51#[allow(clippy::struct_excessive_bools)]
52pub struct Asset {
53    pub name: String,
54    pub asset_uuid: String,
55    pub issuer: i64,
56    pub asset_id: String,
57    pub reissuance_token_id: Option<String>,
58    pub requirements: Vec<i64>,
59    pub ticker: Option<String>,
60    pub precision: i64,
61    pub domain: Option<String>,
62    pub pubkey: Option<String>,
63    pub is_registered: bool,
64    pub is_authorized: bool,
65    pub is_locked: bool,
66    pub issuer_authorization_endpoint: Option<String>,
67    pub transfer_restricted: bool,
68}
69
70#[derive(Debug, Serialize)]
71pub struct IssuanceRequest {
72    pub name: String,
73    pub amount: i64,
74    pub destination_address: String,
75    pub domain: String,
76    pub ticker: String,
77    pub pubkey: String,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub precision: Option<i64>,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub is_confidential: Option<bool>,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub is_reissuable: Option<bool>,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub reissuance_amount: Option<i64>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub reissuance_address: Option<String>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub transfer_restricted: Option<bool>,
90}
91
92#[derive(Debug, Deserialize)]
93pub struct IssuanceResponse {
94    pub name: String,
95    pub amount: i64,
96    pub destination_address: String,
97    pub domain: String,
98    pub ticker: String,
99    pub pubkey: String,
100    pub is_confidential: bool,
101    pub is_reissuable: bool,
102    pub reissuance_amount: i64,
103    pub reissuance_address: String,
104    pub asset_id: String,
105    pub reissuance_token_id: Option<String>,
106    pub asset_uuid: String,
107    pub txid: String,
108    pub vin: i64,
109    pub asset_vout: i64,
110    pub reissuance_vout: Option<i64>,
111    pub issuer_authorization_endpoint: Option<String>,
112    pub transfer_restricted: bool,
113    pub issuance_assetblinder: String,
114    pub issuance_tokenblinder: Option<String>,
115}
116
117#[derive(Debug, Serialize)]
118pub struct EditAssetRequest {
119    pub issuer_authorization_endpoint: String,
120}
121
122#[derive(Debug, Deserialize)]
123pub struct RegisteredUserResponse {
124    pub id: i64,
125    #[serde(rename = "GAID")]
126    pub gaid: Option<String>,
127    pub is_company: bool,
128    pub name: String,
129    pub categories: Vec<i64>,
130    pub creator: i64,
131}
132
133#[derive(Debug, Serialize)]
134pub struct RegisteredUserAdd {
135    pub name: String,
136    #[serde(rename = "GAID")]
137    pub gaid: Option<String>,
138    pub is_company: bool,
139}
140
141#[derive(Debug, Serialize)]
142pub struct RegisteredUserEdit {
143    pub name: Option<String>,
144}
145
146#[derive(Debug, Serialize)]
147pub struct GaidRequest {
148    pub gaid: String,
149}
150
151#[derive(Debug, Serialize)]
152pub struct CategoriesRequest {
153    pub categories: Vec<i64>,
154}
155
156#[derive(Debug, Deserialize)]
157pub struct CategoryResponse {
158    pub id: i64,
159    pub name: String,
160    pub description: Option<String>,
161    pub registered_users: Vec<i64>,
162    pub assets: Vec<String>,
163}
164
165#[derive(Debug, Serialize)]
166pub struct CategoryAdd {
167    pub name: String,
168    pub description: Option<String>,
169}
170
171#[derive(Debug, Serialize)]
172pub struct CategoryEdit {
173    pub name: Option<String>,
174    pub description: Option<String>,
175}
176
177#[derive(Debug, Deserialize)]
178pub struct ValidateGaidResponse {
179    pub is_valid: bool,
180    pub error: Option<String>,
181}
182
183#[derive(Debug, Deserialize)]
184pub struct AddressGaidResponse {
185    pub address: String,
186    pub error: Option<String>,
187}
188
189#[derive(Debug, Deserialize)]
190pub struct Manager {
191    pub username: String,
192    pub id: i64,
193    pub is_locked: bool,
194    pub assets: Vec<String>,
195}
196
197#[derive(Debug, Serialize)]
198pub struct ManagerCreate {
199    pub username: String,
200    pub password: String,
201}
202
203#[derive(Debug, Deserialize, Serialize)]
204#[serde(rename_all = "UPPERCASE")]
205pub enum Status {
206    Unconfirmed,
207    Confirmed,
208}
209
210#[derive(Debug, Deserialize, Serialize)]
211pub struct DistributionAssignment {
212    pub registered_user: i64,
213    pub amount: i64,
214    pub vout: i64,
215}
216
217#[derive(Debug, Deserialize, Serialize)]
218pub struct Transaction {
219    pub txid: String,
220    pub transaction_status: Status,
221    pub included_blockheight: i64,
222    pub confirmed_datetime: String,
223    pub assignments: Vec<DistributionAssignment>,
224}
225
226#[derive(Debug, Deserialize, Serialize)]
227pub struct Distribution {
228    pub distribution_uuid: String,
229    pub distribution_status: Status,
230    pub transactions: Vec<Transaction>,
231}
232
233#[derive(Debug, Serialize, Deserialize, Clone)]
234pub struct CreateAssetAssignmentRequest {
235    pub registered_user: i64,
236    pub amount: i64,
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub vesting_timestamp: Option<i64>, // Unix timestamp in seconds, nullable
239    #[serde(default = "default_ready_for_distribution")]
240    pub ready_for_distribution: bool, // Defaults to false
241}
242
243const fn default_ready_for_distribution() -> bool {
244    false
245}
246
247#[derive(Debug, Serialize)]
248pub struct CreateAssetAssignmentRequestWrapper {
249    pub assignments: Vec<CreateAssetAssignmentRequest>,
250}
251
252#[derive(Debug, Deserialize, Serialize)]
253pub struct Assignment {
254    pub id: i64,
255    pub registered_user: i64,
256    pub amount: i64,
257    pub receiving_address: Option<String>,
258    pub distribution_uuid: Option<String>,
259    pub ready_for_distribution: bool,
260    pub vesting_datetime: Option<String>,
261    pub vesting_timestamp: Option<i64>,
262    pub has_vested: bool,
263    pub is_distributed: bool,
264    pub creator: i64,
265    #[serde(rename = "GAID")]
266    pub gaid: Option<String>,
267    // Legacy field for backward compatibility
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub investor: Option<i64>,
270}
271
272#[derive(Debug, Deserialize, Serialize)]
273pub struct RegisteredUserSummary {
274    pub asset_uuid: String,
275    pub asset_id: String,
276    pub assignments: Vec<Assignment>,
277    pub assignments_sum: i64,
278    pub distributions: Vec<Distribution>,
279    pub distributions_sum: i64,
280    pub balance: i64,
281}
282
283#[derive(Debug, Deserialize, Serialize)]
284pub struct Activity {
285    #[serde(rename = "type")]
286    pub activity_type: String,
287    pub datetime: String,
288    pub description: String,
289    pub txid: String,
290    pub vout: i64,
291    pub blockheight: i64,
292    pub asset_blinder: String,
293    pub amount_blinder: String,
294    #[serde(rename = "registered user")]
295    pub registered_user: Option<i64>,
296    pub amount: i64,
297}
298
299#[derive(Debug, Serialize, Default)]
300pub struct AssetActivityParams {
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub start: Option<i64>,
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub count: Option<i64>,
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub sortcolumn: Option<String>,
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub sortorder: Option<String>,
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub height_start: Option<i64>,
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub height_stop: Option<i64>,
313}
314
315#[derive(Debug, Deserialize, Serialize)]
316pub struct Ownership {
317    pub owner: String,
318    pub amount: i64,
319    #[serde(rename = "GAID")]
320    pub gaid: Option<String>,
321}
322
323#[derive(Debug, Deserialize, Serialize)]
324pub struct Outpoint {
325    pub txid: String,
326    pub vout: i64,
327}
328
329pub type LostOutputs = Vec<Outpoint>;
330
331#[derive(Debug, Deserialize, Serialize)]
332pub struct GaidBalanceEntry {
333    pub asset_uuid: String,
334    pub asset_id: String,
335    pub balance: i64,
336}
337
338pub type Balance = Vec<GaidBalanceEntry>;
339
340#[derive(Debug, Deserialize, Serialize)]
341pub struct AssetLostOutputs {
342    pub lost_outputs: LostOutputs,
343    pub reissuance_lost_outputs: LostOutputs,
344}
345
346#[derive(Debug, Deserialize, Serialize)]
347pub struct AssetSummary {
348    pub asset_id: String,
349    pub reissuance_token_id: Option<String>,
350    pub issued: i64,
351    pub reissued: i64,
352    pub assigned: i64,
353    pub distributed: i64,
354    pub burned: i64,
355    pub blacklisted: i64,
356    pub registered_users: i64,
357    pub active_registered_users: i64,
358    pub active_green_subaccounts: i64,
359    #[serde(rename = "reissuance_tokens")]
360    pub reissuance_tokens: i64,
361}
362
363#[derive(Debug, Deserialize, Serialize)]
364pub struct Utxo {
365    pub txid: String,
366    pub vout: i64,
367    pub asset: String,
368    pub amount: i64,
369    pub registered_user: Option<i64>,
370    pub gaid: Option<String>,
371    pub blacklisted: bool,
372}
373
374#[derive(Debug, Deserialize, Serialize)]
375pub struct Reissuance {
376    pub txid: String,
377    pub vout: i64,
378    pub destination_address: String,
379    pub reissuance_amount: i64,
380    pub confirmed_in_block: String,
381    pub created: String,
382}
383
384#[derive(Debug, Serialize)]
385pub struct ReissueRequest {
386    pub amount_to_reissue: i64,
387}
388
389#[derive(Debug, Serialize)]
390pub struct ReissueConfirmRequest {
391    pub details: serde_json::Value,
392    pub listissuances: Vec<serde_json::Value>,
393    pub reissuance_output: serde_json::Value,
394}
395
396#[derive(Debug, Serialize)]
397pub struct BurnRequest {
398    pub amount: i64,
399}
400
401#[derive(Debug, Deserialize, Serialize)]
402pub struct BurnCreate {
403    pub command: String,
404    pub min_supported_client_script_version: i64,
405    pub base_url: String,
406    pub asset_uuid: String,
407    pub asset_id: String,
408    pub amount: f64,
409    pub utxos: Vec<Outpoint>,
410}
411
412#[derive(Debug, Serialize)]
413pub struct BurnConfirmRequest {
414    pub tx_data: serde_json::Value,
415    pub change_data: Vec<serde_json::Value>,
416}
417
418#[derive(Debug, Serialize)]
419pub struct SetAssetMemoRequest {
420    pub memo: String,
421}
422
423#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
424#[serde(rename_all = "lowercase")]
425pub enum Permission {
426    View,
427    Receive,
428    Transfer,
429    Assign,
430    Distribute,
431    Reissue,
432    Burn,
433    Acquire,
434    Manage,
435    Permissions,
436}
437
438#[derive(Debug, Deserialize, Serialize)]
439pub struct BroadcastResponse {
440    pub txid: String,
441    pub hex: String,
442}
443
444/// Enhanced token data structure with secure storage and timestamp tracking
445#[derive(Debug, Clone, Serialize, Deserialize)]
446pub struct TokenData {
447    #[serde(with = "secret_serde")]
448    pub token: Secret<String>,
449    pub expires_at: DateTime<Utc>,
450    pub obtained_at: DateTime<Utc>,
451}
452
453impl TokenData {
454    /// Creates a new `TokenData` instance
455    ///
456    /// # Examples
457    /// ```
458    /// # use amp_rs::model::TokenData;
459    /// # use chrono::{Utc, Duration};
460    /// let expires_at = Utc::now() + Duration::hours(24);
461    /// let token_data = TokenData::new("my_token".to_string(), expires_at);
462    /// assert!(!token_data.is_expired());
463    /// ```
464    #[must_use]
465    pub fn new(token: String, expires_at: DateTime<Utc>) -> Self {
466        Self {
467            token: Secret::new(token),
468            expires_at,
469            obtained_at: Utc::now(),
470        }
471    }
472
473    /// Checks if the token is expired
474    ///
475    /// # Examples
476    /// ```
477    /// # use amp_rs::model::TokenData;
478    /// # use chrono::{Utc, Duration};
479    /// // Create an expired token
480    /// let expires_at = Utc::now() - Duration::hours(1);
481    /// let token_data = TokenData::new("expired_token".to_string(), expires_at);
482    /// assert!(token_data.is_expired());
483    /// 
484    /// // Create a valid token
485    /// let expires_at = Utc::now() + Duration::hours(1);
486    /// let token_data = TokenData::new("valid_token".to_string(), expires_at);
487    /// assert!(!token_data.is_expired());
488    /// ```
489    #[must_use]
490    pub fn is_expired(&self) -> bool {
491        Utc::now() > self.expires_at
492    }
493
494    /// Checks if the token expires within the given threshold
495    ///
496    /// # Examples
497    /// ```
498    /// # use amp_rs::model::TokenData;
499    /// # use chrono::{Utc, Duration};
500    /// // Token expires in 30 minutes
501    /// let expires_at = Utc::now() + Duration::minutes(30);
502    /// let token_data = TokenData::new("token".to_string(), expires_at);
503    /// 
504    /// // Check if it expires within 1 hour
505    /// assert!(token_data.expires_soon(Duration::hours(1)));
506    /// 
507    /// // Check if it expires within 15 minutes
508    /// assert!(!token_data.expires_soon(Duration::minutes(15)));
509    /// ```
510    #[must_use]
511    pub fn expires_soon(&self, threshold: Duration) -> bool {
512        Utc::now() + threshold > self.expires_at
513    }
514
515    /// Returns the age of the token
516    ///
517    /// # Examples
518    /// ```
519    /// # use amp_rs::model::TokenData;
520    /// # use chrono::{Utc, Duration};
521    /// let expires_at = Utc::now() + Duration::hours(24);
522    /// let token_data = TokenData::new("token".to_string(), expires_at);
523    /// 
524    /// // Token age should be very small (just created)
525    /// let age = token_data.age();
526    /// assert!(age < Duration::seconds(1));
527    /// ```
528    #[must_use]
529    pub fn age(&self) -> Duration {
530        Utc::now() - self.obtained_at
531    }
532}
533
534/// Token information for debugging and monitoring
535#[derive(Debug, Clone)]
536pub struct TokenInfo {
537    pub expires_at: DateTime<Utc>,
538    pub obtained_at: DateTime<Utc>,
539    pub expires_in: Duration,
540    pub age: Duration,
541    pub is_expired: bool,
542    pub expires_soon: bool,
543}
544
545impl From<&TokenData> for TokenInfo {
546    fn from(token_data: &TokenData) -> Self {
547        let now = Utc::now();
548        let expires_in = token_data.expires_at - now;
549        let expires_soon_threshold = Duration::minutes(5);
550
551        Self {
552            expires_at: token_data.expires_at,
553            obtained_at: token_data.obtained_at,
554            expires_in,
555            age: token_data.age(),
556            is_expired: token_data.is_expired(),
557            expires_soon: token_data.expires_soon(expires_soon_threshold),
558        }
559    }
560}
561
562/// Custom serialization module for Secret<String>
563pub mod secret_serde {
564    use super::{Deserialize, Deserializer, Secret, Serialize, Serializer};
565
566    /// # Errors
567    /// Returns an error if serialization fails
568    pub fn serialize<S>(secret: &Secret<String>, serializer: S) -> Result<S::Ok, S::Error>
569    where
570        S: Serializer,
571    {
572        use secrecy::ExposeSecret;
573        secret.expose_secret().serialize(serializer)
574    }
575
576    /// # Errors
577    /// Returns an error if deserialization fails
578    pub fn deserialize<'de, D>(deserializer: D) -> Result<Secret<String>, D::Error>
579    where
580        D: Deserializer<'de>,
581    {
582        let s = String::deserialize(deserializer)?;
583        Ok(Secret::new(s))
584    }
585}