hayabusa-token 0.1.0

Hayabusa Token program interface
Documentation
// Copyright (c) 2025, Arcane Labs <dev@arcane.fi>
// SPDX-License-Identifier: Apache-2.0

use super::AccountState;
use hayabusa_ser::{FromBytesUnchecked, RawZcDeserialize, Deserialize, Zc};
use hayabusa_utility::fail_with_ctx;
use pinocchio::{
    account_info::{AccountInfo, Ref}, hint::unlikely, program_error::ProgramError, pubkey::Pubkey
};

/// Token account data.
#[repr(C)]
pub struct TokenAccount {
    /// The mint associated with this account
    mint: Pubkey,

    /// The owner of this account.
    owner: Pubkey,

    /// The amount of tokens this account holds.
    amount: [u8; 8],

    /// Indicates whether the delegate is present or not.
    delegate_flag: [u8; 4],

    /// If `delegate` is `Some` then `delegated_amount` represents
    /// the amount authorized by the delegate.
    delegate: Pubkey,

    /// The account's state.
    state: u8,

    /// Indicates whether this account represents a native token or not.
    is_native: [u8; 4],

    /// When `is_native.is_some()` is `true`, this is a native token, and the
    /// value logs the rent-exempt reserve. An Account is required to be rent-exempt,
    /// so the value is used by the Processor to ensure that wrapped SOL
    /// accounts do not drop below this threshold.
    native_amount: [u8; 8],

    /// The amount delegated.
    delegated_amount: [u8; 8],

    /// Indicates whether the close authority is present or not.
    close_authority_flag: [u8; 4],

    /// Optional authority to close the account.
    close_authority: Pubkey,
}

/// Return a `TokenAccount` from the given bytes.
///
/// # Safety
///
/// The caller must ensure that `bytes` contains a valid representation of `TokenAccount`, and
/// it is properly aligned to be interpreted as an instance of `TokenAccount`.
/// At the moment `TokenAccount` has an alignment of 1 byte.
/// This method does not perform a length validation.
impl FromBytesUnchecked for TokenAccount {}

impl Zc for TokenAccount {}
impl Deserialize for TokenAccount {}

unsafe impl RawZcDeserialize for TokenAccount {
    #[inline]
    fn try_deserialize_raw<'a>(
        account_info: &'a AccountInfo,
    ) -> Result<Ref<'a, Self>, ProgramError> {
        if unlikely(account_info.data_len() != Self::LEN) {
            fail_with_ctx!(
                "HAYABUSA_SER_TOKEN_ACCOUNT_DATA_TOO_SHORT",
                ProgramError::InvalidAccountData,
                account_info.key(),
                &u32::to_le_bytes(account_info.data_len() as u32),
                &u32::to_le_bytes(Self::LEN as u32),
            );
        }

        if unlikely(!account_info.is_owned_by(&crate::ID)) {
            fail_with_ctx!(
                "HAYABUSA_SER_TOKEN_ACCOUNT_INVALID_OWNER",
                ProgramError::InvalidAccountOwner,
                account_info.key(),
                account_info.owner(),
                &crate::ID,
            );
        }

        Ok(Ref::map(account_info.try_borrow_data()?, |d| unsafe {
            Self::from_bytes_unchecked(d)
        }))
    }
}

impl TokenAccount {
    pub const LEN: usize = core::mem::size_of::<TokenAccount>();

    pub fn mint(&self) -> &Pubkey {
        &self.mint
    }

    pub fn owner(&self) -> &Pubkey {
        &self.owner
    }

    pub fn amount(&self) -> u64 {
        u64::from_le_bytes(self.amount)
    }

    #[inline(always)]
    pub fn has_delegate(&self) -> bool {
        self.delegate_flag[0] == 1
    }

    pub fn delegate(&self) -> Option<&Pubkey> {
        if self.has_delegate() {
            Some(self.delegate_unchecked())
        } else {
            None
        }
    }

    /// Use this when you know the account will have a delegate and want to skip the `Option` check.
    #[inline(always)]
    pub fn delegate_unchecked(&self) -> &Pubkey {
        &self.delegate
    }

    #[inline(always)]
    pub fn state(&self) -> AccountState {
        self.state.into()
    }

    #[inline(always)]
    pub fn is_native(&self) -> bool {
        self.is_native[0] == 1
    }

    pub fn native_amount(&self) -> Option<u64> {
        if self.is_native() {
            Some(self.native_amount_unchecked())
        } else {
            None
        }
    }

    /// Return the native amount.
    ///
    /// This method should be used when the caller knows that the token is native since it
    /// skips the `Option` check.
    #[inline(always)]
    pub fn native_amount_unchecked(&self) -> u64 {
        u64::from_le_bytes(self.native_amount)
    }

    pub fn delegated_amount(&self) -> u64 {
        u64::from_le_bytes(self.delegated_amount)
    }

    #[inline(always)]
    pub fn has_close_authority(&self) -> bool {
        self.close_authority_flag[0] == 1
    }

    pub fn close_authority(&self) -> Option<&Pubkey> {
        if self.has_close_authority() {
            Some(self.close_authority_unchecked())
        } else {
            None
        }
    }

    /// Return the close authority.
    ///
    /// This method should be used when the caller knows that the token will have a close
    /// authority set since it skips the `Option` check.
    #[inline(always)]
    pub fn close_authority_unchecked(&self) -> &Pubkey {
        &self.close_authority
    }

    #[inline(always)]
    pub fn is_initialized(&self) -> bool {
        self.state != AccountState::Uninitialized as u8
    }

    #[inline(always)]
    pub fn is_frozen(&self) -> bool {
        self.state == AccountState::Frozen as u8
    }
}