1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
6pub enum BlockStatusFilter {
7 All,
8 Pending,
9 Canonical,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14pub enum BlockSortBy {
15 #[serde(rename = "BLOCKHEIGHT_ASC")]
16 Asc,
17 #[serde(rename = "BLOCKHEIGHT_DESC")]
18 Desc,
19}
20
21#[derive(Debug, Clone, Default, Serialize)]
37#[serde(rename_all = "camelCase")]
38pub struct EventFilterOptionsInput {
39 pub address: String,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub token_id: Option<String>,
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub status: Option<BlockStatusFilter>,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub from: Option<i32>,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub to: Option<i32>,
50}
51
52impl EventFilterOptionsInput {
53 pub fn for_address(address: impl Into<String>) -> Self {
55 Self {
56 address: address.into(),
57 ..Default::default()
58 }
59 }
60
61 pub fn token_id(mut self, token_id: impl Into<String>) -> Self {
62 self.token_id = Some(token_id.into());
63 self
64 }
65 pub fn status(mut self, status: BlockStatusFilter) -> Self {
66 self.status = Some(status);
67 self
68 }
69 pub fn from(mut self, from: i32) -> Self {
71 self.from = Some(from);
72 self
73 }
74 pub fn to(mut self, to: i32) -> Self {
76 self.to = Some(to);
77 self
78 }
79}
80
81#[derive(Debug, Clone, Default, Serialize)]
97#[serde(rename_all = "camelCase")]
98pub struct ActionFilterOptionsInput {
99 pub address: String,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub token_id: Option<String>,
102 #[serde(skip_serializing_if = "Option::is_none")]
103 pub status: Option<BlockStatusFilter>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub from: Option<i32>,
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub to: Option<i32>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub from_action_state: Option<String>,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub end_action_state: Option<String>,
114}
115
116impl ActionFilterOptionsInput {
117 pub fn for_address(address: impl Into<String>) -> Self {
118 Self {
119 address: address.into(),
120 ..Default::default()
121 }
122 }
123 pub fn token_id(mut self, v: impl Into<String>) -> Self {
124 self.token_id = Some(v.into());
125 self
126 }
127 pub fn status(mut self, status: BlockStatusFilter) -> Self {
128 self.status = Some(status);
129 self
130 }
131 pub fn from(mut self, from: i32) -> Self {
133 self.from = Some(from);
134 self
135 }
136 pub fn to(mut self, to: i32) -> Self {
138 self.to = Some(to);
139 self
140 }
141 pub fn from_action_state(mut self, v: impl Into<String>) -> Self {
142 self.from_action_state = Some(v.into());
143 self
144 }
145 pub fn end_action_state(mut self, v: impl Into<String>) -> Self {
146 self.end_action_state = Some(v.into());
147 self
148 }
149}
150
151#[derive(Debug, Clone, Serialize)]
162#[serde(rename_all = "camelCase")]
163pub struct VerificationKeyUpdateFilterInput {
164 pub verification_key_hash: String,
165 pub from: i32,
167 pub to: i32,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub status: Option<BlockStatusFilter>,
171}
172
173impl VerificationKeyUpdateFilterInput {
174 pub fn new(verification_key_hash: impl Into<String>, from: i32, to: i32) -> Self {
176 Self {
177 verification_key_hash: verification_key_hash.into(),
178 from,
179 to,
180 status: None,
181 }
182 }
183
184 pub fn status(mut self, v: BlockStatusFilter) -> Self {
185 self.status = Some(v);
186 self
187 }
188}
189
190#[derive(Debug, Clone, Default, Serialize)]
197pub struct BlockQueryInput {
198 #[serde(rename = "blockHeight_gte", skip_serializing_if = "Option::is_none")]
199 pub block_height_gte: Option<i32>,
200 #[serde(rename = "blockHeight_lt", skip_serializing_if = "Option::is_none")]
201 pub block_height_lt: Option<i32>,
202 #[serde(rename = "dateTime_gte", skip_serializing_if = "Option::is_none")]
213 pub date_time_gte: Option<String>,
214 #[serde(rename = "dateTime_lt", skip_serializing_if = "Option::is_none")]
220 pub date_time_lt: Option<String>,
221 #[serde(skip_serializing_if = "Option::is_none")]
222 pub canonical: Option<bool>,
223 #[serde(skip_serializing_if = "Option::is_none", rename = "inBestChain")]
224 pub in_best_chain: Option<bool>,
225}
226
227impl BlockQueryInput {
228 pub fn date_time_gte_from_unix_ms(mut self, ms: i64) -> Self {
234 self.date_time_gte = Some(iso8601_from_unix_ms(ms));
235 self
236 }
237
238 pub fn date_time_lt_from_unix_ms(mut self, ms: i64) -> Self {
242 self.date_time_lt = Some(iso8601_from_unix_ms(ms));
243 self
244 }
245}
246
247fn iso8601_from_unix_ms(ms: i64) -> String {
253 const MS_PER_DAY: i64 = 86_400_000;
254 let days = ms.div_euclid(MS_PER_DAY);
255 let mut rem = ms.rem_euclid(MS_PER_DAY);
256
257 let millis = rem % 1000;
258 rem /= 1000;
259 let seconds = rem % 60;
260 rem /= 60;
261 let minutes = rem % 60;
262 let hours = rem / 60;
263
264 let (year, month, day) = civil_from_days(days);
265 format!("{year:04}-{month:02}-{day:02}T{hours:02}:{minutes:02}:{seconds:02}.{millis:03}Z")
266}
267
268fn civil_from_days(days: i64) -> (i64, u32, u32) {
270 let z = days + 719_468;
271 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
272 let doe = z - era * 146_097; let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; let y = yoe + era * 400;
275 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1; let m = if mp < 10 { mp + 3 } else { mp - 9 }; (if m <= 2 { y + 1 } else { y }, m as u32, d as u32)
280}
281
282#[derive(Debug, Clone, Deserialize)]
283#[serde(rename_all = "camelCase")]
284#[non_exhaustive]
285pub struct TransactionInfo {
286 pub status: String,
287 pub hash: String,
288 pub memo: String,
289 pub authorization_kind: String,
290 pub sequence_number: i64,
291 pub zkapp_account_update_ids: Vec<i64>,
292}
293
294#[derive(Debug, Clone, Deserialize)]
295#[serde(rename_all = "camelCase")]
296#[non_exhaustive]
297pub struct EventData {
298 pub account_update_id: String,
299 pub transaction_info: Option<TransactionInfo>,
300 pub data: Vec<String>,
301}
302
303#[derive(Debug, Clone, Deserialize)]
304#[serde(rename_all = "camelCase")]
305#[non_exhaustive]
306pub struct ActionData {
307 pub account_update_id: String,
308 pub transaction_info: Option<TransactionInfo>,
309 pub data: Vec<String>,
310}
311
312#[derive(Debug, Clone, Deserialize)]
313#[serde(rename_all = "camelCase")]
314#[non_exhaustive]
315pub struct BlockInfo {
316 pub height: i64,
317 pub state_hash: String,
318 pub parent_hash: String,
319 pub ledger_hash: String,
320 pub chain_status: String,
321 pub timestamp: String,
333 pub global_slot_since_hardfork: i64,
334 pub global_slot_since_genesis: i64,
335 pub distance_from_max_block_height: i64,
336}
337
338#[derive(Debug, Clone, Deserialize)]
339#[serde(rename_all = "camelCase")]
340#[non_exhaustive]
341pub struct ActionStates {
342 pub action_state_one: Option<String>,
343 pub action_state_two: Option<String>,
344 pub action_state_three: Option<String>,
345 pub action_state_four: Option<String>,
346 pub action_state_five: Option<String>,
347}
348
349#[derive(Debug, Clone, Deserialize)]
350#[serde(rename_all = "camelCase")]
351#[non_exhaustive]
352pub struct EventOutput {
353 pub block_info: Option<BlockInfo>,
354 pub event_data: Option<Vec<EventData>>,
356}
357
358#[derive(Debug, Clone, Deserialize)]
359#[serde(rename_all = "camelCase")]
360#[non_exhaustive]
361pub struct ActionOutput {
362 pub block_info: Option<BlockInfo>,
363 pub transaction_info: Option<TransactionInfo>,
364 pub action_data: Option<Vec<ActionData>>,
366 pub action_state: ActionStates,
367}
368
369#[derive(Debug, Clone, Deserialize)]
371#[serde(rename_all = "camelCase")]
372#[non_exhaustive]
373pub struct VerificationKeyUpdate {
374 pub account_update_id: String,
375 pub address: String,
377 pub token_id: String,
378 pub verification_key_hash: String,
379 pub block_info: BlockInfo,
380 pub transaction_info: TransactionInfo,
381}
382
383#[derive(Debug, Clone, Deserialize)]
384#[serde(rename_all = "camelCase")]
385#[non_exhaustive]
386pub struct MaxBlockHeightInfo {
387 pub canonical_max_block_height: i64,
388 pub pending_max_block_height: i64,
389}
390
391#[derive(Debug, Clone, Deserialize)]
392#[serde(rename_all = "camelCase")]
393#[non_exhaustive]
394pub struct NetworkStateOutput {
395 pub max_block_height: Option<MaxBlockHeightInfo>,
396}
397
398#[derive(Debug, Clone, Deserialize)]
399#[non_exhaustive]
400pub struct UserCommand {
401 pub hash: String,
402 pub kind: String,
403 pub from: String,
404 pub to: String,
405 pub amount: String,
406 pub fee: String,
407 pub memo: String,
408 pub nonce: i64,
409 pub status: String,
410 #[serde(rename = "failureReason")]
411 pub failure_reason: Option<String>,
412}
413
414#[derive(Debug, Clone, Deserialize)]
415#[serde(rename_all = "camelCase")]
416#[non_exhaustive]
417pub struct ZkAppCommand {
418 pub hash: String,
419 pub fee_payer: String,
420 pub fee: String,
421 pub memo: String,
422 pub status: String,
423 pub failure_reason: Option<String>,
424}
425
426#[derive(Debug, Clone, Deserialize)]
427#[non_exhaustive]
428pub struct FeeTransfer {
429 pub recipient: String,
430 pub fee: String,
431 #[serde(rename = "type")]
432 pub kind: String,
433}
434
435#[derive(Debug, Clone, Deserialize)]
441#[serde(rename_all = "camelCase")]
442#[non_exhaustive]
443pub struct BlockTransactions {
444 pub coinbase: String,
445 pub user_commands: Vec<UserCommand>,
446 pub zkapp_commands: Vec<ZkAppCommand>,
447 pub fee_transfer: Vec<FeeTransfer>,
448}
449
450#[derive(Debug, Clone, Deserialize)]
451#[serde(rename_all = "camelCase")]
452#[non_exhaustive]
453pub struct Block {
454 pub block_height: i64,
455 pub creator: String,
456 pub state_hash: String,
457 pub parent_hash: String,
458 pub date_time: String,
465 pub transactions: BlockTransactions,
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471
472 #[test]
475 fn iso8601_from_unix_ms_matches_javascript_to_iso_string() {
476 let cases: &[(i64, &str)] = &[
477 (1_691_971_200_000, "2023-08-14T00:00:00.000Z"),
478 (1_692_054_601_000, "2023-08-14T23:10:01.000Z"),
479 (0, "1970-01-01T00:00:00.000Z"),
480 (-1, "1969-12-31T23:59:59.999Z"),
481 (951_782_400_000, "2000-02-29T00:00:00.000Z"),
482 (4_102_444_800_000, "2100-01-01T00:00:00.000Z"),
483 ];
484 for (ms, expected) in cases {
485 assert_eq!(&iso8601_from_unix_ms(*ms), expected, "for {ms} ms");
486 }
487 }
488
489 #[test]
493 fn typed_constructors_round_trip_through_the_wire_format() {
494 let input = BlockQueryInput::default()
495 .date_time_gte_from_unix_ms(1_691_971_200_000)
496 .date_time_lt_from_unix_ms(1_692_054_601_000);
497
498 let json = serde_json::to_value(&input).unwrap();
499 assert_eq!(json["dateTime_gte"], "2023-08-14T00:00:00.000Z");
500 assert_eq!(json["dateTime_lt"], "2023-08-14T23:10:01.000Z");
501 }
502}