use crate::core::events::{EventMetadata, TokenAccountEvent, TokenInfoEvent};
use crate::DexEvent;
use solana_sdk::pubkey::Pubkey;
use spl_token::solana_program::program_pack::Pack;
use spl_token::state::{Account, Mint};
use spl_token_2022::{
extension::StateWithExtensions,
state::{Account as Account2022, Mint as Mint2022},
};
#[derive(Clone, Debug)]
pub struct AccountData {
pub pubkey: Pubkey,
pub executable: bool,
pub lamports: u64,
pub owner: Pubkey,
pub rent_epoch: u64,
pub data: Vec<u8>,
}
pub fn parse_token_account(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
if account.data.len() <= 100 {
if let Some(event) = parse_mint_fast(account, metadata.clone()) {
return Some(event);
}
}
if let Some(event) = parse_token_fast(account, metadata.clone()) {
return Some(event);
}
parse_token_with_extensions(account, metadata)
}
#[inline]
fn parse_mint_fast(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
const MINT_SIZE: usize = 82;
const SUPPLY_OFFSET: usize = 36;
const DECIMALS_OFFSET: usize = 44;
if account.data.len() < MINT_SIZE {
return None;
}
let supply_bytes: [u8; 8] = account.data[SUPPLY_OFFSET..SUPPLY_OFFSET + 8].try_into().ok()?;
let supply = u64::from_le_bytes(supply_bytes);
let decimals = account.data[DECIMALS_OFFSET];
let event = TokenInfoEvent {
metadata,
pubkey: account.pubkey,
executable: account.executable,
lamports: account.lamports,
owner: account.owner,
rent_epoch: account.rent_epoch,
supply,
decimals,
};
Some(DexEvent::TokenInfo(event))
}
#[inline]
fn parse_token_fast(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
const TOKEN_ACCOUNT_SIZE: usize = 165;
const AMOUNT_OFFSET: usize = 64;
if account.data.len() < AMOUNT_OFFSET + 8 {
return None;
}
if account.data.len() != TOKEN_ACCOUNT_SIZE {
return None;
}
let amount_bytes: [u8; 8] = account.data[AMOUNT_OFFSET..AMOUNT_OFFSET + 8].try_into().ok()?;
let amount = u64::from_le_bytes(amount_bytes);
let event = TokenAccountEvent {
metadata,
pubkey: account.pubkey,
executable: account.executable,
lamports: account.lamports,
owner: account.owner,
rent_epoch: account.rent_epoch,
amount: Some(amount),
token_owner: account.owner,
};
Some(DexEvent::TokenAccount(event))
}
fn parse_token_with_extensions(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
if account.data.len() >= Mint2022::LEN {
if let Ok(mint_state) = StateWithExtensions::<Mint2022>::unpack(&account.data) {
let event = TokenInfoEvent {
metadata,
pubkey: account.pubkey,
executable: account.executable,
lamports: account.lamports,
owner: account.owner,
rent_epoch: account.rent_epoch,
supply: mint_state.base.supply,
decimals: mint_state.base.decimals,
};
return Some(DexEvent::TokenInfo(event));
}
}
if account.data.len() >= Mint::LEN {
if let Ok(mint) = Mint::unpack_from_slice(&account.data) {
let event = TokenInfoEvent {
metadata,
pubkey: account.pubkey,
executable: account.executable,
lamports: account.lamports,
owner: account.owner,
rent_epoch: account.rent_epoch,
supply: mint.supply,
decimals: mint.decimals,
};
return Some(DexEvent::TokenInfo(event));
}
}
if account.owner.to_bytes() == spl_token_2022::ID.to_bytes() {
if let Ok(account_state) = StateWithExtensions::<Account2022>::unpack(&account.data) {
let token_owner = Pubkey::new_from_array(account_state.base.owner.to_bytes());
let event = TokenAccountEvent {
metadata,
pubkey: account.pubkey,
executable: account.executable,
lamports: account.lamports,
owner: account.owner,
rent_epoch: account.rent_epoch,
amount: Some(account_state.base.amount),
token_owner,
};
return Some(DexEvent::TokenAccount(event));
}
}
if let Ok(token_account) = Account::unpack(&account.data) {
let token_owner = Pubkey::new_from_array(token_account.owner.to_bytes());
let event = TokenAccountEvent {
metadata,
pubkey: account.pubkey,
executable: account.executable,
lamports: account.lamports,
owner: account.owner,
rent_epoch: account.rent_epoch,
amount: Some(token_account.amount),
token_owner,
};
return Some(DexEvent::TokenAccount(event));
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_mint_fast() {
let mut data = vec![0u8; 82];
data[36..44].copy_from_slice(&1000000u64.to_le_bytes());
data[44] = 6;
let account = AccountData {
pubkey: Pubkey::new_unique(),
executable: false,
lamports: 1000000,
owner: Pubkey::new_from_array(spl_token::ID.to_bytes()),
rent_epoch: 0,
data,
};
let metadata = EventMetadata::default();
let event = parse_mint_fast(&account, metadata);
assert!(event.is_some());
if let Some(DexEvent::TokenInfo(info)) = event {
assert_eq!(info.supply, 1000000);
assert_eq!(info.decimals, 6);
}
}
#[test]
fn test_parse_token_fast() {
let mut data = vec![0u8; 165];
data[64..72].copy_from_slice(&5000u64.to_le_bytes());
let account = AccountData {
pubkey: Pubkey::new_unique(),
executable: false,
lamports: 2039280,
owner: Pubkey::new_from_array(spl_token::ID.to_bytes()),
rent_epoch: 0,
data,
};
let metadata = EventMetadata::default();
let event = parse_token_fast(&account, metadata);
assert!(event.is_some());
if let Some(DexEvent::TokenAccount(token_account)) = event {
assert_eq!(token_account.amount, Some(5000));
}
}
}