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