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