light_token_interface/state/token/
token_struct.rs

1use light_compressed_account::Pubkey;
2use light_zero_copy::errors::ZeroCopyError;
3
4use crate::{state::ExtensionStruct, AnchorDeserialize, AnchorSerialize, TokenError};
5
6/// AccountType discriminator value for token accounts (at byte 165)
7pub const ACCOUNT_TYPE_TOKEN_ACCOUNT: u8 = 2;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, AnchorSerialize, AnchorDeserialize)]
10#[repr(u8)]
11pub enum AccountState {
12    Uninitialized = 0,
13    Initialized = 1,
14    Frozen = 2,
15}
16
17impl TryFrom<u8> for AccountState {
18    type Error = TokenError;
19
20    fn try_from(value: u8) -> Result<Self, Self::Error> {
21        match value {
22            0 => Ok(AccountState::Uninitialized),
23            1 => Ok(AccountState::Initialized),
24            2 => Ok(AccountState::Frozen),
25            _ => Err(TokenError::InvalidAccountState),
26        }
27    }
28}
29
30/// Ctoken account structure (same as SPL Token Account but with extensions).
31/// Ctokens are solana accounts, compressed tokens are stored
32/// as TokenData that is optimized for compressed accounts.
33#[derive(Debug, PartialEq, Eq, Hash, Clone)]
34pub struct Token {
35    /// The mint associated with this account
36    pub mint: Pubkey,
37    /// The owner of this account.
38    pub owner: Pubkey,
39    /// The amount of tokens this account holds.
40    pub amount: u64,
41    /// If `delegate` is `Some` then `delegated_amount` represents
42    /// the amount authorized by the delegate
43    pub delegate: Option<Pubkey>,
44    /// The account's state
45    pub state: AccountState,
46    /// If `is_some`, this is a native token, and the value logs the rent-exempt
47    /// reserve. An Account is required to be rent-exempt, so the value is
48    /// used by the Processor to ensure that wrapped SOL accounts do not
49    /// drop below this threshold.
50    pub is_native: Option<u64>,
51    /// The amount delegated
52    pub delegated_amount: u64,
53    /// Optional authority to close the account.
54    pub close_authority: Option<Pubkey>,
55    /// Account type discriminator (at byte 165 when extensions present).
56    /// For valid Token accounts this is ACCOUNT_TYPE_TOKEN_ACCOUNT (2).
57    pub account_type: u8,
58    /// Extensions for the token account (including compressible config)
59    pub extensions: Option<Vec<ExtensionStruct>>,
60}
61
62impl Token {
63    /// Extract amount directly from account data slice using hardcoded offset
64    /// Token layout: mint (32 bytes) + owner (32 bytes) + amount (8 bytes)
65    pub fn amount_from_slice(data: &[u8]) -> Result<u64, ZeroCopyError> {
66        const AMOUNT_OFFSET: usize = 64; // 32 (mint) + 32 (owner)
67
68        check_token_account(data)?;
69
70        #[inline(always)]
71        fn check_token_account(bytes: &[u8]) -> Result<(), ZeroCopyError> {
72            if bytes.len() == 165 || (bytes.len() > 165 && bytes[165] == ACCOUNT_TYPE_TOKEN_ACCOUNT)
73            {
74                Ok(())
75            } else {
76                Err(ZeroCopyError::InvalidConversion)
77            }
78        }
79
80        let amount_bytes = &data[AMOUNT_OFFSET..AMOUNT_OFFSET + 8];
81        let amount = u64::from_le_bytes(amount_bytes.try_into().map_err(|_| ZeroCopyError::Size)?);
82
83        Ok(amount)
84    }
85
86    /// Extract amount from an AccountInfo
87    #[cfg(feature = "solana")]
88    pub fn amount_from_account_info(
89        account_info: &solana_account_info::AccountInfo,
90    ) -> Result<u64, ZeroCopyError> {
91        let data = account_info
92            .try_borrow_data()
93            .map_err(|_| ZeroCopyError::Size)?;
94        Self::amount_from_slice(&data)
95    }
96
97    /// Checks if account is frozen
98    pub fn is_frozen(&self) -> bool {
99        self.state == AccountState::Frozen
100    }
101
102    /// Checks if account is native
103    pub fn is_native(&self) -> bool {
104        self.is_native.is_some()
105    }
106
107    /// Checks if account is initialized
108    pub fn is_initialized(&self) -> bool {
109        self.state == AccountState::Initialized
110    }
111
112    /// Returns the account type discriminator
113    #[inline(always)]
114    pub fn account_type(&self) -> u8 {
115        self.account_type
116    }
117
118    /// Checks if account_type matches Token discriminator value
119    #[inline(always)]
120    pub fn is_token_account(&self) -> bool {
121        self.account_type == ACCOUNT_TYPE_TOKEN_ACCOUNT
122    }
123}