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
88impl 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 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 let Some(next) = account.tree_info.next_tree_info {
224 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 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 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 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 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])?, 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 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 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 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 pub token: TokenData,
735 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}