1#[cfg(feature = "pyo3")]
4use anyhow::Context;
5use anyhow::{anyhow, Result};
6use serde::{Deserialize, Serialize};
7
8#[derive(Default, Debug, Clone)]
10#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
11pub struct Query {
12 pub from_block: u64,
13 pub to_block: Option<u64>,
14 pub include_all_blocks: bool,
15 pub fields: Fields,
16 pub instructions: Vec<InstructionRequest>,
17 pub transactions: Vec<TransactionRequest>,
18 pub logs: Vec<LogRequest>,
19 pub balances: Vec<BalanceRequest>,
20 pub token_balances: Vec<TokenBalanceRequest>,
21 pub rewards: Vec<RewardRequest>,
22}
23
24#[derive(Debug, Clone, Copy)]
26pub struct Address(pub [u8; 32]);
27
28#[derive(Debug, Clone)]
30pub struct Data(pub Vec<u8>);
31
32#[derive(Debug, Clone, Copy)]
34pub struct D1(pub [u8; 1]);
35
36#[derive(Debug, Clone, Copy)]
38pub struct D2(pub [u8; 2]);
39
40#[derive(Debug, Clone, Copy)]
42pub struct D3(pub [u8; 3]);
43
44#[derive(Debug, Clone, Copy)]
46pub struct D4(pub [u8; 4]);
47
48#[derive(Debug, Clone, Copy)]
50pub struct D8(pub [u8; 8]);
51
52#[cfg(feature = "pyo3")]
53fn extract_base58<const N: usize>(ob: &pyo3::Bound<'_, pyo3::PyAny>) -> pyo3::PyResult<[u8; N]> {
54 use pyo3::types::PyAnyMethods;
55
56 let s: &str = ob.extract()?;
57 let mut out = [0; N];
58
59 bs58::decode(s)
60 .with_alphabet(bs58::Alphabet::BITCOIN)
61 .onto(&mut out)
62 .context("decode base58")?;
63
64 Ok(out)
65}
66
67#[cfg(feature = "pyo3")]
68fn extract_data<const N: usize>(ob: &pyo3::Bound<'_, pyo3::PyAny>) -> pyo3::PyResult<[u8; N]> {
69 use pyo3::types::PyAnyMethods;
70 use pyo3::types::PyTypeMethods;
71
72 let ob_type: String = ob.get_type().name()?.to_string();
73 match ob_type.as_str() {
74 "str" => {
75 let s: &str = ob.extract()?;
76 let out = hex_to_bytes(s).context("failed to decode hex")?;
77 if out.len() != N {
78 return Err(anyhow!("expected length {}, got {}", N, out.len()).into());
79 }
80 let out: [u8; N] = out
81 .try_into()
82 .map_err(|e| anyhow!("failed to convert to array: {e:?}"))?;
83 Ok(out)
84 }
85 "bytes" => {
86 let out: Vec<u8> = ob.extract()?;
87 if out.len() != N {
88 return Err(anyhow!("expected length {}, got {}", N, out.len()).into());
89 }
90 let out: [u8; N] = out
91 .try_into()
92 .map_err(|e| anyhow!("failed to convert to array: {e:?}"))?;
93 Ok(out)
94 }
95 _ => Err(anyhow!("unknown type: {ob_type}").into()),
96 }
97}
98
99#[cfg(feature = "pyo3")]
100fn hex_to_bytes(hex_string: &str) -> Result<Vec<u8>> {
101 let hex_string = hex_string.strip_prefix("0x").unwrap_or(hex_string);
102 let hex_string = if hex_string.len() % 2 == 1 {
103 format!("0{hex_string}",)
104 } else {
105 hex_string.to_string()
106 };
107 let out = (0..hex_string.len())
108 .step_by(2)
109 .map(|i| {
110 u8::from_str_radix(&hex_string[i..i + 2], 16)
111 .context("failed to parse hexstring to bytes")
112 })
113 .collect::<Result<Vec<_>, _>>()?;
114
115 Ok(out)
116}
117
118#[cfg(feature = "pyo3")]
119impl<'py> pyo3::FromPyObject<'py> for Address {
120 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
121 let out = extract_base58(ob)?;
122 Ok(Self(out))
123 }
124}
125
126#[cfg(feature = "pyo3")]
127impl<'py> pyo3::FromPyObject<'py> for Data {
128 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
129 use pyo3::types::PyAnyMethods;
130 use pyo3::types::PyTypeMethods;
131
132 let ob_type: String = ob.get_type().name()?.to_string();
133 match ob_type.as_str() {
134 "str" => {
135 let s: &str = ob.extract()?;
136 let out = hex_to_bytes(s).context("failed to decode hex")?;
137 Ok(Self(out))
138 }
139 "bytes" => {
140 let out: Vec<u8> = ob.extract()?;
141 Ok(Self(out))
142 }
143 _ => Err(anyhow!("unknown type: {ob_type}").into()),
144 }
145 }
146}
147
148#[cfg(feature = "pyo3")]
149impl<'py> pyo3::FromPyObject<'py> for D1 {
150 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
151 let out = extract_data(ob)?;
152 Ok(Self(out))
153 }
154}
155
156#[cfg(feature = "pyo3")]
157impl<'py> pyo3::FromPyObject<'py> for D2 {
158 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
159 let out = extract_data(ob)?;
160 Ok(Self(out))
161 }
162}
163
164#[cfg(feature = "pyo3")]
165impl<'py> pyo3::FromPyObject<'py> for D3 {
166 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
167 let out = extract_data(ob)?;
168 Ok(Self(out))
169 }
170}
171
172#[cfg(feature = "pyo3")]
173impl<'py> pyo3::FromPyObject<'py> for D4 {
174 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
175 let out = extract_data(ob)?;
176 Ok(Self(out))
177 }
178}
179
180#[cfg(feature = "pyo3")]
181impl<'py> pyo3::FromPyObject<'py> for D8 {
182 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
183 let out = extract_data(ob)?;
184 Ok(Self(out))
185 }
186}
187
188#[derive(Default, Debug, Clone)]
190#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
191#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
192pub struct InstructionRequest {
193 pub program_id: Vec<Address>,
194 pub discriminator: Vec<Data>,
195 pub d1: Vec<D1>,
196 pub d2: Vec<D2>,
197 pub d3: Vec<D3>,
198 pub d4: Vec<D4>,
199 pub d8: Vec<D8>,
200 pub a0: Vec<Address>,
201 pub a1: Vec<Address>,
202 pub a2: Vec<Address>,
203 pub a3: Vec<Address>,
204 pub a4: Vec<Address>,
205 pub a5: Vec<Address>,
206 pub a6: Vec<Address>,
207 pub a7: Vec<Address>,
208 pub a8: Vec<Address>,
209 pub a9: Vec<Address>,
210 pub is_committed: bool,
211 pub include_transactions: bool,
212 pub include_transaction_token_balances: bool,
213 pub include_logs: bool,
214 pub include_inner_instructions: bool,
215 pub include_blocks: bool,
216}
217
218#[derive(Default, Debug, Clone)]
220#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
221pub struct TransactionRequest {
222 pub fee_payer: Vec<Address>,
223 pub include_instructions: bool,
224 pub include_logs: bool,
225 pub include_blocks: bool,
226}
227
228#[derive(Default, Debug, Clone)]
230#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
231pub struct LogRequest {
232 pub program_id: Vec<Address>,
233 pub kind: Vec<LogKind>,
234 pub include_transactions: bool,
235 pub include_instructions: bool,
236 pub include_blocks: bool,
237}
238
239#[derive(Debug, Clone, Copy)]
241pub enum LogKind {
242 Log,
244 Data,
246 Other,
248}
249
250impl LogKind {
251 pub fn as_str(&self) -> &str {
252 match self {
253 Self::Log => "log",
254 Self::Data => "data",
255 Self::Other => "other",
256 }
257 }
258
259 pub fn from_str(s: &str) -> Result<Self> {
260 match s {
261 "log" => Ok(Self::Log),
262 "data" => Ok(Self::Data),
263 "other" => Ok(Self::Other),
264 _ => Err(anyhow!("unknown log kind: {s}")),
265 }
266 }
267}
268
269#[cfg(feature = "pyo3")]
270impl<'py> pyo3::FromPyObject<'py> for LogKind {
271 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
272 use pyo3::types::PyAnyMethods;
273
274 let s: &str = ob.extract().context("extract string")?;
275
276 Ok(Self::from_str(s).context("from str")?)
277 }
278}
279
280#[derive(Default, Debug, Clone)]
282#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
283pub struct BalanceRequest {
284 pub account: Vec<Address>,
285 pub include_transactions: bool,
286 pub include_transaction_instructions: bool,
287 pub include_blocks: bool,
288}
289
290#[derive(Default, Debug, Clone)]
292#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
293pub struct TokenBalanceRequest {
294 pub account: Vec<Address>,
295 pub pre_program_id: Vec<Address>,
296 pub post_program_id: Vec<Address>,
297 pub pre_mint: Vec<Address>,
298 pub post_mint: Vec<Address>,
299 pub pre_owner: Vec<Address>,
300 pub post_owner: Vec<Address>,
301 pub include_transactions: bool,
302 pub include_transaction_instructions: bool,
303 pub include_blocks: bool,
304}
305
306#[derive(Default, Debug, Clone)]
308#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
309pub struct RewardRequest {
310 pub pubkey: Vec<Address>,
311 pub include_blocks: bool,
312}
313
314#[derive(Deserialize, Serialize, Default, Debug, Clone, Copy)]
316#[serde(default)]
317#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
318pub struct Fields {
319 pub instruction: InstructionFields,
320 pub transaction: TransactionFields,
321 pub log: LogFields,
322 pub balance: BalanceFields,
323 pub token_balance: TokenBalanceFields,
324 pub reward: RewardFields,
325 pub block: BlockFields,
326}
327
328impl Fields {
329 pub fn all() -> Self {
330 Self {
331 instruction: InstructionFields::all(),
332 transaction: TransactionFields::all(),
333 log: LogFields::all(),
334 balance: BalanceFields::all(),
335 token_balance: TokenBalanceFields::all(),
336 reward: RewardFields::all(),
337 block: BlockFields::all(),
338 }
339 }
340}
341
342#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
344#[serde(default)]
345#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
346#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
347pub struct InstructionFields {
348 pub block_slot: bool,
349 pub block_hash: bool,
350 pub transaction_index: bool,
351 pub instruction_address: bool,
352 pub program_id: bool,
353 pub a0: bool,
354 pub a1: bool,
355 pub a2: bool,
356 pub a3: bool,
357 pub a4: bool,
358 pub a5: bool,
359 pub a6: bool,
360 pub a7: bool,
361 pub a8: bool,
362 pub a9: bool,
363 pub rest_of_accounts: bool,
364 pub data: bool,
365 pub d1: bool,
366 pub d2: bool,
367 pub d4: bool,
368 pub d8: bool,
369 pub error: bool,
370 pub compute_units_consumed: bool,
371 pub is_committed: bool,
372 pub has_dropped_log_messages: bool,
373}
374
375impl InstructionFields {
376 pub fn all() -> Self {
377 InstructionFields {
378 block_slot: true,
379 block_hash: true,
380 transaction_index: true,
381 instruction_address: true,
382 program_id: true,
383 a0: true,
384 a1: true,
385 a2: true,
386 a3: true,
387 a4: true,
388 a5: true,
389 a6: true,
390 a7: true,
391 a8: true,
392 a9: true,
393 rest_of_accounts: true,
394 data: true,
395 d1: true,
396 d2: true,
397 d4: true,
398 d8: true,
399 error: true,
400 compute_units_consumed: true,
401 is_committed: true,
402 has_dropped_log_messages: true,
403 }
404 }
405}
406
407#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
409#[serde(default)]
410#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
411#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
412pub struct TransactionFields {
413 pub block_slot: bool,
414 pub block_hash: bool,
415 pub transaction_index: bool,
416 pub signature: bool,
417 pub version: bool,
418 pub account_keys: bool,
419 pub address_table_lookups: bool,
420 pub num_readonly_signed_accounts: bool,
421 pub num_readonly_unsigned_accounts: bool,
422 pub num_required_signatures: bool,
423 pub recent_blockhash: bool,
424 pub signatures: bool,
425 pub err: bool,
426 pub fee: bool,
427 pub compute_units_consumed: bool,
428 pub loaded_readonly_addresses: bool,
429 pub loaded_writable_addresses: bool,
430 pub fee_payer: bool,
431 pub has_dropped_log_messages: bool,
432}
433
434impl TransactionFields {
435 pub fn all() -> Self {
436 TransactionFields {
437 block_slot: true,
438 block_hash: true,
439 transaction_index: true,
440 signature: true,
441 version: true,
442 account_keys: true,
443 address_table_lookups: true,
444 num_readonly_signed_accounts: true,
445 num_readonly_unsigned_accounts: true,
446 num_required_signatures: true,
447 recent_blockhash: true,
448 signatures: true,
449 err: true,
450 fee: true,
451 compute_units_consumed: true,
452 loaded_readonly_addresses: true,
453 loaded_writable_addresses: true,
454 fee_payer: true,
455 has_dropped_log_messages: true,
456 }
457 }
458}
459
460#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
462#[serde(default)]
463#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
464#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
465pub struct LogFields {
466 pub block_slot: bool,
467 pub block_hash: bool,
468 pub transaction_index: bool,
469 pub log_index: bool,
470 pub instruction_address: bool,
471 pub program_id: bool,
472 pub kind: bool,
473 pub message: bool,
474}
475
476impl LogFields {
477 pub fn all() -> Self {
478 LogFields {
479 block_slot: true,
480 block_hash: true,
481 transaction_index: true,
482 log_index: true,
483 instruction_address: true,
484 program_id: true,
485 kind: true,
486 message: true,
487 }
488 }
489}
490
491#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
493#[serde(default)]
494#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
495#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
496pub struct BalanceFields {
497 pub block_slot: bool,
498 pub block_hash: bool,
499 pub transaction_index: bool,
500 pub account: bool,
501 pub pre: bool,
502 pub post: bool,
503}
504
505impl BalanceFields {
506 pub fn all() -> Self {
507 BalanceFields {
508 block_slot: true,
509 block_hash: true,
510 transaction_index: true,
511 account: true,
512 pre: true,
513 post: true,
514 }
515 }
516}
517
518#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
520#[serde(default)]
521#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
522#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
523pub struct TokenBalanceFields {
524 pub block_slot: bool,
525 pub block_hash: bool,
526 pub transaction_index: bool,
527 pub account: bool,
528 pub pre_mint: bool,
529 pub post_mint: bool,
530 pub pre_decimals: bool,
531 pub post_decimals: bool,
532 pub pre_program_id: bool,
533 pub post_program_id: bool,
534 pub pre_owner: bool,
535 pub post_owner: bool,
536 pub pre_amount: bool,
537 pub post_amount: bool,
538}
539
540impl TokenBalanceFields {
541 pub fn all() -> Self {
542 TokenBalanceFields {
543 block_slot: true,
544 block_hash: true,
545 transaction_index: true,
546 account: true,
547 pre_mint: true,
548 post_mint: true,
549 pre_decimals: true,
550 post_decimals: true,
551 pre_program_id: true,
552 post_program_id: true,
553 pre_owner: true,
554 post_owner: true,
555 pre_amount: true,
556 post_amount: true,
557 }
558 }
559}
560
561#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
563#[serde(default)]
564#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
565#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
566pub struct RewardFields {
567 pub block_slot: bool,
568 pub block_hash: bool,
569 pub pubkey: bool,
570 pub lamports: bool,
571 pub post_balance: bool,
572 pub reward_type: bool,
573 pub commission: bool,
574}
575
576impl RewardFields {
577 pub fn all() -> Self {
578 RewardFields {
579 block_slot: true,
580 block_hash: true,
581 pubkey: true,
582 lamports: true,
583 post_balance: true,
584 reward_type: true,
585 commission: true,
586 }
587 }
588}
589
590#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
592#[serde(default)]
593#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
594#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
595pub struct BlockFields {
596 pub slot: bool,
597 pub hash: bool,
598 pub parent_slot: bool,
599 pub parent_hash: bool,
600 pub height: bool,
601 pub timestamp: bool,
602}
603
604impl BlockFields {
605 pub fn all() -> Self {
606 BlockFields {
607 slot: true,
608 hash: true,
609 parent_slot: true,
610 parent_hash: true,
611 height: true,
612 timestamp: true,
613 }
614 }
615}