Skip to main content

accumulate_client/generated/
transactions.rs

1//! GENERATED FILE - DO NOT EDIT
2//! Sources: protocol/transaction.yml, user_transactions.yml, system.yml, synthetic_transactions.yml
3//! Generated: 2025-10-03 21:53:38
4
5#![allow(missing_docs)]
6
7use serde::{Serialize, Deserialize};
8use crate::errors::{Error, ValidationError};
9
10/// Validates that a string is a valid Accumulate URL
11/// Accumulate URLs must:
12/// - Start with "acc://"
13/// - Contain only ASCII characters
14/// - Have at least one character after the scheme
15/// - Not contain whitespace or control characters
16fn validate_accumulate_url(url: &str, field_name: &str) -> Result<(), Error> {
17    if url.is_empty() {
18        return Err(ValidationError::RequiredFieldMissing(field_name.to_string()).into());
19    }
20
21    if !url.starts_with("acc://") {
22        return Err(ValidationError::InvalidUrl(
23            format!("{}: must start with 'acc://', got '{}'", field_name, url)
24        ).into());
25    }
26
27    if !url.is_ascii() {
28        return Err(ValidationError::InvalidUrl(
29            format!("{}: must contain only ASCII characters", field_name)
30        ).into());
31    }
32
33    // Check for whitespace or control characters
34    if url.chars().any(|c| c.is_whitespace() || c.is_control()) {
35        return Err(ValidationError::InvalidUrl(
36            format!("{}: must not contain whitespace or control characters", field_name)
37        ).into());
38    }
39
40    // Must have content after acc://
41    if url.len() <= 6 {
42        return Err(ValidationError::InvalidUrl(
43            format!("{}: URL path cannot be empty", field_name)
44        ).into());
45    }
46
47    Ok(())
48}
49
50/// Validates that an amount string represents a valid positive integer
51fn validate_amount_string(amount: &str, field_name: &str) -> Result<(), Error> {
52    if amount.is_empty() {
53        return Err(ValidationError::RequiredFieldMissing(field_name.to_string()).into());
54    }
55
56    // Check if it's a valid integer (can be very large, so use string validation)
57    if !amount.chars().all(|c| c.is_ascii_digit()) {
58        return Err(ValidationError::InvalidAmount(
59            format!("{}: must be a valid non-negative integer, got '{}'", field_name, amount)
60        ).into());
61    }
62
63    // Check it's not all zeros (unless it's literally "0")
64    if amount != "0" && amount.chars().all(|c| c == '0') {
65        return Err(ValidationError::InvalidAmount(
66            format!("{}: invalid zero representation", field_name)
67        ).into());
68    }
69
70    Ok(())
71}
72
73/// Validates a list of authority URLs
74fn validate_authorities(authorities: &Option<Vec<String>>) -> Result<(), Error> {
75    if let Some(ref auths) = authorities {
76        for (i, auth) in auths.iter().enumerate() {
77            validate_accumulate_url(auth, &format!("authorities[{}]", i))?;
78        }
79    }
80    Ok(())
81}
82
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct AcmeFaucetBody {
86    #[serde(rename = "Url")]
87    pub url: String,
88}
89
90impl AcmeFaucetBody {
91    pub fn validate(&self) -> Result<(), Error> {
92        // Faucet URL must be a valid Accumulate URL (typically a lite token account)
93        validate_accumulate_url(&self.url, "url")?;
94        Ok(())
95    }
96}
97
98#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct ActivateProtocolVersionBody {
101    #[serde(rename = "Version")]
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub version: Option<String>,
104}
105
106impl ActivateProtocolVersionBody {
107    pub fn validate(&self) -> Result<(), Error> {
108        // Version is optional, but if present must not be empty
109        if let Some(ref version) = self.version {
110            if version.is_empty() {
111                return Err(ValidationError::InvalidFieldValue {
112                    field: "version".to_string(),
113                    reason: "version string cannot be empty if provided".to_string(),
114                }.into());
115            }
116        }
117        Ok(())
118    }
119}
120
121#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct AddCreditsBody {
124    #[serde(rename = "Recipient")]
125    pub recipient: String,
126    #[serde(rename = "Amount")]
127    pub amount: String,
128    #[serde(rename = "Oracle")]
129    pub oracle: u64,
130}
131
132impl AddCreditsBody {
133    pub fn validate(&self) -> Result<(), Error> {
134        // Recipient must be a valid Accumulate URL
135        validate_accumulate_url(&self.recipient, "recipient")?;
136
137        // Amount must be a valid positive integer string
138        validate_amount_string(&self.amount, "amount")?;
139
140        // Oracle price must be positive (represents ACME price in credits)
141        if self.oracle == 0 {
142            return Err(ValidationError::InvalidFieldValue {
143                field: "oracle".to_string(),
144                reason: "oracle price must be positive".to_string(),
145            }.into());
146        }
147
148        Ok(())
149    }
150}
151
152#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
153#[serde(rename_all = "camelCase")]
154pub struct BlockValidatorAnchorBody {
155    #[serde(rename = "AcmeBurnt")]
156    pub acme_burnt: String,
157}
158
159impl BlockValidatorAnchorBody {
160    pub fn validate(&self) -> Result<(), Error> {
161        // AcmeBurnt must be a valid amount string (can be zero for no burn)
162        validate_amount_string(&self.acme_burnt, "acmeBurnt")?;
163        Ok(())
164    }
165}
166
167#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
168#[serde(rename_all = "camelCase")]
169pub struct BurnCreditsBody {
170    #[serde(rename = "Amount")]
171    pub amount: u64,
172}
173
174impl BurnCreditsBody {
175    pub fn validate(&self) -> Result<(), Error> {
176        // Amount must be positive (can't burn zero credits)
177        if self.amount == 0 {
178            return Err(ValidationError::InvalidAmount(
179                "amount: must be greater than zero to burn credits".to_string()
180            ).into());
181        }
182        Ok(())
183    }
184}
185
186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
187#[serde(rename_all = "camelCase")]
188pub struct BurnTokensBody {
189    #[serde(rename = "Amount")]
190    pub amount: String,
191}
192
193impl BurnTokensBody {
194    pub fn validate(&self) -> Result<(), Error> {
195        // Amount must be a valid positive integer string (can't burn zero)
196        validate_amount_string(&self.amount, "amount")?;
197
198        // Ensure amount is not "0"
199        if self.amount == "0" {
200            return Err(ValidationError::InvalidAmount(
201                "amount: must be greater than zero to burn tokens".to_string()
202            ).into());
203        }
204        Ok(())
205    }
206}
207
208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
209#[serde(rename_all = "camelCase")]
210pub struct CreateDataAccountBody {
211    #[serde(rename = "Url")]
212    pub url: String,
213    #[serde(rename = "Authorities")]
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub authorities: Option<Vec<String>>,
216}
217
218impl CreateDataAccountBody {
219    pub fn validate(&self) -> Result<(), Error> {
220        // URL must be a valid Accumulate URL
221        validate_accumulate_url(&self.url, "url")?;
222
223        // Validate authorities if provided
224        validate_authorities(&self.authorities)?;
225
226        Ok(())
227    }
228}
229
230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
231#[serde(rename_all = "camelCase")]
232pub struct CreateIdentityBody {
233    #[serde(rename = "Url")]
234    pub url: String,
235    #[serde(rename = "KeyHash")]
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub key_hash: Option<Vec<u8>>,
238    #[serde(rename = "KeyBookUrl")]
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub key_book_url: Option<String>,
241    #[serde(rename = "Authorities")]
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub authorities: Option<Vec<String>>,
244}
245
246impl CreateIdentityBody {
247    pub fn validate(&self) -> Result<(), Error> {
248        // URL must be a valid Accumulate URL for the new identity
249        validate_accumulate_url(&self.url, "url")?;
250
251        // If key_book_url is provided, it must be valid
252        if let Some(ref key_book_url) = self.key_book_url {
253            validate_accumulate_url(key_book_url, "keyBookUrl")?;
254        }
255
256        // If key_hash is provided, it should be 32 bytes (SHA-256 hash)
257        if let Some(ref key_hash) = self.key_hash {
258            if key_hash.len() != 32 {
259                return Err(ValidationError::InvalidHash {
260                    expected: 32,
261                    actual: key_hash.len(),
262                }.into());
263            }
264        }
265
266        // Validate authorities if provided
267        validate_authorities(&self.authorities)?;
268
269        Ok(())
270    }
271}
272
273#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
274#[serde(rename_all = "camelCase")]
275pub struct CreateKeyBookBody {
276    #[serde(rename = "Url")]
277    pub url: String,
278    #[serde(rename = "PublicKeyHash")]
279    #[serde(with = "hex::serde")]
280    pub public_key_hash: Vec<u8>,
281    #[serde(rename = "Authorities")]
282    #[serde(skip_serializing_if = "Option::is_none")]
283    pub authorities: Option<Vec<String>>,
284}
285
286impl CreateKeyBookBody {
287    pub fn validate(&self) -> Result<(), Error> {
288        // URL must be a valid Accumulate URL
289        validate_accumulate_url(&self.url, "url")?;
290
291        // Public key hash must be 32 bytes (SHA-256)
292        if self.public_key_hash.len() != 32 {
293            return Err(ValidationError::InvalidHash {
294                expected: 32,
295                actual: self.public_key_hash.len(),
296            }.into());
297        }
298
299        // Validate authorities if provided
300        validate_authorities(&self.authorities)?;
301
302        Ok(())
303    }
304}
305
306#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
307#[serde(rename_all = "camelCase")]
308pub struct CreateKeyPageBody {
309    #[serde(rename = "Keys")]
310    pub keys: Vec<serde_json::Value>,
311}
312
313impl CreateKeyPageBody {
314    pub fn validate(&self) -> Result<(), Error> {
315        // Must have at least one key entry
316        if self.keys.is_empty() {
317            return Err(ValidationError::EmptyCollection(
318                "keys: at least one key is required".to_string()
319            ).into());
320        }
321
322        // Each key entry should be a valid JSON object
323        for (i, key) in self.keys.iter().enumerate() {
324            if !key.is_object() {
325                return Err(ValidationError::InvalidFieldValue {
326                    field: format!("keys[{}]", i),
327                    reason: "each key must be a valid key specification object".to_string(),
328                }.into());
329            }
330        }
331
332        Ok(())
333    }
334}
335
336#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
337#[serde(rename_all = "camelCase")]
338pub struct CreateLiteTokenAccountBody {
339    // No fields defined
340}
341
342impl CreateLiteTokenAccountBody {
343    pub fn validate(&self) -> Result<(), Error> {
344        // No fields to validate - lite token accounts are created implicitly
345        Ok(())
346    }
347}
348
349#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct CreateTokenBody {
352    #[serde(rename = "Url")]
353    pub url: String,
354    #[serde(rename = "Symbol")]
355    pub symbol: String,
356    #[serde(rename = "Precision")]
357    pub precision: u64,
358    #[serde(rename = "Properties")]
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub properties: Option<String>,
361    #[serde(rename = "SupplyLimit")]
362    #[serde(skip_serializing_if = "Option::is_none")]
363    pub supply_limit: Option<String>,
364    #[serde(rename = "Authorities")]
365    #[serde(skip_serializing_if = "Option::is_none")]
366    pub authorities: Option<Vec<String>>,
367}
368
369impl CreateTokenBody {
370    pub fn validate(&self) -> Result<(), Error> {
371        // URL must be a valid Accumulate URL
372        validate_accumulate_url(&self.url, "url")?;
373
374        // Symbol must be non-empty and alphanumeric (1-10 characters typically)
375        if self.symbol.is_empty() {
376            return Err(ValidationError::InvalidTokenSymbol(
377                "symbol cannot be empty".to_string()
378            ).into());
379        }
380
381        if !self.symbol.chars().all(|c| c.is_ascii_alphanumeric()) {
382            return Err(ValidationError::InvalidTokenSymbol(
383                format!("symbol must be alphanumeric, got '{}'", self.symbol)
384            ).into());
385        }
386
387        if self.symbol.len() > 10 {
388            return Err(ValidationError::InvalidTokenSymbol(
389                format!("symbol too long (max 10 chars), got {} chars", self.symbol.len())
390            ).into());
391        }
392
393        // Precision must be between 0 and 18 (standard decimal precision)
394        if self.precision > 18 {
395            return Err(ValidationError::InvalidPrecision(self.precision).into());
396        }
397
398        // If supply_limit is provided, it must be a valid amount
399        if let Some(ref supply_limit) = self.supply_limit {
400            validate_amount_string(supply_limit, "supplyLimit")?;
401        }
402
403        // Validate authorities if provided
404        validate_authorities(&self.authorities)?;
405
406        Ok(())
407    }
408}
409
410#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
411#[serde(rename_all = "camelCase")]
412pub struct CreateTokenAccountBody {
413    #[serde(rename = "Url")]
414    pub url: String,
415    #[serde(rename = "TokenUrl")]
416    pub token_url: String,
417    #[serde(rename = "Authorities")]
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub authorities: Option<Vec<String>>,
420    #[serde(rename = "Proof")]
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub proof: Option<serde_json::Value>,
423}
424
425impl CreateTokenAccountBody {
426    pub fn validate(&self) -> Result<(), Error> {
427        // URL must be a valid Accumulate URL
428        validate_accumulate_url(&self.url, "url")?;
429
430        // TokenUrl must be a valid Accumulate URL
431        validate_accumulate_url(&self.token_url, "tokenUrl")?;
432
433        // Validate authorities if provided
434        validate_authorities(&self.authorities)?;
435
436        Ok(())
437    }
438}
439
440#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
441#[serde(rename_all = "camelCase")]
442pub struct DirectoryAnchorBody {
443    #[serde(rename = "Updates")]
444    pub updates: Vec<serde_json::Value>,
445    #[serde(rename = "Receipts")]
446    pub receipts: Vec<serde_json::Value>,
447    #[serde(rename = "MakeMajorBlock")]
448    pub make_major_block: u64,
449    #[serde(rename = "MakeMajorBlockTime")]
450    pub make_major_block_time: u64,
451}
452
453impl DirectoryAnchorBody {
454    pub fn validate(&self) -> Result<(), Error> {
455        // System transaction - validates structure only
456        // Updates and receipts can be empty arrays
457        // MakeMajorBlock and MakeMajorBlockTime are informational
458        Ok(())
459    }
460}
461
462#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
463#[serde(rename_all = "camelCase")]
464pub struct IssueTokensBody {
465    #[serde(rename = "Recipient")]
466    pub recipient: String,
467    #[serde(rename = "Amount")]
468    pub amount: String,
469    #[serde(rename = "To")]
470    pub to: Vec<serde_json::Value>,
471}
472
473impl IssueTokensBody {
474    pub fn validate(&self) -> Result<(), Error> {
475        // Recipient must be a valid Accumulate URL
476        validate_accumulate_url(&self.recipient, "recipient")?;
477
478        // Amount must be a valid positive integer string
479        validate_amount_string(&self.amount, "amount")?;
480
481        // Amount must be positive for issuance
482        if self.amount == "0" {
483            return Err(ValidationError::InvalidAmount(
484                "amount: must be greater than zero to issue tokens".to_string()
485            ).into());
486        }
487
488        Ok(())
489    }
490}
491
492#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
493#[serde(rename_all = "camelCase")]
494pub struct LockAccountBody {
495    #[serde(rename = "Height")]
496    pub height: u64,
497}
498
499impl LockAccountBody {
500    pub fn validate(&self) -> Result<(), Error> {
501        // Height must be positive (lock until block height)
502        if self.height == 0 {
503            return Err(ValidationError::InvalidFieldValue {
504                field: "height".to_string(),
505                reason: "lock height must be greater than zero".to_string(),
506            }.into());
507        }
508        Ok(())
509    }
510}
511
512#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
513#[serde(rename_all = "camelCase")]
514pub struct NetworkMaintenanceBody {
515    #[serde(rename = "Operations")]
516    pub operations: Vec<serde_json::Value>,
517}
518
519impl NetworkMaintenanceBody {
520    pub fn validate(&self) -> Result<(), Error> {
521        // System transaction - operations can be empty
522        // Validation of individual operations is done at the protocol level
523        Ok(())
524    }
525}
526
527#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
528#[serde(rename_all = "camelCase")]
529pub struct RemoteTransactionBody {
530    #[serde(rename = "Hash")]
531    #[serde(skip_serializing_if = "Option::is_none")]
532    pub hash: Option<Vec<u8>>,
533}
534
535impl RemoteTransactionBody {
536    pub fn validate(&self) -> Result<(), Error> {
537        // If hash is provided, it should be 32 bytes (SHA-256)
538        if let Some(ref hash) = self.hash {
539            if hash.len() != 32 {
540                return Err(ValidationError::InvalidHash {
541                    expected: 32,
542                    actual: hash.len(),
543                }.into());
544            }
545        }
546        Ok(())
547    }
548}
549
550#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
551#[serde(rename_all = "camelCase")]
552pub struct SendTokensBody {
553    #[serde(rename = "Hash")]
554    #[serde(skip_serializing_if = "Option::is_none")]
555    pub hash: Option<Vec<u8>>,
556    #[serde(rename = "Meta")]
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub meta: Option<serde_json::Value>,
559    #[serde(rename = "To")]
560    pub to: Vec<serde_json::Value>,
561}
562
563impl SendTokensBody {
564    pub fn validate(&self) -> Result<(), Error> {
565        // Must have at least one recipient
566        if self.to.is_empty() {
567            return Err(ValidationError::EmptyCollection(
568                "to: at least one recipient is required".to_string()
569            ).into());
570        }
571
572        // Each recipient should be a valid object with url and amount
573        for (i, recipient) in self.to.iter().enumerate() {
574            if !recipient.is_object() {
575                return Err(ValidationError::InvalidFieldValue {
576                    field: format!("to[{}]", i),
577                    reason: "each recipient must be a valid object with url and amount".to_string(),
578                }.into());
579            }
580
581            // Validate url field if present
582            if let Some(url) = recipient.get("url").and_then(|v| v.as_str()) {
583                validate_accumulate_url(url, &format!("to[{}].url", i))?;
584            }
585
586            // Validate amount field if present
587            if let Some(amount) = recipient.get("amount").and_then(|v| v.as_str()) {
588                validate_amount_string(amount, &format!("to[{}].amount", i))?;
589            }
590        }
591
592        // If hash is provided, it should be 32 bytes
593        if let Some(ref hash) = self.hash {
594            if hash.len() != 32 {
595                return Err(ValidationError::InvalidHash {
596                    expected: 32,
597                    actual: hash.len(),
598                }.into());
599            }
600        }
601
602        Ok(())
603    }
604}
605
606#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
607#[serde(rename_all = "camelCase")]
608pub struct SystemGenesisBody {
609    // No fields defined
610}
611
612impl SystemGenesisBody {
613    pub fn validate(&self) -> Result<(), Error> {
614        // System transaction - no fields to validate
615        Ok(())
616    }
617}
618
619#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
620#[serde(rename_all = "camelCase")]
621pub struct SystemWriteDataBody {
622    #[serde(rename = "Entry")]
623    pub entry: serde_json::Value,
624    #[serde(rename = "WriteToState")]
625    #[serde(skip_serializing_if = "Option::is_none")]
626    pub write_to_state: Option<bool>,
627}
628
629impl SystemWriteDataBody {
630    pub fn validate(&self) -> Result<(), Error> {
631        // Entry must be a valid object
632        if !self.entry.is_object() {
633            return Err(ValidationError::InvalidFieldValue {
634                field: "entry".to_string(),
635                reason: "entry must be a valid data entry object".to_string(),
636            }.into());
637        }
638        Ok(())
639    }
640}
641
642#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
643#[serde(rename_all = "camelCase")]
644pub struct TransferCreditsBody {
645    #[serde(rename = "To")]
646    pub to: Vec<serde_json::Value>,
647}
648
649impl TransferCreditsBody {
650    pub fn validate(&self) -> Result<(), Error> {
651        // Must have at least one recipient
652        if self.to.is_empty() {
653            return Err(ValidationError::EmptyCollection(
654                "to: at least one credit recipient is required".to_string()
655            ).into());
656        }
657
658        // Each recipient should be a valid object
659        for (i, recipient) in self.to.iter().enumerate() {
660            if !recipient.is_object() {
661                return Err(ValidationError::InvalidFieldValue {
662                    field: format!("to[{}]", i),
663                    reason: "each recipient must be a valid object with url and amount".to_string(),
664                }.into());
665            }
666
667            // Validate url field if present
668            if let Some(url) = recipient.get("url").and_then(|v| v.as_str()) {
669                validate_accumulate_url(url, &format!("to[{}].url", i))?;
670            }
671        }
672
673        Ok(())
674    }
675}
676
677#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
678#[serde(rename_all = "camelCase")]
679pub struct UpdateAccountAuthBody {
680    #[serde(rename = "Operations")]
681    pub operations: Vec<serde_json::Value>,
682}
683
684impl UpdateAccountAuthBody {
685    pub fn validate(&self) -> Result<(), Error> {
686        // Must have at least one operation
687        if self.operations.is_empty() {
688            return Err(ValidationError::EmptyCollection(
689                "operations: at least one auth operation is required".to_string()
690            ).into());
691        }
692
693        // Each operation should be a valid object
694        for (i, op) in self.operations.iter().enumerate() {
695            if !op.is_object() {
696                return Err(ValidationError::InvalidFieldValue {
697                    field: format!("operations[{}]", i),
698                    reason: "each operation must be a valid auth operation object".to_string(),
699                }.into());
700            }
701        }
702
703        Ok(())
704    }
705}
706
707#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
708#[serde(rename_all = "camelCase")]
709pub struct UpdateKeyBody {
710    #[serde(rename = "NewKeyHash")]
711    #[serde(with = "hex::serde")]
712    pub new_key_hash: Vec<u8>,
713}
714
715impl UpdateKeyBody {
716    pub fn validate(&self) -> Result<(), Error> {
717        // New key hash must be 32 bytes (SHA-256)
718        if self.new_key_hash.len() != 32 {
719            return Err(ValidationError::InvalidHash {
720                expected: 32,
721                actual: self.new_key_hash.len(),
722            }.into());
723        }
724        Ok(())
725    }
726}
727
728#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
729#[serde(rename_all = "camelCase")]
730pub struct UpdateKeyPageBody {
731    #[serde(rename = "Operation")]
732    pub operation: Vec<serde_json::Value>,
733}
734
735impl UpdateKeyPageBody {
736    pub fn validate(&self) -> Result<(), Error> {
737        // Must have at least one operation
738        if self.operation.is_empty() {
739            return Err(ValidationError::EmptyCollection(
740                "operation: at least one key page operation is required".to_string()
741            ).into());
742        }
743
744        // Each operation should be a valid object
745        for (i, op) in self.operation.iter().enumerate() {
746            if !op.is_object() {
747                return Err(ValidationError::InvalidFieldValue {
748                    field: format!("operation[{}]", i),
749                    reason: "each operation must be a valid key page operation object".to_string(),
750                }.into());
751            }
752        }
753
754        Ok(())
755    }
756}
757
758#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
759#[serde(rename_all = "camelCase")]
760pub struct WriteDataBody {
761    #[serde(rename = "Entry")]
762    pub entry: serde_json::Value,
763    #[serde(rename = "Scratch")]
764    #[serde(skip_serializing_if = "Option::is_none")]
765    pub scratch: Option<bool>,
766    #[serde(rename = "WriteToState")]
767    #[serde(skip_serializing_if = "Option::is_none")]
768    pub write_to_state: Option<bool>,
769}
770
771impl WriteDataBody {
772    pub fn validate(&self) -> Result<(), Error> {
773        // Entry must be a valid object
774        if !self.entry.is_object() {
775            return Err(ValidationError::InvalidFieldValue {
776                field: "entry".to_string(),
777                reason: "entry must be a valid data entry object".to_string(),
778            }.into());
779        }
780        Ok(())
781    }
782}
783
784#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
785#[serde(rename_all = "camelCase")]
786pub struct WriteDataToBody {
787    #[serde(rename = "Recipient")]
788    pub recipient: String,
789    #[serde(rename = "Entry")]
790    pub entry: serde_json::Value,
791}
792
793impl WriteDataToBody {
794    pub fn validate(&self) -> Result<(), Error> {
795        // Recipient must be a valid Accumulate URL
796        validate_accumulate_url(&self.recipient, "recipient")?;
797
798        // Entry must be a valid object
799        if !self.entry.is_object() {
800            return Err(ValidationError::InvalidFieldValue {
801                field: "entry".to_string(),
802                reason: "entry must be a valid data entry object".to_string(),
803            }.into());
804        }
805
806        Ok(())
807    }
808}
809
810#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
811#[serde(tag = "type")]
812pub enum TransactionBody {
813    #[serde(rename = "acmeFaucet")]
814    AcmeFaucet(AcmeFaucetBody),
815    #[serde(rename = "activateProtocolVersion")]
816    ActivateProtocolVersion(ActivateProtocolVersionBody),
817    #[serde(rename = "addCredits")]
818    AddCredits(AddCreditsBody),
819    #[serde(rename = "blockValidatorAnchor")]
820    BlockValidatorAnchor(BlockValidatorAnchorBody),
821    #[serde(rename = "burnCredits")]
822    BurnCredits(BurnCreditsBody),
823    #[serde(rename = "burnTokens")]
824    BurnTokens(BurnTokensBody),
825    #[serde(rename = "createDataAccount")]
826    CreateDataAccount(CreateDataAccountBody),
827    #[serde(rename = "createIdentity")]
828    CreateIdentity(CreateIdentityBody),
829    #[serde(rename = "createKeyBook")]
830    CreateKeyBook(CreateKeyBookBody),
831    #[serde(rename = "createKeyPage")]
832    CreateKeyPage(CreateKeyPageBody),
833    #[serde(rename = "createLiteTokenAccount")]
834    CreateLiteTokenAccount(CreateLiteTokenAccountBody),
835    #[serde(rename = "createToken")]
836    CreateToken(CreateTokenBody),
837    #[serde(rename = "createTokenAccount")]
838    CreateTokenAccount(CreateTokenAccountBody),
839    #[serde(rename = "directoryAnchor")]
840    DirectoryAnchor(DirectoryAnchorBody),
841    #[serde(rename = "issueTokens")]
842    IssueTokens(IssueTokensBody),
843    #[serde(rename = "lockAccount")]
844    LockAccount(LockAccountBody),
845    #[serde(rename = "networkMaintenance")]
846    NetworkMaintenance(NetworkMaintenanceBody),
847    #[serde(rename = "remoteTransaction")]
848    RemoteTransaction(RemoteTransactionBody),
849    #[serde(rename = "sendTokens")]
850    SendTokens(SendTokensBody),
851    #[serde(rename = "systemGenesis")]
852    SystemGenesis(SystemGenesisBody),
853    #[serde(rename = "systemWriteData")]
854    SystemWriteData(SystemWriteDataBody),
855    #[serde(rename = "transferCredits")]
856    TransferCredits(TransferCreditsBody),
857    #[serde(rename = "updateAccountAuth")]
858    UpdateAccountAuth(UpdateAccountAuthBody),
859    #[serde(rename = "updateKey")]
860    UpdateKey(UpdateKeyBody),
861    #[serde(rename = "updateKeyPage")]
862    UpdateKeyPage(UpdateKeyPageBody),
863    #[serde(rename = "writeData")]
864    WriteData(WriteDataBody),
865    #[serde(rename = "writeDataTo")]
866    WriteDataTo(WriteDataToBody),
867}
868
869impl TransactionBody {
870    pub fn validate(&self) -> Result<(), Error> {
871        match self {
872            TransactionBody::AcmeFaucet(b) => b.validate(),
873            TransactionBody::ActivateProtocolVersion(b) => b.validate(),
874            TransactionBody::AddCredits(b) => b.validate(),
875            TransactionBody::BlockValidatorAnchor(b) => b.validate(),
876            TransactionBody::BurnCredits(b) => b.validate(),
877            TransactionBody::BurnTokens(b) => b.validate(),
878            TransactionBody::CreateDataAccount(b) => b.validate(),
879            TransactionBody::CreateIdentity(b) => b.validate(),
880            TransactionBody::CreateKeyBook(b) => b.validate(),
881            TransactionBody::CreateKeyPage(b) => b.validate(),
882            TransactionBody::CreateLiteTokenAccount(b) => b.validate(),
883            TransactionBody::CreateToken(b) => b.validate(),
884            TransactionBody::CreateTokenAccount(b) => b.validate(),
885            TransactionBody::DirectoryAnchor(b) => b.validate(),
886            TransactionBody::IssueTokens(b) => b.validate(),
887            TransactionBody::LockAccount(b) => b.validate(),
888            TransactionBody::NetworkMaintenance(b) => b.validate(),
889            TransactionBody::RemoteTransaction(b) => b.validate(),
890            TransactionBody::SendTokens(b) => b.validate(),
891            TransactionBody::SystemGenesis(b) => b.validate(),
892            TransactionBody::SystemWriteData(b) => b.validate(),
893            TransactionBody::TransferCredits(b) => b.validate(),
894            TransactionBody::UpdateAccountAuth(b) => b.validate(),
895            TransactionBody::UpdateKey(b) => b.validate(),
896            TransactionBody::UpdateKeyPage(b) => b.validate(),
897            TransactionBody::WriteData(b) => b.validate(),
898            TransactionBody::WriteDataTo(b) => b.validate(),
899        }
900    }
901}
902
903#[cfg(test)]
904pub fn __minimal_tx_body_json(wire_tag: &str) -> serde_json::Value {
905    match wire_tag {
906        "acmeFaucet" => serde_json::json!({
907            "type": "acmeFaucet",
908            "Url": ""
909        }),
910        "activateProtocolVersion" => serde_json::json!({
911            "type": "activateProtocolVersion"
912        }),
913        "addCredits" => serde_json::json!({
914            "type": "addCredits",
915            "Recipient": "",
916            "Amount": "0",
917            "Oracle": 0
918        }),
919        "blockValidatorAnchor" => serde_json::json!({
920            "type": "blockValidatorAnchor",
921            "AcmeBurnt": "0"
922        }),
923        "burnCredits" => serde_json::json!({
924            "type": "burnCredits",
925            "Amount": 0
926        }),
927        "burnTokens" => serde_json::json!({
928            "type": "burnTokens",
929            "Amount": "0"
930        }),
931        "createDataAccount" => serde_json::json!({
932            "type": "createDataAccount",
933            "Url": ""
934        }),
935        "createIdentity" => serde_json::json!({
936            "type": "createIdentity",
937            "Url": ""
938        }),
939        "createKeyBook" => serde_json::json!({
940            "type": "createKeyBook",
941            "Url": "",
942            "PublicKeyHash": "00"
943        }),
944        "createKeyPage" => serde_json::json!({
945            "type": "createKeyPage",
946            "Keys": []
947        }),
948        "createLiteTokenAccount" => serde_json::json!({
949            "type": "createLiteTokenAccount"
950        }),
951        "createToken" => serde_json::json!({
952            "type": "createToken",
953            "Url": "",
954            "Symbol": "",
955            "Precision": 0
956        }),
957        "createTokenAccount" => serde_json::json!({
958            "type": "createTokenAccount",
959            "Url": "",
960            "TokenUrl": ""
961        }),
962        "directoryAnchor" => serde_json::json!({
963            "type": "directoryAnchor",
964            "Updates": [],
965            "Receipts": [],
966            "MakeMajorBlock": 0,
967            "MakeMajorBlockTime": {}
968        }),
969        "issueTokens" => serde_json::json!({
970            "type": "issueTokens",
971            "Recipient": "",
972            "Amount": "0",
973            "To": []
974        }),
975        "lockAccount" => serde_json::json!({
976            "type": "lockAccount",
977            "Height": 0
978        }),
979        "networkMaintenance" => serde_json::json!({
980            "type": "networkMaintenance",
981            "Operations": []
982        }),
983        "remoteTransaction" => serde_json::json!({
984            "type": "remoteTransaction"
985        }),
986        "sendTokens" => serde_json::json!({
987            "type": "sendTokens",
988            "To": []
989        }),
990        "systemGenesis" => serde_json::json!({
991            "type": "systemGenesis"
992        }),
993        "systemWriteData" => serde_json::json!({
994            "type": "systemWriteData",
995            "Entry": {}
996        }),
997        "transferCredits" => serde_json::json!({
998            "type": "transferCredits",
999            "To": []
1000        }),
1001        "updateAccountAuth" => serde_json::json!({
1002            "type": "updateAccountAuth",
1003            "Operations": []
1004        }),
1005        "updateKey" => serde_json::json!({
1006            "type": "updateKey",
1007            "NewKeyHash": "00"
1008        }),
1009        "updateKeyPage" => serde_json::json!({
1010            "type": "updateKeyPage",
1011            "Operation": []
1012        }),
1013        "writeData" => serde_json::json!({
1014            "type": "writeData",
1015            "Entry": {}
1016        }),
1017        "writeDataTo" => serde_json::json!({
1018            "type": "writeDataTo",
1019            "Recipient": "",
1020            "Entry": {}
1021        }),
1022        _ => serde_json::json!({"type": wire_tag}),
1023    }
1024}
1025
1026#[cfg(test)]
1027pub fn __tx_roundtrip_one(wire_tag: &str) -> Result<(), Box<dyn std::error::Error>> {
1028    let original = __minimal_tx_body_json(wire_tag);
1029    let body: TransactionBody = serde_json::from_value(original.clone())?;
1030    let serialized = serde_json::to_value(&body)?;
1031
1032    if original != serialized {
1033        return Err(format!("Roundtrip mismatch for {}: original != serialized", wire_tag).into());
1034    }
1035
1036    body.validate()?;
1037    Ok(())
1038}
1039
1040#[cfg(test)]
1041pub fn __test_all_tx_roundtrips() -> Result<(), Box<dyn std::error::Error>> {
1042        __tx_roundtrip_one("acmeFaucet");
1043        __tx_roundtrip_one("activateProtocolVersion");
1044        __tx_roundtrip_one("addCredits");
1045        __tx_roundtrip_one("blockValidatorAnchor");
1046        __tx_roundtrip_one("burnCredits");
1047        __tx_roundtrip_one("burnTokens");
1048        __tx_roundtrip_one("createDataAccount");
1049        __tx_roundtrip_one("createIdentity");
1050        __tx_roundtrip_one("createKeyBook");
1051        __tx_roundtrip_one("createKeyPage");
1052        __tx_roundtrip_one("createLiteTokenAccount");
1053        __tx_roundtrip_one("createToken");
1054        __tx_roundtrip_one("createTokenAccount");
1055        __tx_roundtrip_one("directoryAnchor");
1056        __tx_roundtrip_one("issueTokens");
1057        __tx_roundtrip_one("lockAccount");
1058        __tx_roundtrip_one("networkMaintenance");
1059        __tx_roundtrip_one("remoteTransaction");
1060        __tx_roundtrip_one("sendTokens");
1061        __tx_roundtrip_one("systemGenesis");
1062        __tx_roundtrip_one("systemWriteData");
1063        __tx_roundtrip_one("transferCredits");
1064        __tx_roundtrip_one("updateAccountAuth");
1065        __tx_roundtrip_one("updateKey");
1066        __tx_roundtrip_one("updateKeyPage");
1067        __tx_roundtrip_one("writeData");
1068        __tx_roundtrip_one("writeDataTo");
1069    Ok(())
1070}