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