use light_token::utils::get_associated_token_address_and_bump;
use light_token_interface::state::ExtensionStruct;
use solana_account::Account;
use solana_pubkey::Pubkey;
use spl_pod::{
bytemuck::{pod_bytes_of, pod_from_bytes, pod_get_packed_len},
primitives::PodU64,
};
use spl_token_2022_interface::{
pod::{PodAccount, PodCOption},
state::AccountState,
};
use thiserror::Error;
use crate::indexer::{CompressedAccount, CompressedTokenAccount, TreeInfo};
#[derive(Debug, Error)]
pub enum AccountInterfaceError {
#[error("Account not found")]
NotFound,
#[error("Invalid account data")]
InvalidData,
#[error("Parse error: {0}")]
ParseError(String),
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct AccountInterface {
pub key: Pubkey,
pub account: Account,
pub cold: Option<CompressedAccount>,
}
impl AccountInterface {
pub fn hot(key: Pubkey, account: Account) -> Self {
Self {
key,
account,
cold: None,
}
}
pub fn cold(key: Pubkey, compressed: CompressedAccount, owner: Pubkey) -> Self {
let data = compressed
.data
.as_ref()
.map(|d| {
let mut buf = d.discriminator.to_vec();
buf.extend_from_slice(&d.data);
buf
})
.unwrap_or_default();
Self {
key,
account: Account {
lamports: compressed.lamports,
data,
owner,
executable: false,
rent_epoch: 0,
},
cold: Some(compressed),
}
}
#[inline]
pub fn is_cold(&self) -> bool {
self.cold.is_some()
}
#[inline]
pub fn is_hot(&self) -> bool {
self.cold.is_none()
}
#[inline]
pub fn data(&self) -> &[u8] {
&self.account.data
}
pub fn hash(&self) -> Option<[u8; 32]> {
self.cold.as_ref().map(|c| c.hash)
}
pub fn tree_info(&self) -> Option<&TreeInfo> {
self.cold.as_ref().map(|c| &c.tree_info)
}
pub fn leaf_index(&self) -> Option<u32> {
self.cold.as_ref().map(|c| c.leaf_index)
}
pub fn as_compressed_account(&self) -> Option<&CompressedAccount> {
self.cold.as_ref()
}
pub fn as_mint(&self) -> Option<light_token_interface::state::Mint> {
let ca = self.cold.as_ref()?;
let data = ca.data.as_ref()?;
borsh::BorshDeserialize::deserialize(&mut data.data.as_slice()).ok()
}
pub fn mint_signer(&self) -> Option<[u8; 32]> {
self.as_mint().map(|m| m.metadata.mint_signer)
}
pub fn mint_compressed_address(&self) -> Option<[u8; 32]> {
self.as_mint().map(|m| m.metadata.compressed_address())
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct TokenAccountInterface {
pub key: Pubkey,
pub account: Account,
pub parsed: PodAccount,
pub cold: Option<CompressedTokenAccount>,
pub extensions: Option<Vec<ExtensionStruct>>,
}
impl TokenAccountInterface {
pub fn hot(key: Pubkey, account: Account) -> Result<Self, AccountInterfaceError> {
let pod_len = pod_get_packed_len::<PodAccount>();
if account.data.len() < pod_len {
return Err(AccountInterfaceError::InvalidData);
}
let parsed: &PodAccount = pod_from_bytes(&account.data[..pod_len])
.map_err(|e| AccountInterfaceError::ParseError(e.to_string()))?;
Ok(Self {
key,
parsed: *parsed,
account,
cold: None,
extensions: None,
})
}
pub fn cold(
key: Pubkey,
compressed: CompressedTokenAccount,
owner_override: Pubkey,
program_owner: Pubkey,
) -> Self {
use light_token::compat::AccountState as LightAccountState;
let token = &compressed.token;
let parsed = PodAccount {
mint: token.mint,
owner: owner_override,
amount: PodU64::from(token.amount),
delegate: match token.delegate {
Some(pk) => PodCOption::some(pk),
None => PodCOption::none(),
},
state: match token.state {
LightAccountState::Frozen => AccountState::Frozen as u8,
_ => AccountState::Initialized as u8,
},
is_native: PodCOption::none(),
delegated_amount: PodU64::from(0u64),
close_authority: PodCOption::none(),
};
let data = pod_bytes_of(&parsed).to_vec();
let extensions = token.tlv.clone();
let account = Account {
lamports: compressed.account.lamports,
data,
owner: program_owner,
executable: false,
rent_epoch: 0,
};
Self {
key,
account,
parsed,
cold: Some(compressed),
extensions,
}
}
#[inline]
pub fn is_cold(&self) -> bool {
self.cold.is_some()
}
#[inline]
pub fn is_hot(&self) -> bool {
self.cold.is_none()
}
pub fn compressed(&self) -> Option<&CompressedTokenAccount> {
self.cold.as_ref()
}
#[inline]
pub fn amount(&self) -> u64 {
u64::from(self.parsed.amount)
}
#[inline]
pub fn delegate(&self) -> Option<Pubkey> {
if self.parsed.delegate.is_some() {
Some(self.parsed.delegate.value)
} else {
None
}
}
#[inline]
pub fn mint(&self) -> Pubkey {
self.parsed.mint
}
#[inline]
pub fn owner(&self) -> Pubkey {
self.parsed.owner
}
#[inline]
pub fn is_frozen(&self) -> bool {
self.parsed.state == AccountState::Frozen as u8
}
#[inline]
pub fn hash(&self) -> Option<[u8; 32]> {
self.compressed().map(|c| c.account.hash)
}
#[inline]
pub fn tree_info(&self) -> Option<&TreeInfo> {
self.compressed().map(|c| &c.account.tree_info)
}
#[inline]
pub fn leaf_index(&self) -> Option<u32> {
self.compressed().map(|c| c.account.leaf_index)
}
pub fn ata_bump(&self) -> Option<u8> {
let (derived_ata, bump) =
get_associated_token_address_and_bump(&self.parsed.owner, &self.parsed.mint);
(derived_ata == self.key).then_some(bump)
}
pub fn is_ata(&self) -> bool {
self.ata_bump().is_some()
}
}
impl From<TokenAccountInterface> for AccountInterface {
fn from(tai: TokenAccountInterface) -> Self {
Self {
key: tai.key,
account: tai.account,
cold: tai.cold.map(|ct| ct.account),
}
}
}