1use borsh::BorshDeserialize;
2use light_account::PackedAccounts;
3use light_compressed_account::{
4 compressed_account::{
5 CompressedAccount as ProgramCompressedAccount, CompressedAccountData,
6 CompressedAccountWithMerkleContext,
7 },
8 instruction_data::compressed_proof::CompressedProof,
9 TreeType,
10};
11use light_indexed_merkle_tree::array::IndexedElement;
12use light_sdk::instruction::{PackedAddressTreeInfo, PackedStateTreeInfo, ValidityProof};
13use light_token::compat::{AccountState, TokenData};
14use light_token_interface::state::ExtensionStruct;
15use num_bigint::BigUint;
16use solana_pubkey::Pubkey;
17use tracing::warn;
18
19use super::{
20 base58::{decode_base58_option_to_pubkey, decode_base58_to_fixed_array},
21 tree_info::QUEUE_TREE_MAPPING,
22 IndexerError,
23};
24
25pub struct ProofOfLeaf {
26 pub leaf: [u8; 32],
27 pub proof: Vec<[u8; 32]>,
28}
29
30pub type Address = [u8; 32];
31pub type Hash = [u8; 32];
32
33#[derive(Debug, Clone, PartialEq)]
34pub struct QueueInfo {
35 pub tree: Pubkey,
36 pub queue: Pubkey,
37 pub queue_type: u8,
38 pub queue_size: u64,
39}
40
41#[derive(Debug, Clone, PartialEq, Default)]
42pub struct QueueInfoResult {
43 pub queues: Vec<QueueInfo>,
44 pub slot: u64,
45}
46
47#[derive(Debug, Clone, PartialEq, Default)]
48pub struct OutputQueueData {
49 pub leaf_indices: Vec<u64>,
50 pub account_hashes: Vec<[u8; 32]>,
51 pub old_leaves: Vec<[u8; 32]>,
52 pub first_queue_index: u64,
53 pub next_index: u64,
55 pub leaves_hash_chains: Vec<[u8; 32]>,
57}
58
59#[derive(Debug, Clone, PartialEq, Default)]
61pub struct InputQueueData {
62 pub leaf_indices: Vec<u64>,
63 pub account_hashes: Vec<[u8; 32]>,
64 pub current_leaves: Vec<[u8; 32]>,
65 pub tx_hashes: Vec<[u8; 32]>,
66 pub nullifiers: Vec<[u8; 32]>,
68 pub first_queue_index: u64,
69 pub leaves_hash_chains: Vec<[u8; 32]>,
71}
72
73#[derive(Debug, Clone, PartialEq, Default)]
75pub struct StateQueueData {
76 pub nodes: Vec<u64>,
79 pub node_hashes: Vec<[u8; 32]>,
80 pub initial_root: [u8; 32],
82 pub root_seq: u64,
84 pub output_queue: Option<OutputQueueData>,
86 pub input_queue: Option<InputQueueData>,
88}
89
90#[derive(Debug, Clone, PartialEq, Default)]
93pub struct AddressQueueData {
94 pub addresses: Vec<[u8; 32]>,
95 pub low_element_values: Vec<[u8; 32]>,
96 pub low_element_next_values: Vec<[u8; 32]>,
97 pub low_element_indices: Vec<u64>,
98 pub low_element_next_indices: Vec<u64>,
99 pub nodes: Vec<u64>,
101 pub node_hashes: Vec<[u8; 32]>,
103 pub initial_root: [u8; 32],
104 pub leaves_hash_chains: Vec<[u8; 32]>,
105 pub subtrees: Vec<[u8; 32]>,
106 pub start_index: u64,
107 pub root_seq: u64,
108}
109
110impl AddressQueueData {
111 pub fn reconstruct_proof(
114 &self,
115 address_idx: usize,
116 tree_height: u8,
117 ) -> Result<Vec<[u8; 32]>, IndexerError> {
118 let leaf_index = self.low_element_indices[address_idx];
119 let mut proof = Vec::with_capacity(tree_height as usize);
120 let mut pos = leaf_index;
121
122 for level in 0..tree_height {
123 let sibling_pos = if pos.is_multiple_of(2) {
124 pos + 1
125 } else {
126 pos - 1
127 };
128 let sibling_idx = Self::encode_node_index(level, sibling_pos);
129
130 if let Some(hash_idx) = self.nodes.iter().position(|&n| n == sibling_idx) {
131 proof.push(self.node_hashes[hash_idx]);
132 } else {
133 return Err(IndexerError::MissingResult {
134 context: "reconstruct_proof".to_string(),
135 message: format!(
136 "Missing proof node at level {} position {} (encoded: {})",
137 level, sibling_pos, sibling_idx
138 ),
139 });
140 }
141 pos /= 2;
142 }
143
144 Ok(proof)
145 }
146
147 pub fn reconstruct_all_proofs(
149 &self,
150 tree_height: u8,
151 ) -> Result<Vec<Vec<[u8; 32]>>, IndexerError> {
152 (0..self.addresses.len())
153 .map(|i| self.reconstruct_proof(i, tree_height))
154 .collect()
155 }
156
157 #[inline]
159 fn encode_node_index(level: u8, position: u64) -> u64 {
160 ((level as u64) << 56) | position
161 }
162}
163
164#[derive(Debug, Clone, PartialEq, Default)]
166pub struct QueueElementsResult {
167 pub state_queue: Option<StateQueueData>,
168 pub address_queue: Option<AddressQueueData>,
169}
170
171#[derive(Debug, Clone, PartialEq, Default)]
172pub struct MerkleProofWithContext {
173 pub proof: Vec<[u8; 32]>,
174 pub root: [u8; 32],
175 pub leaf_index: u64,
176 pub leaf: [u8; 32],
177 pub merkle_tree: [u8; 32],
178 pub root_seq: u64,
179 pub tx_hash: Option<[u8; 32]>,
180 pub account_hash: [u8; 32],
181}
182
183#[derive(Debug, Clone, PartialEq, Default)]
184pub struct MerkleProof {
185 pub hash: [u8; 32],
186 pub leaf_index: u64,
187 pub merkle_tree: Pubkey,
188 pub proof: Vec<[u8; 32]>,
189 pub root_seq: u64,
190 pub root: [u8; 32],
191}
192
193#[derive(Debug, Clone, Copy, PartialEq)]
194pub struct AddressWithTree {
195 pub address: Address,
196 pub tree: Pubkey,
197}
198
199#[derive(Clone, Default, Debug, PartialEq)]
200pub struct NewAddressProofWithContext {
201 pub merkle_tree: Pubkey,
202 pub root: [u8; 32],
203 pub root_seq: u64,
204 pub low_address_index: u64,
205 pub low_address_value: [u8; 32],
206 pub low_address_next_index: u64,
207 pub low_address_next_value: [u8; 32],
208 pub low_address_proof: Vec<[u8; 32]>,
209 pub new_low_element: Option<IndexedElement<usize>>,
210 pub new_element: Option<IndexedElement<usize>>,
211 pub new_element_next_value: Option<BigUint>,
212}
213
214#[derive(Debug, Default, Clone, PartialEq)]
215pub struct ValidityProofWithContext {
216 pub proof: ValidityProof,
217 pub accounts: Vec<AccountProofInputs>,
218 pub addresses: Vec<AddressProofInputs>,
219}
220
221impl ValidityProofWithContext {
224 pub fn get_root_indices(&self) -> Vec<Option<u16>> {
225 self.accounts
226 .iter()
227 .map(|account| account.root_index.root_index())
228 .collect()
229 }
230
231 pub fn get_address_root_indices(&self) -> Vec<u16> {
232 self.addresses
233 .iter()
234 .map(|address| address.root_index)
235 .collect()
236 }
237}
238
239#[derive(Clone, Default, Debug, PartialEq)]
240pub struct AccountProofInputs {
241 pub hash: [u8; 32],
242 pub root: [u8; 32],
243 pub root_index: RootIndex,
244 pub leaf_index: u64,
245 pub tree_info: TreeInfo,
246}
247
248#[derive(Clone, Default, Copy, Debug, PartialEq)]
249pub struct RootIndex {
250 proof_by_index: bool,
251 root_index: u16,
252}
253
254impl RootIndex {
255 pub fn new_none() -> Self {
256 Self {
257 proof_by_index: true,
258 root_index: 0,
259 }
260 }
261
262 pub fn new_some(root_index: u16) -> Self {
263 Self {
264 proof_by_index: false,
265 root_index,
266 }
267 }
268
269 pub fn proof_by_index(&self) -> bool {
270 self.proof_by_index
271 }
272
273 pub fn root_index(&self) -> Option<u16> {
274 if !self.proof_by_index {
275 Some(self.root_index)
276 } else {
277 None
278 }
279 }
280}
281
282impl AccountProofInputs {
283 pub fn from_api_model(
284 value: &photon_api::models::AccountProofInputs,
285 ) -> Result<Self, IndexerError> {
286 let root_index = {
287 if value.root_index.prove_by_index {
288 RootIndex::new_none()
289 } else {
290 RootIndex::new_some(value.root_index.root_index)
291 }
292 };
293 Ok(Self {
294 hash: decode_base58_to_fixed_array(&value.hash)?,
295 root: decode_base58_to_fixed_array(&value.root)?,
296 root_index,
297 leaf_index: value.leaf_index,
298 tree_info: TreeInfo::from_api_model(&value.merkle_context)?,
299 })
300 }
301}
302
303#[derive(Clone, Default, Debug, PartialEq)]
304pub struct AddressProofInputs {
305 pub address: [u8; 32],
306 pub root: [u8; 32],
307 pub root_index: u16,
308 pub tree_info: TreeInfo,
309}
310
311impl AddressProofInputs {
312 pub fn from_api_model(
313 value: &photon_api::models::AddressProofInputs,
314 ) -> Result<Self, IndexerError> {
315 Ok(Self {
316 address: decode_base58_to_fixed_array(&value.address)?,
317 root: decode_base58_to_fixed_array(&value.root)?,
318 root_index: value.root_index,
319 tree_info: TreeInfo::from_api_model(&value.merkle_context)?,
320 })
321 }
322}
323
324#[derive(Clone, Default, Debug, PartialEq)]
325pub struct PackedStateTreeInfos {
326 pub packed_tree_infos: Vec<PackedStateTreeInfo>,
327 pub output_tree_index: u8,
328}
329
330#[derive(Clone, Default, Debug, PartialEq)]
331pub struct PackedTreeInfos {
332 pub state_trees: Option<PackedStateTreeInfos>,
333 pub address_trees: Vec<PackedAddressTreeInfo>,
334}
335
336impl ValidityProofWithContext {
337 pub fn pack_tree_infos(&self, packed_accounts: &mut PackedAccounts) -> PackedTreeInfos {
338 let mut packed_tree_infos = Vec::new();
339 let mut address_trees = Vec::new();
340 let mut output_tree_index = None;
341 for account in self.accounts.iter() {
342 let merkle_tree_pubkey_index = packed_accounts.insert_or_get(account.tree_info.tree);
344 let queue_pubkey_index = packed_accounts.insert_or_get(account.tree_info.queue);
345 let tree_info_packed = PackedStateTreeInfo {
346 root_index: account.root_index.root_index,
347 merkle_tree_pubkey_index,
348 queue_pubkey_index,
349 leaf_index: account.leaf_index as u32,
350 prove_by_index: account.root_index.proof_by_index(),
351 };
352 packed_tree_infos.push(tree_info_packed);
353
354 if let Some(next) = account.tree_info.next_tree_info {
357 let index = next.pack_output_tree_index(packed_accounts).unwrap();
360 if output_tree_index.is_none() {
361 output_tree_index = Some(index);
362 }
363 } else {
364 let index = account
367 .tree_info
368 .pack_output_tree_index(packed_accounts)
369 .unwrap();
370 if output_tree_index.is_none() {
371 output_tree_index = Some(index);
372 }
373 }
374 }
375
376 for address in self.addresses.iter() {
377 let address_merkle_tree_pubkey_index =
379 packed_accounts.insert_or_get(address.tree_info.tree);
380 let address_queue_pubkey_index = packed_accounts.insert_or_get(address.tree_info.queue);
381 address_trees.push(PackedAddressTreeInfo {
382 address_merkle_tree_pubkey_index,
383 address_queue_pubkey_index,
384 root_index: address.root_index,
385 });
386 }
387 let packed_tree_infos = if packed_tree_infos.is_empty() {
388 None
389 } else {
390 Some(PackedStateTreeInfos {
391 packed_tree_infos,
392 output_tree_index: output_tree_index.unwrap(),
393 })
394 };
395 PackedTreeInfos {
396 state_trees: packed_tree_infos,
397 address_trees,
398 }
399 }
400
401 pub fn from_api_model(
402 value: photon_api::models::CompressedProofWithContext,
403 num_hashes: usize,
404 ) -> Result<Self, IndexerError> {
405 let proof = ValidityProof::new(Some(CompressedProof {
406 a: value
407 .compressed_proof
408 .a
409 .try_into()
410 .map_err(|_| IndexerError::InvalidResponseData)?,
411 b: value
412 .compressed_proof
413 .b
414 .try_into()
415 .map_err(|_| IndexerError::InvalidResponseData)?,
416 c: value
417 .compressed_proof
418 .c
419 .try_into()
420 .map_err(|_| IndexerError::InvalidResponseData)?,
421 }));
422
423 let accounts = (0..num_hashes)
425 .map(|i| {
426 let tree_pubkey =
427 Pubkey::new_from_array(decode_base58_to_fixed_array(&value.merkle_trees[i])?);
428 let tree_info = super::tree_info::QUEUE_TREE_MAPPING
429 .get(&value.merkle_trees[i])
430 .ok_or(IndexerError::InvalidResponseData)?;
431
432 Ok(AccountProofInputs {
433 hash: decode_base58_to_fixed_array(&value.leaves[i])?,
434 root: decode_base58_to_fixed_array(&value.roots[i])?,
435 root_index: RootIndex::new_some(value.root_indices[i] as u16),
436 leaf_index: value.leaf_indices[i] as u64,
437 tree_info: TreeInfo {
438 tree_type: tree_info.tree_type,
439 tree: tree_pubkey,
440 queue: tree_info.queue,
441 cpi_context: tree_info.cpi_context,
442 next_tree_info: None,
443 },
444 })
445 })
446 .collect::<Result<Vec<_>, IndexerError>>()?;
447
448 let addresses = if value.root_indices.len() > num_hashes {
450 (num_hashes..value.root_indices.len())
451 .map(|i| {
452 let tree_pubkey = Pubkey::new_from_array(decode_base58_to_fixed_array(
453 &value.merkle_trees[i],
454 )?);
455 let tree_info = super::tree_info::QUEUE_TREE_MAPPING
456 .get(&value.merkle_trees[i])
457 .ok_or(IndexerError::InvalidResponseData)?;
458
459 Ok(AddressProofInputs {
460 address: decode_base58_to_fixed_array(&value.leaves[i])?, root: decode_base58_to_fixed_array(&value.roots[i])?,
462 root_index: value.root_indices[i] as u16,
463 tree_info: TreeInfo {
464 tree_type: tree_info.tree_type,
465 tree: tree_pubkey,
466 queue: tree_info.queue,
467 cpi_context: tree_info.cpi_context,
468 next_tree_info: None,
469 },
470 })
471 })
472 .collect::<Result<Vec<_>, IndexerError>>()?
473 } else {
474 Vec::new()
475 };
476
477 Ok(Self {
478 proof,
479 accounts,
480 addresses,
481 })
482 }
483
484 pub fn from_api_model_v2(
485 value: photon_api::models::CompressedProofWithContextV2,
486 ) -> Result<Self, IndexerError> {
487 let proof = if let Some(proof) = value.compressed_proof {
488 ValidityProof::new(Some(CompressedProof {
489 a: proof
490 .a
491 .try_into()
492 .map_err(|_| IndexerError::InvalidResponseData)?,
493 b: proof
494 .b
495 .try_into()
496 .map_err(|_| IndexerError::InvalidResponseData)?,
497 c: proof
498 .c
499 .try_into()
500 .map_err(|_| IndexerError::InvalidResponseData)?,
501 }))
502 } else {
503 ValidityProof::new(None)
504 };
505
506 let accounts = value
507 .accounts
508 .iter()
509 .map(AccountProofInputs::from_api_model)
510 .collect::<Result<Vec<_>, IndexerError>>()?;
511
512 let addresses = value
513 .addresses
514 .iter()
515 .map(AddressProofInputs::from_api_model)
516 .collect::<Result<Vec<_>, IndexerError>>()?;
517
518 Ok(Self {
519 proof,
520 accounts,
521 addresses,
522 })
523 }
524}
525
526#[derive(Clone, Copy, Default, Debug, PartialEq)]
527pub struct NextTreeInfo {
528 pub cpi_context: Option<Pubkey>,
529 pub queue: Pubkey,
530 pub tree: Pubkey,
531 pub tree_type: TreeType,
532}
533
534impl NextTreeInfo {
535 pub fn pack_output_tree_index(
541 &self,
542 packed_accounts: &mut PackedAccounts,
543 ) -> Result<u8, IndexerError> {
544 match self.tree_type {
545 TreeType::StateV1 => Ok(packed_accounts.insert_or_get(self.tree)),
546 TreeType::StateV2 => Ok(packed_accounts.insert_or_get(self.queue)),
547 _ => Err(IndexerError::InvalidPackTreeType),
548 }
549 }
550 pub fn from_api_model(
551 value: &photon_api::models::TreeContextInfo,
552 ) -> Result<Self, IndexerError> {
553 Ok(Self {
554 tree_type: TreeType::from(value.tree_type as u64),
555 tree: Pubkey::new_from_array(decode_base58_to_fixed_array(&value.tree)?),
556 queue: Pubkey::new_from_array(decode_base58_to_fixed_array(&value.queue)?),
557 cpi_context: decode_base58_option_to_pubkey(&value.cpi_context)?,
558 })
559 }
560}
561
562impl TryFrom<&photon_api::models::TreeContextInfo> for NextTreeInfo {
563 type Error = IndexerError;
564
565 fn try_from(value: &photon_api::models::TreeContextInfo) -> Result<Self, Self::Error> {
566 Ok(Self {
567 tree_type: TreeType::from(value.tree_type as u64),
568 tree: Pubkey::new_from_array(decode_base58_to_fixed_array(&value.tree)?),
569 queue: Pubkey::new_from_array(decode_base58_to_fixed_array(&value.queue)?),
570 cpi_context: decode_base58_option_to_pubkey(&value.cpi_context)?,
571 })
572 }
573}
574
575#[derive(Clone, Copy, Default, Debug, PartialEq)]
576pub struct TreeInfo {
577 pub cpi_context: Option<Pubkey>,
578 pub next_tree_info: Option<NextTreeInfo>,
579 pub queue: Pubkey,
580 pub tree: Pubkey,
581 pub tree_type: TreeType,
582}
583
584impl TreeInfo {
585 pub fn pack_output_tree_index(
591 &self,
592 packed_accounts: &mut PackedAccounts,
593 ) -> Result<u8, IndexerError> {
594 match self.tree_type {
595 TreeType::StateV1 => Ok(packed_accounts.insert_or_get(self.tree)),
596 TreeType::StateV2 => Ok(packed_accounts.insert_or_get(self.queue)),
597 _ => Err(IndexerError::InvalidPackTreeType),
598 }
599 }
600
601 pub fn get_output_pubkey(&self) -> Result<Pubkey, IndexerError> {
602 match self.tree_type {
603 TreeType::StateV1 => Ok(self.tree),
604 TreeType::StateV2 => Ok(self.queue),
605 _ => Err(IndexerError::InvalidPackTreeType),
606 }
607 }
608
609 pub fn from_api_model(
610 value: &photon_api::models::MerkleContextV2,
611 ) -> Result<Self, IndexerError> {
612 Ok(Self {
613 tree_type: TreeType::from(value.tree_type as u64),
614 tree: Pubkey::new_from_array(decode_base58_to_fixed_array(&value.tree)?),
615 queue: Pubkey::new_from_array(decode_base58_to_fixed_array(&value.queue)?),
616 cpi_context: decode_base58_option_to_pubkey(&value.cpi_context)?,
617 next_tree_info: value
618 .next_tree_context
619 .as_ref()
620 .map(|tree_info| NextTreeInfo::from_api_model(tree_info.as_ref()))
621 .transpose()?,
622 })
623 }
624
625 pub fn to_light_merkle_context(
626 &self,
627 leaf_index: u32,
628 prove_by_index: bool,
629 ) -> light_compressed_account::compressed_account::MerkleContext {
630 use light_compressed_account::Pubkey;
631 light_compressed_account::compressed_account::MerkleContext {
632 merkle_tree_pubkey: Pubkey::new_from_array(self.tree.to_bytes()),
633 queue_pubkey: Pubkey::new_from_array(self.queue.to_bytes()),
634 leaf_index,
635 tree_type: self.tree_type,
636 prove_by_index,
637 }
638 }
639}
640
641#[derive(Clone, Default, Debug, PartialEq)]
642pub struct CompressedAccount {
643 pub address: Option<[u8; 32]>,
644 pub data: Option<CompressedAccountData>,
645 pub hash: [u8; 32],
646 pub lamports: u64,
647 pub leaf_index: u32,
648 pub owner: Pubkey,
649 pub prove_by_index: bool,
650 pub seq: Option<u64>,
651 pub slot_created: u64,
652 pub tree_info: TreeInfo,
653}
654
655impl TryFrom<CompressedAccountWithMerkleContext> for CompressedAccount {
656 type Error = IndexerError;
657
658 fn try_from(account: CompressedAccountWithMerkleContext) -> Result<Self, Self::Error> {
659 let hash = account
660 .hash()
661 .map_err(|_| IndexerError::InvalidResponseData)?;
662 let tree_info = QUEUE_TREE_MAPPING.get(
664 &Pubkey::new_from_array(account.merkle_context.merkle_tree_pubkey.to_bytes())
665 .to_string(),
666 );
667 let cpi_context = if let Some(tree_info) = tree_info {
668 tree_info.cpi_context
669 } else {
670 warn!("Cpi context not found in queue tree mapping");
671 None
672 };
673 Ok(CompressedAccount {
674 address: account.compressed_account.address,
675 data: account.compressed_account.data,
676 hash,
677 lamports: account.compressed_account.lamports,
678 leaf_index: account.merkle_context.leaf_index,
679 tree_info: TreeInfo {
680 tree: Pubkey::new_from_array(account.merkle_context.merkle_tree_pubkey.to_bytes()),
681 queue: Pubkey::new_from_array(account.merkle_context.queue_pubkey.to_bytes()),
682 tree_type: account.merkle_context.tree_type,
683 cpi_context,
684 next_tree_info: None,
685 },
686 owner: Pubkey::new_from_array(account.compressed_account.owner.to_bytes()),
687 prove_by_index: account.merkle_context.prove_by_index,
688 seq: None,
689 slot_created: u64::MAX,
690 })
691 }
692}
693
694impl From<CompressedAccount> for CompressedAccountWithMerkleContext {
695 fn from(account: CompressedAccount) -> Self {
696 use light_compressed_account::Pubkey;
697 let compressed_account = ProgramCompressedAccount {
698 owner: Pubkey::new_from_array(account.owner.to_bytes()),
699 lamports: account.lamports,
700 address: account.address,
701 data: account.data,
702 };
703
704 let merkle_context = account
705 .tree_info
706 .to_light_merkle_context(account.leaf_index, account.prove_by_index);
707
708 CompressedAccountWithMerkleContext {
709 compressed_account,
710 merkle_context,
711 }
712 }
713}
714
715impl TryFrom<&photon_api::models::AccountV2> for CompressedAccount {
716 type Error = IndexerError;
717
718 fn try_from(account: &photon_api::models::AccountV2) -> Result<Self, Self::Error> {
719 let data = if let Some(data) = &account.data {
720 Ok::<Option<CompressedAccountData>, IndexerError>(Some(CompressedAccountData {
721 discriminator: data.discriminator.to_le_bytes(),
722 data: base64::decode_config(&data.data, base64::STANDARD_NO_PAD)
723 .map_err(|_| IndexerError::InvalidResponseData)?,
724 data_hash: decode_base58_to_fixed_array(&data.data_hash)?,
725 }))
726 } else {
727 Ok::<Option<CompressedAccountData>, IndexerError>(None)
728 }?;
729
730 let owner = Pubkey::new_from_array(decode_base58_to_fixed_array(&account.owner)?);
731 let address = account
732 .address
733 .as_ref()
734 .map(|address| decode_base58_to_fixed_array(address))
735 .transpose()?;
736 let hash = decode_base58_to_fixed_array(&account.hash)?;
737
738 let tree_info = TreeInfo {
739 tree: Pubkey::new_from_array(decode_base58_to_fixed_array(
740 &account.merkle_context.tree,
741 )?),
742 queue: Pubkey::new_from_array(decode_base58_to_fixed_array(
743 &account.merkle_context.queue,
744 )?),
745 tree_type: TreeType::from(account.merkle_context.tree_type as u64),
746 cpi_context: decode_base58_option_to_pubkey(&account.merkle_context.cpi_context)?,
747 next_tree_info: account
748 .merkle_context
749 .next_tree_context
750 .as_ref()
751 .map(|ctx| NextTreeInfo::try_from(ctx.as_ref()))
752 .transpose()?,
753 };
754
755 Ok(CompressedAccount {
756 owner,
757 address,
758 data,
759 hash,
760 lamports: account.lamports,
761 leaf_index: account.leaf_index,
762 seq: account.seq,
763 slot_created: account.slot_created,
764 tree_info,
765 prove_by_index: account.prove_by_index,
766 })
767 }
768}
769
770impl TryFrom<&photon_api::models::Account> for CompressedAccount {
771 type Error = IndexerError;
772
773 fn try_from(account: &photon_api::models::Account) -> Result<Self, Self::Error> {
774 let data = if let Some(data) = &account.data {
775 Ok::<Option<CompressedAccountData>, IndexerError>(Some(CompressedAccountData {
776 discriminator: data.discriminator.to_le_bytes(),
777 data: base64::decode_config(&data.data, base64::STANDARD_NO_PAD)
778 .map_err(|_| IndexerError::InvalidResponseData)?,
779 data_hash: decode_base58_to_fixed_array(&data.data_hash)?,
780 }))
781 } else {
782 Ok::<Option<CompressedAccountData>, IndexerError>(None)
783 }?;
784 let owner = Pubkey::new_from_array(decode_base58_to_fixed_array(&account.owner)?);
785 let address = account
786 .address
787 .as_ref()
788 .map(|address| decode_base58_to_fixed_array(address))
789 .transpose()?;
790 let hash = decode_base58_to_fixed_array(&account.hash)?;
791 let seq = account.seq;
792 let slot_created = account.slot_created;
793 let lamports = account.lamports;
794 let leaf_index = account.leaf_index;
795
796 let tree_info = QUEUE_TREE_MAPPING
797 .get(&account.tree)
798 .ok_or(IndexerError::InvalidResponseData)?;
799
800 let tree_info = TreeInfo {
801 cpi_context: tree_info.cpi_context,
802 queue: tree_info.queue,
803 tree_type: tree_info.tree_type,
804 next_tree_info: None,
805 tree: tree_info.tree,
806 };
807
808 Ok(CompressedAccount {
809 owner,
810 address,
811 data,
812 hash,
813 lamports,
814 leaf_index,
815 seq,
816 slot_created,
817 tree_info,
818 prove_by_index: false,
819 })
820 }
821}
822
823#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
824pub struct StateMerkleTreeAccounts {
825 pub merkle_tree: Pubkey,
826 pub nullifier_queue: Pubkey,
827 pub cpi_context: Pubkey,
828 pub tree_type: TreeType,
829}
830
831#[allow(clippy::from_over_into)]
832impl Into<TreeInfo> for StateMerkleTreeAccounts {
833 fn into(self) -> TreeInfo {
834 TreeInfo {
835 tree: self.merkle_tree,
836 queue: self.nullifier_queue,
837 cpi_context: Some(self.cpi_context),
838 tree_type: self.tree_type,
839 next_tree_info: None,
840 }
841 }
842}
843
844#[derive(Debug, Clone, Copy)]
845pub struct AddressMerkleTreeAccounts {
846 pub merkle_tree: Pubkey,
847 pub queue: Pubkey,
848}
849
850#[derive(Clone, Default, Debug, PartialEq)]
851pub struct CompressedTokenAccount {
852 pub token: TokenData,
854 pub account: CompressedAccount,
856}
857
858impl TryFrom<&photon_api::models::TokenAccount> for CompressedTokenAccount {
859 type Error = IndexerError;
860
861 fn try_from(token_account: &photon_api::models::TokenAccount) -> Result<Self, Self::Error> {
862 let account = CompressedAccount::try_from(token_account.account.as_ref())?;
863
864 let token = TokenData {
865 mint: Pubkey::new_from_array(decode_base58_to_fixed_array(
866 &token_account.token_data.mint,
867 )?),
868 owner: Pubkey::new_from_array(decode_base58_to_fixed_array(
869 &token_account.token_data.owner,
870 )?),
871 amount: token_account.token_data.amount,
872 delegate: token_account
873 .token_data
874 .delegate
875 .as_ref()
876 .map(|d| decode_base58_to_fixed_array(d).map(Pubkey::new_from_array))
877 .transpose()?,
878 state: match token_account.token_data.state {
879 photon_api::models::AccountState::Initialized => AccountState::Initialized,
880 photon_api::models::AccountState::Frozen => AccountState::Frozen,
881 },
882 tlv: token_account
883 .token_data
884 .tlv
885 .as_ref()
886 .map(|tlv| {
887 let bytes = base64::decode_config(tlv, base64::STANDARD_NO_PAD)
888 .map_err(|_| IndexerError::InvalidResponseData)?;
889 Vec::<ExtensionStruct>::deserialize(&mut bytes.as_slice())
890 .map_err(|_| IndexerError::InvalidResponseData)
891 })
892 .transpose()?,
893 };
894
895 Ok(CompressedTokenAccount { token, account })
896 }
897}
898
899impl TryFrom<&photon_api::models::TokenAccountV2> for CompressedTokenAccount {
900 type Error = IndexerError;
901
902 fn try_from(token_account: &photon_api::models::TokenAccountV2) -> Result<Self, Self::Error> {
903 let account = CompressedAccount::try_from(token_account.account.as_ref())?;
904
905 let token = TokenData {
906 mint: Pubkey::new_from_array(decode_base58_to_fixed_array(
907 &token_account.token_data.mint,
908 )?),
909 owner: Pubkey::new_from_array(decode_base58_to_fixed_array(
910 &token_account.token_data.owner,
911 )?),
912 amount: token_account.token_data.amount,
913 delegate: token_account
914 .token_data
915 .delegate
916 .as_ref()
917 .map(|d| decode_base58_to_fixed_array(d).map(Pubkey::new_from_array))
918 .transpose()?,
919 state: match token_account.token_data.state {
920 photon_api::models::AccountState::Initialized => AccountState::Initialized,
921 photon_api::models::AccountState::Frozen => AccountState::Frozen,
922 },
923 tlv: token_account
924 .token_data
925 .tlv
926 .as_ref()
927 .map(|tlv| {
928 let bytes = base64::decode_config(tlv, base64::STANDARD_NO_PAD)
929 .map_err(|_| IndexerError::InvalidResponseData)?;
930 Vec::<ExtensionStruct>::deserialize(&mut bytes.as_slice())
931 .map_err(|_| IndexerError::InvalidResponseData)
932 })
933 .transpose()?,
934 };
935
936 Ok(CompressedTokenAccount { token, account })
937 }
938}
939
940#[allow(clippy::from_over_into)]
941impl Into<light_token::compat::TokenDataWithMerkleContext> for CompressedTokenAccount {
942 fn into(self) -> light_token::compat::TokenDataWithMerkleContext {
943 let compressed_account = CompressedAccountWithMerkleContext::from(self.account);
944
945 light_token::compat::TokenDataWithMerkleContext {
946 token_data: self.token,
947 compressed_account,
948 }
949 }
950}
951
952#[allow(clippy::from_over_into)]
953impl Into<Vec<light_token::compat::TokenDataWithMerkleContext>>
954 for super::response::Response<super::response::ItemsWithCursor<CompressedTokenAccount>>
955{
956 fn into(self) -> Vec<light_token::compat::TokenDataWithMerkleContext> {
957 self.value
958 .items
959 .into_iter()
960 .map(
961 |token_account| light_token::compat::TokenDataWithMerkleContext {
962 token_data: token_account.token,
963 compressed_account: CompressedAccountWithMerkleContext::from(
964 token_account.account.clone(),
965 ),
966 },
967 )
968 .collect::<Vec<light_token::compat::TokenDataWithMerkleContext>>()
969 }
970}
971
972impl TryFrom<light_token::compat::TokenDataWithMerkleContext> for CompressedTokenAccount {
973 type Error = IndexerError;
974
975 fn try_from(
976 token_data_with_context: light_token::compat::TokenDataWithMerkleContext,
977 ) -> Result<Self, Self::Error> {
978 let account = CompressedAccount::try_from(token_data_with_context.compressed_account)?;
979
980 Ok(CompressedTokenAccount {
981 token: token_data_with_context.token_data,
982 account,
983 })
984 }
985}
986
987#[derive(Clone, Default, Debug, PartialEq)]
988pub struct TokenBalance {
989 pub balance: u64,
990 pub mint: Pubkey,
991}
992
993impl TryFrom<&photon_api::models::TokenBalance> for TokenBalance {
994 type Error = IndexerError;
995
996 fn try_from(token_balance: &photon_api::models::TokenBalance) -> Result<Self, Self::Error> {
997 Ok(TokenBalance {
998 balance: token_balance.balance,
999 mint: Pubkey::new_from_array(decode_base58_to_fixed_array(&token_balance.mint)?),
1000 })
1001 }
1002}
1003
1004#[derive(Debug, Clone, PartialEq, Default)]
1005pub struct SignatureWithMetadata {
1006 pub block_time: u64,
1007 pub signature: String,
1008 pub slot: u64,
1009}
1010
1011impl TryFrom<&photon_api::models::SignatureInfo> for SignatureWithMetadata {
1012 type Error = IndexerError;
1013
1014 fn try_from(sig_info: &photon_api::models::SignatureInfo) -> Result<Self, Self::Error> {
1015 Ok(SignatureWithMetadata {
1016 block_time: sig_info.block_time,
1017 signature: sig_info.signature.clone(),
1018 slot: sig_info.slot,
1019 })
1020 }
1021}
1022
1023#[derive(Clone, Default, Debug, PartialEq)]
1024pub struct OwnerBalance {
1025 pub balance: u64,
1026 pub owner: Pubkey,
1027}
1028
1029impl TryFrom<&photon_api::models::OwnerBalance> for OwnerBalance {
1030 type Error = IndexerError;
1031
1032 fn try_from(owner_balance: &photon_api::models::OwnerBalance) -> Result<Self, Self::Error> {
1033 Ok(OwnerBalance {
1034 balance: owner_balance.balance,
1035 owner: Pubkey::new_from_array(decode_base58_to_fixed_array(&owner_balance.owner)?),
1036 })
1037 }
1038}