Skip to main content

accumulate_client/
types.rs

1//! GENERATED BY Accumulate gen-sdk. DO NOT EDIT.
2
3#![allow(missing_docs)]
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8// Generated types would go here - for now using handcrafted common types
9
10// Common types that are always needed
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub struct StatusResponse {
14    pub network: String,
15    pub version: String,
16    pub commit: String,
17    pub node_info: NodeInfo,
18}
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub struct NodeInfo {
22    pub id: String,
23    pub listen_addr: String,
24    pub network: String,
25    pub version: String,
26    pub channels: String,
27    pub moniker: String,
28    pub other: HashMap<String, serde_json::Value>,
29}
30
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct QueryResponse<T> {
33    pub result: T,
34    pub height: i64,
35    pub proof: Option<serde_json::Value>,
36}
37
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39pub struct TransactionResponse {
40    pub txid: String,
41    pub hash: String,
42    pub height: i64,
43    pub index: i32,
44    pub tx: serde_json::Value,
45    pub tx_result: TransactionResult,
46}
47
48#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
49pub struct TransactionResult {
50    pub code: i32,
51    pub data: Option<String>,
52    pub log: String,
53    pub info: String,
54    pub gas_wanted: String,
55    pub gas_used: String,
56    pub events: Vec<Event>,
57    pub codespace: String,
58}
59
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61pub struct Event {
62    #[serde(rename = "type")]
63    pub event_type: String,
64    pub attributes: Vec<Attribute>,
65}
66
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68pub struct Attribute {
69    pub key: String,
70    pub value: String,
71    pub index: bool,
72}
73
74#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct SignedTransaction {
76    pub body: serde_json::Value,
77    pub signatures: Vec<Signature>,
78}
79
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
81pub struct Signature {
82    pub public_key: String,
83    pub signature: String,
84    #[serde(rename = "type")]
85    pub signature_type: String,
86}
87
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89pub struct Account {
90    pub url: String,
91    #[serde(rename = "type")]
92    pub account_type: String,
93    pub data: serde_json::Value,
94    pub credits: Option<i64>,
95    pub nonce: Option<i64>,
96}
97
98#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct FaucetResponse {
101    /// Transaction ID - may be "txid", "transactionHash", or "hash"
102    #[serde(default, alias = "transactionHash", alias = "hash")]
103    pub txid: String,
104    #[serde(default)]
105    pub link: String,
106    #[serde(default)]
107    pub account: String,
108    #[serde(default)]
109    pub amount: String,
110    /// Simple hash (V3 response)
111    #[serde(default, alias = "simpleHash")]
112    pub simple_hash: Option<String>,
113}
114
115/// Merkle receipt entry - a node in the proof path
116#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct MerkleReceiptEntry {
119    /// Whether this hash should be applied on the right side
120    #[serde(default)]
121    pub right: bool,
122    /// The hash value at this position in the proof
123    #[serde(with = "hex::serde")]
124    pub hash: Vec<u8>,
125}
126
127/// Merkle receipt - cryptographic proof of inclusion in a Merkle tree
128/// Used for anchoring transactions and proving state inclusion
129#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct MerkleReceipt {
132    /// The starting entry hash for which we want a proof
133    #[serde(with = "hex::serde")]
134    pub start: Vec<u8>,
135    /// Index of the start entry in the chain
136    #[serde(default)]
137    pub start_index: i64,
138    /// The ending entry hash at the anchor point
139    #[serde(with = "hex::serde")]
140    pub end: Vec<u8>,
141    /// Index of the end entry in the chain
142    #[serde(default)]
143    pub end_index: i64,
144    /// The Merkle root hash expected after applying all proof entries
145    #[serde(with = "hex::serde")]
146    pub anchor: Vec<u8>,
147    /// The list of hashes in the proof path
148    #[serde(default)]
149    pub entries: Vec<MerkleReceiptEntry>,
150}
151
152impl MerkleReceipt {
153    /// Create an empty receipt
154    pub fn new() -> Self {
155        Self {
156            start: Vec::new(),
157            start_index: 0,
158            end: Vec::new(),
159            end_index: 0,
160            anchor: Vec::new(),
161            entries: Vec::new(),
162        }
163    }
164
165    /// Validate the receipt structure
166    pub fn validate(&self) -> Result<(), crate::errors::Error> {
167        use crate::errors::ValidationError;
168
169        // Start hash should be 32 bytes (SHA-256) or empty
170        if !self.start.is_empty() && self.start.len() != 32 {
171            return Err(ValidationError::InvalidHash {
172                expected: 32,
173                actual: self.start.len(),
174            }.into());
175        }
176
177        // End hash should be 32 bytes (SHA-256) or empty
178        if !self.end.is_empty() && self.end.len() != 32 {
179            return Err(ValidationError::InvalidHash {
180                expected: 32,
181                actual: self.end.len(),
182            }.into());
183        }
184
185        // Anchor hash should be 32 bytes (SHA-256) or empty
186        if !self.anchor.is_empty() && self.anchor.len() != 32 {
187            return Err(ValidationError::InvalidHash {
188                expected: 32,
189                actual: self.anchor.len(),
190            }.into());
191        }
192
193        // Validate each entry hash
194        for (i, entry) in self.entries.iter().enumerate() {
195            if entry.hash.len() != 32 {
196                return Err(ValidationError::InvalidFieldValue {
197                    field: format!("entries[{}].hash", i),
198                    reason: format!("expected 32 bytes, got {}", entry.hash.len()),
199                }.into());
200            }
201        }
202
203        // Start index should not be negative
204        if self.start_index < 0 {
205            return Err(ValidationError::OutOfRange {
206                field: "startIndex".to_string(),
207                min: "0".to_string(),
208                max: "i64::MAX".to_string(),
209            }.into());
210        }
211
212        // End index should not be negative
213        if self.end_index < 0 {
214            return Err(ValidationError::OutOfRange {
215                field: "endIndex".to_string(),
216                min: "0".to_string(),
217                max: "i64::MAX".to_string(),
218            }.into());
219        }
220
221        // End index should be >= start index
222        if self.end_index < self.start_index {
223            return Err(ValidationError::InvalidFieldValue {
224                field: "endIndex".to_string(),
225                reason: format!("endIndex ({}) must be >= startIndex ({})", self.end_index, self.start_index),
226            }.into());
227        }
228
229        Ok(())
230    }
231
232    /// Verify the receipt by computing the Merkle proof
233    /// Returns true if applying the entries produces the expected anchor
234    pub fn verify(&self) -> Result<bool, crate::errors::Error> {
235        use sha2::{Sha256, Digest};
236
237        self.validate()?;
238
239        if self.start.is_empty() || self.anchor.is_empty() {
240            return Ok(false);
241        }
242
243        let mut current: [u8; 32] = self.start.clone().try_into()
244            .map_err(|_| crate::errors::Error::General("Invalid start hash length".to_string()))?;
245
246        // Apply each entry in the proof path
247        for entry in &self.entries {
248            let entry_hash: [u8; 32] = entry.hash.clone().try_into()
249                .map_err(|_| crate::errors::Error::General("Invalid entry hash length".to_string()))?;
250
251            let mut hasher = Sha256::new();
252            if entry.right {
253                // Entry goes on the right: hash(current || entry)
254                hasher.update(&current);
255                hasher.update(&entry_hash);
256            } else {
257                // Entry goes on the left: hash(entry || current)
258                hasher.update(&entry_hash);
259                hasher.update(&current);
260            }
261            current = hasher.finalize().into();
262        }
263
264        // Compare computed root with expected anchor
265        Ok(current.as_slice() == self.anchor.as_slice())
266    }
267
268    /// Check if the receipt is empty
269    pub fn is_empty(&self) -> bool {
270        self.start.is_empty() && self.end.is_empty() && self.anchor.is_empty() && self.entries.is_empty()
271    }
272}
273
274impl Default for MerkleReceipt {
275    fn default() -> Self {
276        Self::new()
277    }
278}
279
280impl std::hash::Hash for MerkleReceipt {
281    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
282        self.start.hash(state);
283        self.start_index.hash(state);
284        self.end.hash(state);
285        self.end_index.hash(state);
286        self.anchor.hash(state);
287        self.entries.hash(state);
288    }
289}
290
291// V3 specific types
292#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
293pub struct V3SubmitRequest {
294    pub envelope: TransactionEnvelope,
295}
296
297#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
298pub struct V3SubmitResponse {
299    pub hash: String,
300    pub result: SubmitResult,
301}
302
303#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
304pub struct SubmitResult {
305    pub code: i32,
306    pub message: String,
307    pub data: Option<serde_json::Value>,
308}
309
310#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
311pub struct TransactionEnvelope {
312    pub transaction: serde_json::Value,
313    pub signatures: Vec<V3Signature>,
314    pub metadata: Option<serde_json::Value>,
315}
316
317#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
318pub struct V3Signature {
319    pub public_key: Vec<u8>,
320    pub signature: Vec<u8>,
321    pub timestamp: i64,
322    pub vote: Option<String>,
323}
324
325// ============================================================================
326// V3 API Service Types
327// ============================================================================
328
329/// Node information returned by NodeInfo service
330#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
331#[serde(rename_all = "camelCase")]
332pub struct V3NodeInfo {
333    /// The peer ID of this node
334    #[serde(default)]
335    pub peer_id: String,
336    /// Network name
337    #[serde(default)]
338    pub network: String,
339    /// Services provided by this node
340    #[serde(default)]
341    pub services: Vec<ServiceAddress>,
342    /// Node software version
343    #[serde(default)]
344    pub version: String,
345    /// Git commit hash
346    #[serde(default)]
347    pub commit: String,
348}
349
350/// Service address identifying a network service
351#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
352#[serde(rename_all = "camelCase")]
353pub struct ServiceAddress {
354    /// Service type identifier
355    #[serde(rename = "type")]
356    pub service_type: String,
357    /// Optional service argument
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub argument: Option<String>,
360}
361
362/// Options for NodeInfo request
363#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
364#[serde(rename_all = "camelCase")]
365pub struct NodeInfoOptions {
366    /// Optional peer ID to query
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub peer_id: Option<String>,
369}
370
371/// Consensus status information
372#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
373#[serde(rename_all = "camelCase")]
374pub struct V3ConsensusStatus {
375    /// Whether the node is operational
376    #[serde(default)]
377    pub ok: bool,
378    /// Last block information
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub last_block: Option<LastBlock>,
381    /// Node software version
382    #[serde(default)]
383    pub version: String,
384    /// Git commit hash
385    #[serde(default)]
386    pub commit: String,
387    /// Hash of the node's public key
388    #[serde(default)]
389    pub node_key_hash: String,
390    /// Hash of the validator's public key
391    #[serde(default)]
392    pub validator_key_hash: String,
393    /// Partition identifier
394    #[serde(default)]
395    pub partition_id: String,
396    /// Partition type (Directory, BlockValidator)
397    #[serde(default)]
398    pub partition_type: String,
399    /// Connected peers
400    #[serde(default)]
401    pub peers: Vec<ConsensusPeerInfo>,
402}
403
404/// Last block information
405#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
406#[serde(rename_all = "camelCase")]
407pub struct LastBlock {
408    /// Block height
409    #[serde(default)]
410    pub height: i64,
411    /// Block timestamp
412    #[serde(default)]
413    pub time: String,
414    /// Chain root hash
415    #[serde(default)]
416    pub chain_root: String,
417    /// State root hash
418    #[serde(default)]
419    pub state_root: String,
420    /// Directory anchor height
421    #[serde(default)]
422    pub directory_anchor_height: u64,
423}
424
425/// Consensus peer information
426#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
427#[serde(rename_all = "camelCase")]
428pub struct ConsensusPeerInfo {
429    /// Node ID
430    #[serde(default)]
431    pub node_id: String,
432    /// Host address
433    #[serde(default)]
434    pub host: String,
435    /// Port number
436    #[serde(default)]
437    pub port: u64,
438}
439
440/// Options for ConsensusStatus request
441#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
442#[serde(rename_all = "camelCase")]
443pub struct ConsensusStatusOptions {
444    /// Node ID to query
445    #[serde(skip_serializing_if = "Option::is_none")]
446    pub node_id: Option<String>,
447    /// Partition to query
448    #[serde(skip_serializing_if = "Option::is_none")]
449    pub partition: Option<String>,
450    /// Include connected peers in response
451    #[serde(skip_serializing_if = "Option::is_none")]
452    pub include_peers: Option<bool>,
453    /// Include Accumulate-specific info
454    #[serde(skip_serializing_if = "Option::is_none")]
455    pub include_accumulate: Option<bool>,
456}
457
458/// Network status information
459#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
460#[serde(rename_all = "camelCase")]
461pub struct V3NetworkStatus {
462    /// ACME oracle price information
463    #[serde(skip_serializing_if = "Option::is_none")]
464    pub oracle: Option<AcmeOracle>,
465    /// Network global settings
466    #[serde(skip_serializing_if = "Option::is_none")]
467    pub globals: Option<serde_json::Value>,
468    /// Network definition
469    #[serde(skip_serializing_if = "Option::is_none")]
470    pub network: Option<serde_json::Value>,
471    /// Routing table
472    #[serde(skip_serializing_if = "Option::is_none")]
473    pub routing: Option<serde_json::Value>,
474    /// Active executor version
475    #[serde(skip_serializing_if = "Option::is_none")]
476    pub executor_version: Option<String>,
477    /// Directory network height
478    #[serde(default)]
479    pub directory_height: u64,
480    /// Major block height
481    #[serde(default)]
482    pub major_block_height: u64,
483    /// BVN executor versions
484    #[serde(default)]
485    pub bvn_executor_versions: Vec<PartitionExecutorVersion>,
486}
487
488/// ACME oracle price
489#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
490#[serde(rename_all = "camelCase")]
491pub struct AcmeOracle {
492    /// Price in micro-USD per ACME
493    #[serde(default)]
494    pub price: u64,
495}
496
497/// Partition executor version
498#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
499#[serde(rename_all = "camelCase")]
500pub struct PartitionExecutorVersion {
501    /// Partition identifier
502    #[serde(default)]
503    pub partition: String,
504    /// Executor version
505    #[serde(default)]
506    pub version: String,
507}
508
509/// Options for NetworkStatus request
510#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
511#[serde(rename_all = "camelCase")]
512pub struct NetworkStatusOptions {
513    /// Partition to query
514    #[serde(skip_serializing_if = "Option::is_none")]
515    pub partition: Option<String>,
516}
517
518/// Metrics information
519#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
520#[serde(rename_all = "camelCase")]
521pub struct V3Metrics {
522    /// Transactions per second
523    #[serde(default)]
524    pub tps: f64,
525}
526
527/// Options for Metrics request
528#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
529#[serde(rename_all = "camelCase")]
530pub struct MetricsOptions {
531    /// Partition to query metrics for
532    #[serde(skip_serializing_if = "Option::is_none")]
533    pub partition: Option<String>,
534    /// Window span in blocks
535    #[serde(skip_serializing_if = "Option::is_none")]
536    pub span: Option<u64>,
537}
538
539/// Submission result for validate/submit operations
540#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
541#[serde(rename_all = "camelCase")]
542pub struct V3Submission {
543    /// Transaction status
544    #[serde(skip_serializing_if = "Option::is_none")]
545    pub status: Option<serde_json::Value>,
546    /// Whether the submission was successful
547    #[serde(default)]
548    pub success: bool,
549    /// Message from the consensus engine
550    #[serde(default)]
551    pub message: String,
552}
553
554/// Options for Submit request
555#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
556#[serde(rename_all = "camelCase")]
557pub struct SubmitOptions {
558    /// Verify envelope is well-formed before submitting (default: true)
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub verify: Option<bool>,
561    /// Wait until envelope is accepted or rejected (default: true)
562    #[serde(skip_serializing_if = "Option::is_none")]
563    pub wait: Option<bool>,
564}
565
566/// Options for Validate request
567#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
568#[serde(rename_all = "camelCase")]
569pub struct ValidateOptions {
570    /// Full validation (default: true)
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub full: Option<bool>,
573}
574
575/// Options for Faucet request
576#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
577#[serde(rename_all = "camelCase")]
578pub struct V3FaucetOptions {
579    /// Token URL (default: ACME)
580    #[serde(skip_serializing_if = "Option::is_none")]
581    pub token: Option<String>,
582}
583
584/// Snapshot information
585#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
586#[serde(rename_all = "camelCase")]
587pub struct V3SnapshotInfo {
588    /// Snapshot header
589    #[serde(skip_serializing_if = "Option::is_none")]
590    pub header: Option<serde_json::Value>,
591    /// Consensus genesis document
592    #[serde(skip_serializing_if = "Option::is_none")]
593    pub consensus_info: Option<serde_json::Value>,
594}
595
596/// Options for ListSnapshots request
597#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
598#[serde(rename_all = "camelCase")]
599pub struct ListSnapshotsOptions {
600    /// Node ID to query
601    #[serde(skip_serializing_if = "Option::is_none")]
602    pub node_id: Option<String>,
603    /// Partition to query
604    #[serde(skip_serializing_if = "Option::is_none")]
605    pub partition: Option<String>,
606}
607
608/// Options for FindService request
609#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
610#[serde(rename_all = "camelCase")]
611pub struct FindServiceOptions {
612    /// Network to search in
613    #[serde(skip_serializing_if = "Option::is_none")]
614    pub network: Option<String>,
615    /// Service to find
616    #[serde(skip_serializing_if = "Option::is_none")]
617    pub service: Option<ServiceAddress>,
618    /// Restrict to known peers only
619    #[serde(skip_serializing_if = "Option::is_none")]
620    pub known: Option<bool>,
621    /// Timeout for DHT query (milliseconds)
622    #[serde(skip_serializing_if = "Option::is_none")]
623    pub timeout: Option<u64>,
624}
625
626/// FindService result
627#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
628#[serde(rename_all = "camelCase")]
629pub struct FindServiceResult {
630    /// Peer ID
631    #[serde(default)]
632    pub peer_id: String,
633    /// Known peer status
634    #[serde(default)]
635    pub status: String,
636    /// Peer addresses
637    #[serde(default)]
638    pub addresses: Vec<String>,
639}
640
641/// Options for Subscribe (events) request
642#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
643#[serde(rename_all = "camelCase")]
644pub struct SubscribeOptions {
645    /// Partition to subscribe to
646    #[serde(skip_serializing_if = "Option::is_none")]
647    pub partition: Option<String>,
648    /// Account to subscribe to
649    #[serde(skip_serializing_if = "Option::is_none")]
650    pub account: Option<String>,
651}
652
653// ============================================================================
654// V3 Query Types
655// ============================================================================
656
657/// Range options for paginated queries
658#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
659#[serde(rename_all = "camelCase")]
660pub struct RangeOptions {
661    /// Starting index
662    #[serde(skip_serializing_if = "Option::is_none")]
663    pub start: Option<u64>,
664    /// Number of results to return
665    #[serde(skip_serializing_if = "Option::is_none")]
666    pub count: Option<u64>,
667    /// Expand results with full data
668    #[serde(skip_serializing_if = "Option::is_none")]
669    pub expand: Option<bool>,
670    /// Query from the end of the range
671    #[serde(skip_serializing_if = "Option::is_none")]
672    pub from_end: Option<bool>,
673}
674
675/// Receipt options for queries
676#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
677#[serde(rename_all = "camelCase")]
678pub struct ReceiptOptions {
679    /// Include receipt for any height
680    #[serde(skip_serializing_if = "Option::is_none")]
681    pub for_any: Option<bool>,
682    /// Include receipt for specific height
683    #[serde(skip_serializing_if = "Option::is_none")]
684    pub for_height: Option<u64>,
685}
686
687/// Default query - basic account/transaction query
688#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
689#[serde(rename_all = "camelCase")]
690pub struct DefaultQuery {
691    /// Include receipt in response
692    #[serde(skip_serializing_if = "Option::is_none")]
693    pub include_receipt: Option<ReceiptOptions>,
694}
695
696/// Chain query - query chain data for an account
697#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
698#[serde(rename_all = "camelCase")]
699pub struct ChainQuery {
700    /// Chain name (e.g., "main", "signature", "pending")
701    #[serde(skip_serializing_if = "Option::is_none")]
702    pub name: Option<String>,
703    /// Query specific index
704    #[serde(skip_serializing_if = "Option::is_none")]
705    pub index: Option<u64>,
706    /// Query by entry hash
707    #[serde(skip_serializing_if = "Option::is_none")]
708    pub entry: Option<String>,
709    /// Range options for paginated results
710    #[serde(skip_serializing_if = "Option::is_none")]
711    pub range: Option<RangeOptions>,
712    /// Include receipt in response
713    #[serde(skip_serializing_if = "Option::is_none")]
714    pub include_receipt: Option<ReceiptOptions>,
715}
716
717impl ChainQuery {
718    /// Validate the query parameters
719    pub fn validate(&self) -> Result<(), crate::errors::Error> {
720        let has_name = self.name.is_some();
721        let has_index = self.index.is_some();
722        let has_entry = self.entry.is_some();
723        let has_range = self.range.is_some();
724
725        if has_range && (has_index || has_entry) {
726            return Err(crate::errors::ValidationError::InvalidFieldValue {
727                field: "range".to_string(),
728                reason: "range is mutually exclusive with index and entry".to_string(),
729            }.into());
730        }
731
732        if !has_name && (has_index || has_entry || has_range) {
733            return Err(crate::errors::ValidationError::RequiredFieldMissing(
734                "name is required when querying by index, entry, or range".to_string(),
735            ).into());
736        }
737
738        Ok(())
739    }
740}
741
742/// Data query - query data entries for a data account
743#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
744#[serde(rename_all = "camelCase")]
745pub struct DataQuery {
746    /// Query specific index
747    #[serde(skip_serializing_if = "Option::is_none")]
748    pub index: Option<u64>,
749    /// Query by entry hash
750    #[serde(skip_serializing_if = "Option::is_none")]
751    pub entry: Option<String>,
752    /// Range options for paginated results
753    #[serde(skip_serializing_if = "Option::is_none")]
754    pub range: Option<RangeOptions>,
755}
756
757impl DataQuery {
758    /// Validate the query parameters
759    pub fn validate(&self) -> Result<(), crate::errors::Error> {
760        let has_index = self.index.is_some();
761        let has_entry = self.entry.is_some();
762        let has_range = self.range.is_some();
763
764        if has_range && (has_index || has_entry) {
765            return Err(crate::errors::ValidationError::InvalidFieldValue {
766                field: "range".to_string(),
767                reason: "range is mutually exclusive with index and entry".to_string(),
768            }.into());
769        }
770
771        Ok(())
772    }
773}
774
775/// Directory query - query sub-accounts of an identity
776#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
777#[serde(rename_all = "camelCase")]
778pub struct DirectoryQuery {
779    /// Range options for paginated results
780    #[serde(skip_serializing_if = "Option::is_none")]
781    pub range: Option<RangeOptions>,
782}
783
784/// Pending query - query pending transactions
785#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
786#[serde(rename_all = "camelCase")]
787pub struct PendingQuery {
788    /// Range options for paginated results
789    #[serde(skip_serializing_if = "Option::is_none")]
790    pub range: Option<RangeOptions>,
791}
792
793/// Block query - query block information
794#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
795#[serde(rename_all = "camelCase")]
796pub struct BlockQuery {
797    /// Query by minor block index
798    #[serde(skip_serializing_if = "Option::is_none")]
799    pub minor: Option<u64>,
800    /// Query by major block index
801    #[serde(skip_serializing_if = "Option::is_none")]
802    pub major: Option<u64>,
803    /// Range of minor blocks
804    #[serde(skip_serializing_if = "Option::is_none")]
805    pub minor_range: Option<RangeOptions>,
806    /// Range of major blocks
807    #[serde(skip_serializing_if = "Option::is_none")]
808    pub major_range: Option<RangeOptions>,
809    /// Range of entries within a block
810    #[serde(skip_serializing_if = "Option::is_none")]
811    pub entry_range: Option<RangeOptions>,
812    /// Omit empty (unrecorded) blocks
813    #[serde(skip_serializing_if = "Option::is_none")]
814    pub omit_empty: Option<bool>,
815}
816
817impl BlockQuery {
818    /// Validate the query parameters
819    pub fn validate(&self) -> Result<(), crate::errors::Error> {
820        let has_minor = self.minor.is_some();
821        let has_major = self.major.is_some();
822        let has_minor_range = self.minor_range.is_some();
823        let has_major_range = self.major_range.is_some();
824        let has_entry_range = self.entry_range.is_some();
825
826        if !has_minor && !has_major && !has_minor_range && !has_major_range {
827            return Err(crate::errors::ValidationError::RequiredFieldMissing(
828                "minor, major, minor_range, or major_range must be specified".to_string(),
829            ).into());
830        }
831        if has_minor && has_major {
832            return Err(crate::errors::ValidationError::InvalidFieldValue {
833                field: "minor/major".to_string(),
834                reason: "minor and major are mutually exclusive".to_string(),
835            }.into());
836        }
837        if has_minor_range && has_major_range {
838            return Err(crate::errors::ValidationError::InvalidFieldValue {
839                field: "minor_range/major_range".to_string(),
840                reason: "minor_range and major_range are mutually exclusive".to_string(),
841            }.into());
842        }
843        if has_minor && (has_minor_range || has_major_range) {
844            return Err(crate::errors::ValidationError::InvalidFieldValue {
845                field: "minor".to_string(),
846                reason: "minor is mutually exclusive with minor_range and major_range".to_string(),
847            }.into());
848        }
849        if has_major && has_major_range {
850            return Err(crate::errors::ValidationError::InvalidFieldValue {
851                field: "major".to_string(),
852                reason: "major and major_range are mutually exclusive".to_string(),
853            }.into());
854        }
855        if has_entry_range && (has_major || has_minor_range || has_major_range) {
856            return Err(crate::errors::ValidationError::InvalidFieldValue {
857                field: "entry_range".to_string(),
858                reason: "entry_range is mutually exclusive with major, minor_range, and major_range".to_string(),
859            }.into());
860        }
861
862        Ok(())
863    }
864}
865
866/// Anchor search query - search by anchor hash
867#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
868#[serde(rename_all = "camelCase")]
869pub struct AnchorSearchQuery {
870    /// Anchor hash to search for (32 bytes, hex-encoded)
871    pub anchor: String,
872    /// Include receipt in response
873    #[serde(skip_serializing_if = "Option::is_none")]
874    pub include_receipt: Option<ReceiptOptions>,
875}
876
877/// Public key search query - search signers by public key
878#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
879#[serde(rename_all = "camelCase")]
880pub struct PublicKeySearchQuery {
881    /// Public key to search for (hex-encoded)
882    pub public_key: String,
883    /// Signature type (e.g., "ed25519", "btc", "eth")
884    #[serde(rename = "type")]
885    pub signature_type: String,
886}
887
888/// Public key hash search query - search signers by public key hash
889#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
890#[serde(rename_all = "camelCase")]
891pub struct PublicKeyHashSearchQuery {
892    /// Public key hash to search for (hex-encoded)
893    pub public_key_hash: String,
894}
895
896/// Delegate search query - search for delegated keys
897#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
898#[serde(rename_all = "camelCase")]
899pub struct DelegateSearchQuery {
900    /// Delegate URL to search for
901    pub delegate: String,
902}
903
904/// Message hash search query - search by message/transaction hash
905#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
906#[serde(rename_all = "camelCase")]
907pub struct MessageHashSearchQuery {
908    /// Message hash to search for (32 bytes, hex-encoded)
909    pub hash: String,
910}
911
912/// Query union type - represents all possible query types
913#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
914#[serde(tag = "queryType", rename_all = "camelCase")]
915pub enum V3Query {
916    /// Default query for account/transaction
917    #[serde(rename = "default")]
918    Default(DefaultQuery),
919    /// Chain query
920    #[serde(rename = "chain")]
921    Chain(ChainQuery),
922    /// Data query
923    #[serde(rename = "data")]
924    Data(DataQuery),
925    /// Directory query
926    #[serde(rename = "directory")]
927    Directory(DirectoryQuery),
928    /// Pending query
929    #[serde(rename = "pending")]
930    Pending(PendingQuery),
931    /// Block query
932    #[serde(rename = "block")]
933    Block(BlockQuery),
934    /// Anchor search query
935    #[serde(rename = "anchorSearch")]
936    AnchorSearch(AnchorSearchQuery),
937    /// Public key search query
938    #[serde(rename = "publicKeySearch")]
939    PublicKeySearch(PublicKeySearchQuery),
940    /// Public key hash search query
941    #[serde(rename = "publicKeyHashSearch")]
942    PublicKeyHashSearch(PublicKeyHashSearchQuery),
943    /// Delegate search query
944    #[serde(rename = "delegateSearch")]
945    DelegateSearch(DelegateSearchQuery),
946    /// Message hash search query
947    #[serde(rename = "messageHashSearch")]
948    MessageHashSearch(MessageHashSearchQuery),
949}
950
951impl V3Query {
952    /// Validate the query
953    pub fn validate(&self) -> Result<(), crate::errors::Error> {
954        match self {
955            V3Query::Chain(q) => q.validate(),
956            V3Query::Data(q) => q.validate(),
957            V3Query::Block(q) => q.validate(),
958            _ => Ok(()),
959        }
960    }
961}