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