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