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<'a, 'py> pyo3::FromPyObject<'a, 'py> for Address {
120 type Error = pyo3::PyErr;
121 fn extract(ob: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
122 let out = extract_base58(&ob)?;
123 Ok(Self(out))
124 }
125}
126
127#[cfg(feature = "pyo3")]
128impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for Data {
129 type Error = pyo3::PyErr;
130 fn extract(ob: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
131 use pyo3::types::PyAnyMethods;
132 use pyo3::types::PyTypeMethods;
133
134 let ob_type: String = ob.get_type().name()?.to_string();
135 match ob_type.as_str() {
136 "str" => {
137 let s: &str = ob.extract()?;
138 let out = hex_to_bytes(s).context("failed to decode hex")?;
139 Ok(Self(out))
140 }
141 "bytes" => {
142 let out: Vec<u8> = ob.extract()?;
143 Ok(Self(out))
144 }
145 _ => Err(anyhow!("unknown type: {ob_type}").into()),
146 }
147 }
148}
149
150#[cfg(feature = "pyo3")]
151impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for D1 {
152 type Error = pyo3::PyErr;
153 fn extract(ob: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
154 let out = extract_data(&ob)?;
155 Ok(Self(out))
156 }
157}
158
159#[cfg(feature = "pyo3")]
160impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for D2 {
161 type Error = pyo3::PyErr;
162 fn extract(ob: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
163 let out = extract_data(&ob)?;
164 Ok(Self(out))
165 }
166}
167
168#[cfg(feature = "pyo3")]
169impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for D3 {
170 type Error = pyo3::PyErr;
171 fn extract(ob: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
172 let out = extract_data(&ob)?;
173 Ok(Self(out))
174 }
175}
176
177#[cfg(feature = "pyo3")]
178impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for D4 {
179 type Error = pyo3::PyErr;
180 fn extract(ob: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
181 let out = extract_data(&ob)?;
182 Ok(Self(out))
183 }
184}
185
186#[cfg(feature = "pyo3")]
187impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for D8 {
188 type Error = pyo3::PyErr;
189 fn extract(ob: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
190 let out = extract_data(&ob)?;
191 Ok(Self(out))
192 }
193}
194
195#[derive(Default, Debug, Clone)]
197#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
198#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
199pub struct InstructionRequest {
200 pub program_id: Vec<Address>,
201 pub discriminator: Vec<Data>,
202 pub d1: Vec<D1>,
203 pub d2: Vec<D2>,
204 pub d3: Vec<D3>,
205 pub d4: Vec<D4>,
206 pub d8: Vec<D8>,
207 pub a0: Vec<Address>,
208 pub a1: Vec<Address>,
209 pub a2: Vec<Address>,
210 pub a3: Vec<Address>,
211 pub a4: Vec<Address>,
212 pub a5: Vec<Address>,
213 pub a6: Vec<Address>,
214 pub a7: Vec<Address>,
215 pub a8: Vec<Address>,
216 pub a9: Vec<Address>,
217 pub is_committed: bool,
218 pub include_transactions: bool,
219 pub include_transaction_token_balances: bool,
220 pub include_logs: bool,
221 pub include_inner_instructions: bool,
222 pub include_blocks: bool,
223}
224
225#[derive(Default, Debug, Clone)]
227#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
228pub struct TransactionRequest {
229 pub fee_payer: Vec<Address>,
230 pub include_instructions: bool,
231 pub include_logs: bool,
232 pub include_blocks: bool,
233}
234
235#[derive(Default, Debug, Clone)]
237#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
238pub struct LogRequest {
239 pub program_id: Vec<Address>,
240 pub kind: Vec<LogKind>,
241 pub include_transactions: bool,
242 pub include_instructions: bool,
243 pub include_blocks: bool,
244}
245
246#[derive(Debug, Clone, Copy)]
248pub enum LogKind {
249 Log,
251 Data,
253 Other,
255}
256
257impl LogKind {
258 pub fn as_str(&self) -> &str {
259 match self {
260 Self::Log => "log",
261 Self::Data => "data",
262 Self::Other => "other",
263 }
264 }
265
266 pub fn from_str(s: &str) -> Result<Self> {
267 match s {
268 "log" => Ok(Self::Log),
269 "data" => Ok(Self::Data),
270 "other" => Ok(Self::Other),
271 _ => Err(anyhow!("unknown log kind: {s}")),
272 }
273 }
274}
275
276#[cfg(feature = "pyo3")]
277impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for LogKind {
278 type Error = pyo3::PyErr;
279 fn extract(ob: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
280 let s: &str = ob.extract().context("extract string")?;
281
282 Ok(Self::from_str(s).context("from str")?)
283 }
284}
285
286#[derive(Default, Debug, Clone)]
288#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
289pub struct BalanceRequest {
290 pub account: Vec<Address>,
291 pub include_transactions: bool,
292 pub include_transaction_instructions: bool,
293 pub include_blocks: bool,
294}
295
296#[derive(Default, Debug, Clone)]
298#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
299pub struct TokenBalanceRequest {
300 pub account: Vec<Address>,
301 pub pre_program_id: Vec<Address>,
302 pub post_program_id: Vec<Address>,
303 pub pre_mint: Vec<Address>,
304 pub post_mint: Vec<Address>,
305 pub pre_owner: Vec<Address>,
306 pub post_owner: Vec<Address>,
307 pub include_transactions: bool,
308 pub include_transaction_instructions: bool,
309 pub include_blocks: bool,
310}
311
312#[derive(Default, Debug, Clone)]
314#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
315pub struct RewardRequest {
316 pub pubkey: Vec<Address>,
317 pub include_blocks: bool,
318}
319
320#[derive(Deserialize, Serialize, Default, Debug, Clone, Copy)]
322#[serde(default)]
323#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
324pub struct Fields {
325 pub instruction: InstructionFields,
326 pub transaction: TransactionFields,
327 pub log: LogFields,
328 pub balance: BalanceFields,
329 pub token_balance: TokenBalanceFields,
330 pub reward: RewardFields,
331 pub block: BlockFields,
332}
333
334impl Fields {
335 pub fn all() -> Self {
336 Self {
337 instruction: InstructionFields::all(),
338 transaction: TransactionFields::all(),
339 log: LogFields::all(),
340 balance: BalanceFields::all(),
341 token_balance: TokenBalanceFields::all(),
342 reward: RewardFields::all(),
343 block: BlockFields::all(),
344 }
345 }
346}
347
348#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
350#[serde(default)]
351#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
352#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
353pub struct InstructionFields {
354 pub block_slot: bool,
355 pub block_hash: bool,
356 pub transaction_index: bool,
357 pub instruction_address: bool,
358 pub program_id: bool,
359 pub a0: bool,
360 pub a1: bool,
361 pub a2: bool,
362 pub a3: bool,
363 pub a4: bool,
364 pub a5: bool,
365 pub a6: bool,
366 pub a7: bool,
367 pub a8: bool,
368 pub a9: bool,
369 pub rest_of_accounts: bool,
370 pub data: bool,
371 pub d1: bool,
372 pub d2: bool,
373 pub d4: bool,
374 pub d8: bool,
375 pub error: bool,
376 pub compute_units_consumed: bool,
377 pub is_committed: bool,
378 pub has_dropped_log_messages: bool,
379}
380
381impl InstructionFields {
382 pub fn all() -> Self {
383 InstructionFields {
384 block_slot: true,
385 block_hash: true,
386 transaction_index: true,
387 instruction_address: true,
388 program_id: true,
389 a0: true,
390 a1: true,
391 a2: true,
392 a3: true,
393 a4: true,
394 a5: true,
395 a6: true,
396 a7: true,
397 a8: true,
398 a9: true,
399 rest_of_accounts: true,
400 data: true,
401 d1: true,
402 d2: true,
403 d4: true,
404 d8: true,
405 error: true,
406 compute_units_consumed: true,
407 is_committed: true,
408 has_dropped_log_messages: true,
409 }
410 }
411}
412
413#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
415#[serde(default)]
416#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
417#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
418pub struct TransactionFields {
419 pub block_slot: bool,
420 pub block_hash: bool,
421 pub transaction_index: bool,
422 pub signature: bool,
423 pub version: bool,
424 pub account_keys: bool,
425 pub address_table_lookups: bool,
426 pub num_readonly_signed_accounts: bool,
427 pub num_readonly_unsigned_accounts: bool,
428 pub num_required_signatures: bool,
429 pub recent_blockhash: bool,
430 pub signatures: bool,
431 pub err: bool,
432 pub fee: bool,
433 pub compute_units_consumed: bool,
434 pub loaded_readonly_addresses: bool,
435 pub loaded_writable_addresses: bool,
436 pub fee_payer: bool,
437 pub has_dropped_log_messages: bool,
438}
439
440impl TransactionFields {
441 pub fn all() -> Self {
442 TransactionFields {
443 block_slot: true,
444 block_hash: true,
445 transaction_index: true,
446 signature: true,
447 version: true,
448 account_keys: true,
449 address_table_lookups: true,
450 num_readonly_signed_accounts: true,
451 num_readonly_unsigned_accounts: true,
452 num_required_signatures: true,
453 recent_blockhash: true,
454 signatures: true,
455 err: true,
456 fee: true,
457 compute_units_consumed: true,
458 loaded_readonly_addresses: true,
459 loaded_writable_addresses: true,
460 fee_payer: true,
461 has_dropped_log_messages: true,
462 }
463 }
464}
465
466#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
468#[serde(default)]
469#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
470#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
471pub struct LogFields {
472 pub block_slot: bool,
473 pub block_hash: bool,
474 pub transaction_index: bool,
475 pub log_index: bool,
476 pub instruction_address: bool,
477 pub program_id: bool,
478 pub kind: bool,
479 pub message: bool,
480}
481
482impl LogFields {
483 pub fn all() -> Self {
484 LogFields {
485 block_slot: true,
486 block_hash: true,
487 transaction_index: true,
488 log_index: true,
489 instruction_address: true,
490 program_id: true,
491 kind: true,
492 message: true,
493 }
494 }
495}
496
497#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
499#[serde(default)]
500#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
501#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
502pub struct BalanceFields {
503 pub block_slot: bool,
504 pub block_hash: bool,
505 pub transaction_index: bool,
506 pub account: bool,
507 pub pre: bool,
508 pub post: bool,
509}
510
511impl BalanceFields {
512 pub fn all() -> Self {
513 BalanceFields {
514 block_slot: true,
515 block_hash: true,
516 transaction_index: true,
517 account: true,
518 pre: true,
519 post: true,
520 }
521 }
522}
523
524#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
526#[serde(default)]
527#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
528#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
529pub struct TokenBalanceFields {
530 pub block_slot: bool,
531 pub block_hash: bool,
532 pub transaction_index: bool,
533 pub account: bool,
534 pub pre_mint: bool,
535 pub post_mint: bool,
536 pub pre_decimals: bool,
537 pub post_decimals: bool,
538 pub pre_program_id: bool,
539 pub post_program_id: bool,
540 pub pre_owner: bool,
541 pub post_owner: bool,
542 pub pre_amount: bool,
543 pub post_amount: bool,
544}
545
546impl TokenBalanceFields {
547 pub fn all() -> Self {
548 TokenBalanceFields {
549 block_slot: true,
550 block_hash: true,
551 transaction_index: true,
552 account: true,
553 pre_mint: true,
554 post_mint: true,
555 pre_decimals: true,
556 post_decimals: true,
557 pre_program_id: true,
558 post_program_id: true,
559 pre_owner: true,
560 post_owner: true,
561 pre_amount: true,
562 post_amount: true,
563 }
564 }
565}
566
567#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
569#[serde(default)]
570#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
571#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
572pub struct RewardFields {
573 pub block_slot: bool,
574 pub block_hash: bool,
575 pub pubkey: bool,
576 pub lamports: bool,
577 pub post_balance: bool,
578 pub reward_type: bool,
579 pub commission: bool,
580}
581
582impl RewardFields {
583 pub fn all() -> Self {
584 RewardFields {
585 block_slot: true,
586 block_hash: true,
587 pubkey: true,
588 lamports: true,
589 post_balance: true,
590 reward_type: true,
591 commission: true,
592 }
593 }
594}
595
596#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
598#[serde(default)]
599#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
600#[expect(clippy::struct_excessive_bools, reason = "fields selection flags")]
601pub struct BlockFields {
602 pub slot: bool,
603 pub hash: bool,
604 pub parent_slot: bool,
605 pub parent_hash: bool,
606 pub height: bool,
607 pub timestamp: bool,
608}
609
610impl BlockFields {
611 pub fn all() -> Self {
612 BlockFields {
613 slot: true,
614 hash: true,
615 parent_slot: true,
616 parent_hash: true,
617 height: true,
618 timestamp: true,
619 }
620 }
621}