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