light_client/indexer/
types.rs

1use light_compressed_account::{
2    compressed_account::{
3        CompressedAccount as ProgramCompressedAccount, CompressedAccountData,
4        CompressedAccountWithMerkleContext,
5    },
6    instruction_data::compressed_proof::CompressedProof,
7    TreeType,
8};
9use light_indexed_merkle_tree::array::IndexedElement;
10use light_sdk::{
11    instruction::{PackedAccounts, PackedAddressTreeInfo, PackedStateTreeInfo, ValidityProof},
12    token::{AccountState, TokenData},
13};
14use num_bigint::BigUint;
15use solana_pubkey::Pubkey;
16use tracing::warn;
17
18use super::{
19    base58::{decode_base58_option_to_pubkey, decode_base58_to_fixed_array},
20    tree_info::QUEUE_TREE_MAPPING,
21    IndexerError,
22};
23
24pub struct ProofOfLeaf {
25    pub leaf: [u8; 32],
26    pub proof: Vec<[u8; 32]>,
27}
28
29pub type Address = [u8; 32];
30pub type Hash = [u8; 32];
31
32#[derive(Debug, Clone, PartialEq, Default)]
33pub struct QueueElementsResult {
34    pub elements: Vec<MerkleProofWithContext>,
35    pub first_value_queue_index: Option<u64>,
36}
37
38#[derive(Debug, Clone, PartialEq, Default)]
39pub struct MerkleProofWithContext {
40    pub proof: Vec<[u8; 32]>,
41    pub root: [u8; 32],
42    pub leaf_index: u64,
43    pub leaf: [u8; 32],
44    pub merkle_tree: [u8; 32],
45    pub root_seq: u64,
46    pub tx_hash: Option<[u8; 32]>,
47    pub account_hash: [u8; 32],
48}
49
50#[derive(Debug, Clone, PartialEq, Default)]
51pub struct MerkleProof {
52    pub hash: [u8; 32],
53    pub leaf_index: u64,
54    pub merkle_tree: Pubkey,
55    pub proof: Vec<[u8; 32]>,
56    pub root_seq: u64,
57    pub root: [u8; 32],
58}
59
60#[derive(Debug, Clone, Copy, PartialEq)]
61pub struct AddressWithTree {
62    pub address: Address,
63    pub tree: Pubkey,
64}
65
66#[derive(Clone, Default, Debug, PartialEq)]
67pub struct NewAddressProofWithContext {
68    pub merkle_tree: Pubkey,
69    pub root: [u8; 32],
70    pub root_seq: u64,
71    pub low_address_index: u64,
72    pub low_address_value: [u8; 32],
73    pub low_address_next_index: u64,
74    pub low_address_next_value: [u8; 32],
75    pub low_address_proof: Vec<[u8; 32]>,
76    pub new_low_element: Option<IndexedElement<usize>>,
77    pub new_element: Option<IndexedElement<usize>>,
78    pub new_element_next_value: Option<BigUint>,
79}
80
81#[derive(Debug, Default, Clone, PartialEq)]
82pub struct ValidityProofWithContext {
83    pub proof: ValidityProof,
84    pub accounts: Vec<AccountProofInputs>,
85    pub addresses: Vec<AddressProofInputs>,
86}
87
88// TODO: add get_public_inputs
89// -> to make it easier to use light-verifier with get_validity_proof()
90impl ValidityProofWithContext {
91    pub fn get_root_indices(&self) -> Vec<Option<u16>> {
92        self.accounts
93            .iter()
94            .map(|account| account.root_index.root_index())
95            .collect()
96    }
97
98    pub fn get_address_root_indices(&self) -> Vec<u16> {
99        self.addresses
100            .iter()
101            .map(|address| address.root_index)
102            .collect()
103    }
104}
105
106#[derive(Clone, Default, Debug, PartialEq)]
107pub struct AccountProofInputs {
108    pub hash: [u8; 32],
109    pub root: [u8; 32],
110    pub root_index: RootIndex,
111    pub leaf_index: u64,
112    pub tree_info: TreeInfo,
113}
114
115#[derive(Clone, Default, Copy, Debug, PartialEq)]
116pub struct RootIndex {
117    proof_by_index: bool,
118    root_index: u16,
119}
120
121impl RootIndex {
122    pub fn new_none() -> Self {
123        Self {
124            proof_by_index: true,
125            root_index: 0,
126        }
127    }
128
129    pub fn new_some(root_index: u16) -> Self {
130        Self {
131            proof_by_index: false,
132            root_index,
133        }
134    }
135
136    pub fn proof_by_index(&self) -> bool {
137        self.proof_by_index
138    }
139
140    pub fn root_index(&self) -> Option<u16> {
141        if !self.proof_by_index {
142            Some(self.root_index)
143        } else {
144            None
145        }
146    }
147}
148
149impl AccountProofInputs {
150    pub fn from_api_model(
151        value: &photon_api::models::AccountProofInputs,
152    ) -> Result<Self, IndexerError> {
153        let root_index = {
154            if value.root_index.prove_by_index {
155                RootIndex::new_none()
156            } else {
157                RootIndex::new_some(value.root_index.root_index)
158            }
159        };
160        Ok(Self {
161            hash: decode_base58_to_fixed_array(&value.hash)?,
162            root: decode_base58_to_fixed_array(&value.root)?,
163            root_index,
164            leaf_index: value.leaf_index,
165            tree_info: TreeInfo::from_api_model(&value.merkle_context)?,
166        })
167    }
168}
169
170#[derive(Clone, Default, Debug, PartialEq)]
171pub struct AddressProofInputs {
172    pub address: [u8; 32],
173    pub root: [u8; 32],
174    pub root_index: u16,
175    pub tree_info: TreeInfo,
176}
177
178impl AddressProofInputs {
179    pub fn from_api_model(
180        value: &photon_api::models::AddressProofInputs,
181    ) -> Result<Self, IndexerError> {
182        Ok(Self {
183            address: decode_base58_to_fixed_array(&value.address)?,
184            root: decode_base58_to_fixed_array(&value.root)?,
185            root_index: value.root_index,
186            tree_info: TreeInfo::from_api_model(&value.merkle_context)?,
187        })
188    }
189}
190
191#[derive(Clone, Default, Debug, PartialEq)]
192pub struct PackedStateTreeInfos {
193    pub packed_tree_infos: Vec<PackedStateTreeInfo>,
194    pub output_tree_index: u8,
195}
196
197#[derive(Clone, Default, Debug, PartialEq)]
198pub struct PackedTreeInfos {
199    pub state_trees: Option<PackedStateTreeInfos>,
200    pub address_trees: Vec<PackedAddressTreeInfo>,
201}
202
203impl ValidityProofWithContext {
204    pub fn pack_tree_infos(&self, packed_accounts: &mut PackedAccounts) -> PackedTreeInfos {
205        let mut packed_tree_infos = Vec::new();
206        let mut address_trees = Vec::new();
207        let mut output_tree_index = None;
208        for account in self.accounts.iter() {
209            // Pack TreeInfo
210            let merkle_tree_pubkey_index = packed_accounts.insert_or_get(account.tree_info.tree);
211            let queue_pubkey_index = packed_accounts.insert_or_get(account.tree_info.queue);
212            let tree_info_packed = PackedStateTreeInfo {
213                root_index: account.root_index.root_index,
214                merkle_tree_pubkey_index,
215                queue_pubkey_index,
216                leaf_index: account.leaf_index as u32,
217                prove_by_index: account.root_index.proof_by_index(),
218            };
219            packed_tree_infos.push(tree_info_packed);
220
221            // If a next Merkle tree exists the Merkle tree is full -> use the next Merkle tree for new state.
222            // Else use the current Merkle tree for new state.
223            if let Some(next) = account.tree_info.next_tree_info {
224                // SAFETY: account will always have a state Merkle tree context.
225                // pack_output_tree_index only panics on an address Merkle tree context.
226                let index = next.pack_output_tree_index(packed_accounts).unwrap();
227                if output_tree_index.is_none() {
228                    output_tree_index = Some(index);
229                }
230            } else {
231                // SAFETY: account will always have a state Merkle tree context.
232                // pack_output_tree_index only panics on an address Merkle tree context.
233                let index = account
234                    .tree_info
235                    .pack_output_tree_index(packed_accounts)
236                    .unwrap();
237                if output_tree_index.is_none() {
238                    output_tree_index = Some(index);
239                }
240            }
241        }
242
243        for address in self.addresses.iter() {
244            // Pack AddressTreeInfo
245            let address_merkle_tree_pubkey_index =
246                packed_accounts.insert_or_get(address.tree_info.tree);
247            let address_queue_pubkey_index = packed_accounts.insert_or_get(address.tree_info.queue);
248            address_trees.push(PackedAddressTreeInfo {
249                address_merkle_tree_pubkey_index,
250                address_queue_pubkey_index,
251                root_index: address.root_index,
252            });
253        }
254        let packed_tree_infos = if packed_tree_infos.is_empty() {
255            None
256        } else {
257            Some(PackedStateTreeInfos {
258                packed_tree_infos,
259                output_tree_index: output_tree_index.unwrap(),
260            })
261        };
262        PackedTreeInfos {
263            state_trees: packed_tree_infos,
264            address_trees,
265        }
266    }
267
268    pub fn from_api_model(
269        value: photon_api::models::CompressedProofWithContext,
270        num_hashes: usize,
271    ) -> Result<Self, IndexerError> {
272        let proof = ValidityProof::new(Some(CompressedProof {
273            a: value
274                .compressed_proof
275                .a
276                .try_into()
277                .map_err(|_| IndexerError::InvalidResponseData)?,
278            b: value
279                .compressed_proof
280                .b
281                .try_into()
282                .map_err(|_| IndexerError::InvalidResponseData)?,
283            c: value
284                .compressed_proof
285                .c
286                .try_into()
287                .map_err(|_| IndexerError::InvalidResponseData)?,
288        }));
289
290        // Convert account data from V1 flat arrays to V2 structured format
291        let accounts = (0..num_hashes)
292            .map(|i| {
293                let tree_pubkey =
294                    Pubkey::new_from_array(decode_base58_to_fixed_array(&value.merkle_trees[i])?);
295                let tree_info = super::tree_info::QUEUE_TREE_MAPPING
296                    .get(&value.merkle_trees[i])
297                    .ok_or(IndexerError::InvalidResponseData)?;
298
299                Ok(AccountProofInputs {
300                    hash: decode_base58_to_fixed_array(&value.leaves[i])?,
301                    root: decode_base58_to_fixed_array(&value.roots[i])?,
302                    root_index: RootIndex::new_some(value.root_indices[i] as u16),
303                    leaf_index: value.leaf_indices[i] as u64,
304                    tree_info: TreeInfo {
305                        tree_type: tree_info.tree_type,
306                        tree: tree_pubkey,
307                        queue: tree_info.queue,
308                        cpi_context: tree_info.cpi_context,
309                        next_tree_info: None,
310                    },
311                })
312            })
313            .collect::<Result<Vec<_>, IndexerError>>()?;
314
315        // Convert address data from remaining indices (if any)
316        let addresses = if value.root_indices.len() > num_hashes {
317            (num_hashes..value.root_indices.len())
318                .map(|i| {
319                    let tree_pubkey = Pubkey::new_from_array(decode_base58_to_fixed_array(
320                        &value.merkle_trees[i],
321                    )?);
322                    let tree_info = super::tree_info::QUEUE_TREE_MAPPING
323                        .get(&value.merkle_trees[i])
324                        .ok_or(IndexerError::InvalidResponseData)?;
325
326                    Ok(AddressProofInputs {
327                        address: decode_base58_to_fixed_array(&value.leaves[i])?, // Address is in leaves
328                        root: decode_base58_to_fixed_array(&value.roots[i])?,
329                        root_index: value.root_indices[i] as u16,
330                        tree_info: TreeInfo {
331                            tree_type: tree_info.tree_type,
332                            tree: tree_pubkey,
333                            queue: tree_info.queue,
334                            cpi_context: tree_info.cpi_context,
335                            next_tree_info: None,
336                        },
337                    })
338                })
339                .collect::<Result<Vec<_>, IndexerError>>()?
340        } else {
341            Vec::new()
342        };
343
344        Ok(Self {
345            proof,
346            accounts,
347            addresses,
348        })
349    }
350
351    pub fn from_api_model_v2(
352        value: photon_api::models::CompressedProofWithContextV2,
353    ) -> Result<Self, IndexerError> {
354        let proof = if let Some(proof) = value.compressed_proof {
355            ValidityProof::new(Some(CompressedProof {
356                a: proof
357                    .a
358                    .try_into()
359                    .map_err(|_| IndexerError::InvalidResponseData)?,
360                b: proof
361                    .b
362                    .try_into()
363                    .map_err(|_| IndexerError::InvalidResponseData)?,
364                c: proof
365                    .c
366                    .try_into()
367                    .map_err(|_| IndexerError::InvalidResponseData)?,
368            }))
369        } else {
370            ValidityProof::new(None)
371        };
372
373        let accounts = value
374            .accounts
375            .iter()
376            .map(AccountProofInputs::from_api_model)
377            .collect::<Result<Vec<_>, IndexerError>>()?;
378
379        let addresses = value
380            .addresses
381            .iter()
382            .map(AddressProofInputs::from_api_model)
383            .collect::<Result<Vec<_>, IndexerError>>()?;
384
385        Ok(Self {
386            proof,
387            accounts,
388            addresses,
389        })
390    }
391}
392
393#[derive(Clone, Copy, Default, Debug, PartialEq)]
394pub struct NextTreeInfo {
395    pub cpi_context: Option<Pubkey>,
396    pub queue: Pubkey,
397    pub tree: Pubkey,
398    pub tree_type: TreeType,
399}
400
401impl NextTreeInfo {
402    /// Get the index of the output tree in the packed accounts.
403    /// For StateV1, it returns the index of the tree account.
404    /// For StateV2, it returns the index of the queue account.
405    /// (For V2 trees new state is inserted into the output queue.
406    /// The forester updates the tree from the queue asynchronously.)
407    pub fn pack_output_tree_index(
408        &self,
409        packed_accounts: &mut PackedAccounts,
410    ) -> Result<u8, IndexerError> {
411        match self.tree_type {
412            TreeType::StateV1 => Ok(packed_accounts.insert_or_get(self.tree)),
413            TreeType::StateV2 => Ok(packed_accounts.insert_or_get(self.queue)),
414            _ => Err(IndexerError::InvalidPackTreeType),
415        }
416    }
417    pub fn from_api_model(
418        value: &photon_api::models::TreeContextInfo,
419    ) -> Result<Self, IndexerError> {
420        Ok(Self {
421            tree_type: TreeType::from(value.tree_type as u64),
422            tree: Pubkey::new_from_array(decode_base58_to_fixed_array(&value.tree)?),
423            queue: Pubkey::new_from_array(decode_base58_to_fixed_array(&value.queue)?),
424            cpi_context: decode_base58_option_to_pubkey(&value.cpi_context)?,
425        })
426    }
427}
428
429impl TryFrom<&photon_api::models::TreeContextInfo> for NextTreeInfo {
430    type Error = IndexerError;
431
432    fn try_from(value: &photon_api::models::TreeContextInfo) -> Result<Self, Self::Error> {
433        Ok(Self {
434            tree_type: TreeType::from(value.tree_type as u64),
435            tree: Pubkey::new_from_array(decode_base58_to_fixed_array(&value.tree)?),
436            queue: Pubkey::new_from_array(decode_base58_to_fixed_array(&value.queue)?),
437            cpi_context: decode_base58_option_to_pubkey(&value.cpi_context)?,
438        })
439    }
440}
441
442#[derive(Clone, Copy, Default, Debug, PartialEq)]
443pub struct TreeInfo {
444    pub cpi_context: Option<Pubkey>,
445    pub next_tree_info: Option<NextTreeInfo>,
446    pub queue: Pubkey,
447    pub tree: Pubkey,
448    pub tree_type: TreeType,
449}
450
451impl TreeInfo {
452    /// Get the index of the output tree in the packed accounts.
453    /// For StateV1, it returns the index of the tree account.
454    /// For StateV2, it returns the index of the queue account.
455    /// (For V2 trees new state is inserted into the output queue.
456    /// The forester updates the tree from the queue asynchronously.)
457    pub fn pack_output_tree_index(
458        &self,
459        packed_accounts: &mut PackedAccounts,
460    ) -> Result<u8, IndexerError> {
461        match self.tree_type {
462            TreeType::StateV1 => Ok(packed_accounts.insert_or_get(self.tree)),
463            TreeType::StateV2 => Ok(packed_accounts.insert_or_get(self.queue)),
464            _ => Err(IndexerError::InvalidPackTreeType),
465        }
466    }
467
468    pub fn get_output_pubkey(&self) -> Result<Pubkey, IndexerError> {
469        match self.tree_type {
470            TreeType::StateV1 => Ok(self.tree),
471            TreeType::StateV2 => Ok(self.queue),
472            _ => Err(IndexerError::InvalidPackTreeType),
473        }
474    }
475
476    pub fn from_api_model(
477        value: &photon_api::models::MerkleContextV2,
478    ) -> Result<Self, IndexerError> {
479        Ok(Self {
480            tree_type: TreeType::from(value.tree_type as u64),
481            tree: Pubkey::new_from_array(decode_base58_to_fixed_array(&value.tree)?),
482            queue: Pubkey::new_from_array(decode_base58_to_fixed_array(&value.queue)?),
483            cpi_context: decode_base58_option_to_pubkey(&value.cpi_context)?,
484            next_tree_info: value
485                .next_tree_context
486                .as_ref()
487                .map(|tree_info| NextTreeInfo::from_api_model(tree_info.as_ref()))
488                .transpose()?,
489        })
490    }
491
492    pub fn to_light_merkle_context(
493        &self,
494        leaf_index: u32,
495        prove_by_index: bool,
496    ) -> light_compressed_account::compressed_account::MerkleContext {
497        use light_compressed_account::Pubkey;
498        light_compressed_account::compressed_account::MerkleContext {
499            merkle_tree_pubkey: Pubkey::new_from_array(self.tree.to_bytes()),
500            queue_pubkey: Pubkey::new_from_array(self.queue.to_bytes()),
501            leaf_index,
502            tree_type: self.tree_type,
503            prove_by_index,
504        }
505    }
506}
507
508#[derive(Clone, Default, Debug, PartialEq)]
509pub struct CompressedAccount {
510    pub address: Option<[u8; 32]>,
511    pub data: Option<CompressedAccountData>,
512    pub hash: [u8; 32],
513    pub lamports: u64,
514    pub leaf_index: u32,
515    pub owner: Pubkey,
516    pub prove_by_index: bool,
517    pub seq: Option<u64>,
518    pub slot_created: u64,
519    pub tree_info: TreeInfo,
520}
521
522impl TryFrom<CompressedAccountWithMerkleContext> for CompressedAccount {
523    type Error = IndexerError;
524
525    fn try_from(account: CompressedAccountWithMerkleContext) -> Result<Self, Self::Error> {
526        let hash = account
527            .hash()
528            .map_err(|_| IndexerError::InvalidResponseData)?;
529        // Breaks light-program-test
530        let tree_info = QUEUE_TREE_MAPPING.get(
531            &Pubkey::new_from_array(account.merkle_context.merkle_tree_pubkey.to_bytes())
532                .to_string(),
533        );
534        let cpi_context = if let Some(tree_info) = tree_info {
535            tree_info.cpi_context
536        } else {
537            warn!("Cpi context not found in queue tree mapping");
538            None
539        };
540        Ok(CompressedAccount {
541            address: account.compressed_account.address,
542            data: account.compressed_account.data,
543            hash,
544            lamports: account.compressed_account.lamports,
545            leaf_index: account.merkle_context.leaf_index,
546            tree_info: TreeInfo {
547                tree: Pubkey::new_from_array(account.merkle_context.merkle_tree_pubkey.to_bytes()),
548                queue: Pubkey::new_from_array(account.merkle_context.queue_pubkey.to_bytes()),
549                tree_type: account.merkle_context.tree_type,
550                cpi_context,
551                next_tree_info: None,
552            },
553            owner: Pubkey::new_from_array(account.compressed_account.owner.to_bytes()),
554            prove_by_index: account.merkle_context.prove_by_index,
555            seq: None,
556            slot_created: u64::MAX,
557        })
558    }
559}
560
561impl From<CompressedAccount> for CompressedAccountWithMerkleContext {
562    fn from(account: CompressedAccount) -> Self {
563        use light_compressed_account::Pubkey;
564        let compressed_account = ProgramCompressedAccount {
565            owner: Pubkey::new_from_array(account.owner.to_bytes()),
566            lamports: account.lamports,
567            address: account.address,
568            data: account.data,
569        };
570
571        let merkle_context = account
572            .tree_info
573            .to_light_merkle_context(account.leaf_index, account.prove_by_index);
574
575        CompressedAccountWithMerkleContext {
576            compressed_account,
577            merkle_context,
578        }
579    }
580}
581
582impl TryFrom<&photon_api::models::AccountV2> for CompressedAccount {
583    type Error = IndexerError;
584
585    fn try_from(account: &photon_api::models::AccountV2) -> Result<Self, Self::Error> {
586        let data = if let Some(data) = &account.data {
587            Ok::<Option<CompressedAccountData>, IndexerError>(Some(CompressedAccountData {
588                discriminator: data.discriminator.to_le_bytes(),
589                data: base64::decode_config(&data.data, base64::STANDARD_NO_PAD)
590                    .map_err(|_| IndexerError::InvalidResponseData)?,
591                data_hash: decode_base58_to_fixed_array(&data.data_hash)?,
592            }))
593        } else {
594            Ok::<Option<CompressedAccountData>, IndexerError>(None)
595        }?;
596
597        let owner = Pubkey::new_from_array(decode_base58_to_fixed_array(&account.owner)?);
598        let address = account
599            .address
600            .as_ref()
601            .map(|address| decode_base58_to_fixed_array(address))
602            .transpose()?;
603        let hash = decode_base58_to_fixed_array(&account.hash)?;
604
605        let tree_info = TreeInfo {
606            tree: Pubkey::new_from_array(decode_base58_to_fixed_array(
607                &account.merkle_context.tree,
608            )?),
609            queue: Pubkey::new_from_array(decode_base58_to_fixed_array(
610                &account.merkle_context.queue,
611            )?),
612            tree_type: TreeType::from(account.merkle_context.tree_type as u64),
613            cpi_context: decode_base58_option_to_pubkey(&account.merkle_context.cpi_context)?,
614            next_tree_info: account
615                .merkle_context
616                .next_tree_context
617                .as_ref()
618                .map(|ctx| NextTreeInfo::try_from(ctx.as_ref()))
619                .transpose()?,
620        };
621
622        Ok(CompressedAccount {
623            owner,
624            address,
625            data,
626            hash,
627            lamports: account.lamports,
628            leaf_index: account.leaf_index,
629            seq: account.seq,
630            slot_created: account.slot_created,
631            tree_info,
632            prove_by_index: account.prove_by_index,
633        })
634    }
635}
636
637impl TryFrom<&photon_api::models::Account> for CompressedAccount {
638    type Error = IndexerError;
639
640    fn try_from(account: &photon_api::models::Account) -> Result<Self, Self::Error> {
641        let data = if let Some(data) = &account.data {
642            Ok::<Option<CompressedAccountData>, IndexerError>(Some(CompressedAccountData {
643                discriminator: data.discriminator.to_le_bytes(),
644                data: base64::decode_config(&data.data, base64::STANDARD_NO_PAD)
645                    .map_err(|_| IndexerError::InvalidResponseData)?,
646                data_hash: decode_base58_to_fixed_array(&data.data_hash)?,
647            }))
648        } else {
649            Ok::<Option<CompressedAccountData>, IndexerError>(None)
650        }?;
651        let owner = Pubkey::new_from_array(decode_base58_to_fixed_array(&account.owner)?);
652        let address = account
653            .address
654            .as_ref()
655            .map(|address| decode_base58_to_fixed_array(address))
656            .transpose()?;
657        let hash = decode_base58_to_fixed_array(&account.hash)?;
658        let seq = account.seq;
659        let slot_created = account.slot_created;
660        let lamports = account.lamports;
661        let leaf_index = account.leaf_index;
662
663        let tree_info = QUEUE_TREE_MAPPING
664            .get(&account.tree)
665            .ok_or(IndexerError::InvalidResponseData)?;
666
667        let tree_info = TreeInfo {
668            cpi_context: tree_info.cpi_context,
669            queue: tree_info.queue,
670            tree_type: tree_info.tree_type,
671            next_tree_info: None,
672            tree: tree_info.tree,
673        };
674
675        Ok(CompressedAccount {
676            owner,
677            address,
678            data,
679            hash,
680            lamports,
681            leaf_index,
682            seq,
683            slot_created,
684            tree_info,
685            prove_by_index: false,
686        })
687    }
688}
689
690#[derive(Debug, Clone, PartialEq, Default)]
691pub struct AddressQueueIndex {
692    pub address: [u8; 32],
693    pub queue_index: u64,
694}
695
696#[derive(Debug, Clone, PartialEq, Default)]
697pub struct BatchAddressUpdateIndexerResponse {
698    pub batch_start_index: u64,
699    pub addresses: Vec<AddressQueueIndex>,
700    pub non_inclusion_proofs: Vec<NewAddressProofWithContext>,
701    pub subtrees: Vec<[u8; 32]>,
702}
703
704#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
705pub struct StateMerkleTreeAccounts {
706    pub merkle_tree: Pubkey,
707    pub nullifier_queue: Pubkey,
708    pub cpi_context: Pubkey,
709    pub tree_type: TreeType,
710}
711
712#[allow(clippy::from_over_into)]
713impl Into<TreeInfo> for StateMerkleTreeAccounts {
714    fn into(self) -> TreeInfo {
715        TreeInfo {
716            tree: self.merkle_tree,
717            queue: self.nullifier_queue,
718            cpi_context: Some(self.cpi_context),
719            tree_type: self.tree_type,
720            next_tree_info: None,
721        }
722    }
723}
724
725#[derive(Debug, Clone, Copy)]
726pub struct AddressMerkleTreeAccounts {
727    pub merkle_tree: Pubkey,
728    pub queue: Pubkey,
729}
730
731#[derive(Clone, Default, Debug, PartialEq)]
732pub struct CompressedTokenAccount {
733    /// Token-specific data (mint, owner, amount, delegate, state, tlv)
734    pub token: TokenData,
735    /// General account information (address, hash, lamports, merkle context, etc.)
736    pub account: CompressedAccount,
737}
738
739impl TryFrom<&photon_api::models::TokenAccount> for CompressedTokenAccount {
740    type Error = IndexerError;
741
742    fn try_from(token_account: &photon_api::models::TokenAccount) -> Result<Self, Self::Error> {
743        let account = CompressedAccount::try_from(token_account.account.as_ref())?;
744
745        let token = TokenData {
746            mint: Pubkey::new_from_array(decode_base58_to_fixed_array(
747                &token_account.token_data.mint,
748            )?),
749            owner: Pubkey::new_from_array(decode_base58_to_fixed_array(
750                &token_account.token_data.owner,
751            )?),
752            amount: token_account.token_data.amount,
753            delegate: token_account
754                .token_data
755                .delegate
756                .as_ref()
757                .map(|d| decode_base58_to_fixed_array(d).map(Pubkey::new_from_array))
758                .transpose()?,
759            state: match token_account.token_data.state {
760                photon_api::models::AccountState::Initialized => AccountState::Initialized,
761                photon_api::models::AccountState::Frozen => AccountState::Frozen,
762            },
763            tlv: token_account
764                .token_data
765                .tlv
766                .as_ref()
767                .map(|tlv| base64::decode_config(tlv, base64::STANDARD_NO_PAD))
768                .transpose()
769                .map_err(|_| IndexerError::InvalidResponseData)?,
770        };
771
772        Ok(CompressedTokenAccount { token, account })
773    }
774}
775
776impl TryFrom<&photon_api::models::TokenAccountV2> for CompressedTokenAccount {
777    type Error = IndexerError;
778
779    fn try_from(token_account: &photon_api::models::TokenAccountV2) -> Result<Self, Self::Error> {
780        let account = CompressedAccount::try_from(token_account.account.as_ref())?;
781
782        let token = TokenData {
783            mint: Pubkey::new_from_array(decode_base58_to_fixed_array(
784                &token_account.token_data.mint,
785            )?),
786            owner: Pubkey::new_from_array(decode_base58_to_fixed_array(
787                &token_account.token_data.owner,
788            )?),
789            amount: token_account.token_data.amount,
790            delegate: token_account
791                .token_data
792                .delegate
793                .as_ref()
794                .map(|d| decode_base58_to_fixed_array(d).map(Pubkey::new_from_array))
795                .transpose()?,
796            state: match token_account.token_data.state {
797                photon_api::models::AccountState::Initialized => AccountState::Initialized,
798                photon_api::models::AccountState::Frozen => AccountState::Frozen,
799            },
800            tlv: token_account
801                .token_data
802                .tlv
803                .as_ref()
804                .map(|tlv| base64::decode_config(tlv, base64::STANDARD_NO_PAD))
805                .transpose()
806                .map_err(|_| IndexerError::InvalidResponseData)?,
807        };
808
809        Ok(CompressedTokenAccount { token, account })
810    }
811}
812
813#[allow(clippy::from_over_into)]
814impl Into<light_sdk::token::TokenDataWithMerkleContext> for CompressedTokenAccount {
815    fn into(self) -> light_sdk::token::TokenDataWithMerkleContext {
816        let compressed_account = CompressedAccountWithMerkleContext::from(self.account);
817
818        light_sdk::token::TokenDataWithMerkleContext {
819            token_data: self.token,
820            compressed_account,
821        }
822    }
823}
824
825#[allow(clippy::from_over_into)]
826impl Into<Vec<light_sdk::token::TokenDataWithMerkleContext>>
827    for super::response::Response<super::response::ItemsWithCursor<CompressedTokenAccount>>
828{
829    fn into(self) -> Vec<light_sdk::token::TokenDataWithMerkleContext> {
830        self.value
831            .items
832            .into_iter()
833            .map(
834                |token_account| light_sdk::token::TokenDataWithMerkleContext {
835                    token_data: token_account.token,
836                    compressed_account: CompressedAccountWithMerkleContext::from(
837                        token_account.account.clone(),
838                    ),
839                },
840            )
841            .collect::<Vec<light_sdk::token::TokenDataWithMerkleContext>>()
842    }
843}
844
845impl TryFrom<light_sdk::token::TokenDataWithMerkleContext> for CompressedTokenAccount {
846    type Error = IndexerError;
847
848    fn try_from(
849        token_data_with_context: light_sdk::token::TokenDataWithMerkleContext,
850    ) -> Result<Self, Self::Error> {
851        let account = CompressedAccount::try_from(token_data_with_context.compressed_account)?;
852
853        Ok(CompressedTokenAccount {
854            token: token_data_with_context.token_data,
855            account,
856        })
857    }
858}
859
860#[derive(Clone, Default, Debug, PartialEq)]
861pub struct TokenBalance {
862    pub balance: u64,
863    pub mint: Pubkey,
864}
865
866impl TryFrom<&photon_api::models::TokenBalance> for TokenBalance {
867    type Error = IndexerError;
868
869    fn try_from(token_balance: &photon_api::models::TokenBalance) -> Result<Self, Self::Error> {
870        Ok(TokenBalance {
871            balance: token_balance.balance,
872            mint: Pubkey::new_from_array(decode_base58_to_fixed_array(&token_balance.mint)?),
873        })
874    }
875}
876
877#[derive(Debug, Clone, PartialEq, Default)]
878pub struct SignatureWithMetadata {
879    pub block_time: u64,
880    pub signature: String,
881    pub slot: u64,
882}
883
884impl TryFrom<&photon_api::models::SignatureInfo> for SignatureWithMetadata {
885    type Error = IndexerError;
886
887    fn try_from(sig_info: &photon_api::models::SignatureInfo) -> Result<Self, Self::Error> {
888        Ok(SignatureWithMetadata {
889            block_time: sig_info.block_time,
890            signature: sig_info.signature.clone(),
891            slot: sig_info.slot,
892        })
893    }
894}
895
896#[derive(Clone, Default, Debug, PartialEq)]
897pub struct OwnerBalance {
898    pub balance: u64,
899    pub owner: Pubkey,
900}
901
902impl TryFrom<&photon_api::models::OwnerBalance> for OwnerBalance {
903    type Error = IndexerError;
904
905    fn try_from(owner_balance: &photon_api::models::OwnerBalance) -> Result<Self, Self::Error> {
906        Ok(OwnerBalance {
907            balance: owner_balance.balance,
908            owner: Pubkey::new_from_array(decode_base58_to_fixed_array(&owner_balance.owner)?),
909        })
910    }
911}