sol_parser_sdk/accounts/
token.rs1use crate::core::events::{EventMetadata, TokenAccountEvent, TokenInfoEvent};
13use crate::DexEvent;
14use solana_sdk::pubkey::Pubkey;
15use spl_token::solana_program::program_pack::Pack;
16use spl_token::state::{Account, Mint};
17use spl_token_2022::{
18 extension::StateWithExtensions,
19 state::{Account as Account2022, Mint as Mint2022},
20};
21
22#[derive(Clone, Debug)]
23pub struct AccountData {
24 pub pubkey: Pubkey,
25 pub executable: bool,
26 pub lamports: u64,
27 pub owner: Pubkey,
28 pub rent_epoch: u64,
29 pub data: Vec<u8>,
30}
31
32pub fn parse_token_account(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
43 if account.data.len() <= 100 {
45 if let Some(event) = parse_mint_fast(account, metadata.clone()) {
46 return Some(event);
47 }
48 }
49
50 if let Some(event) = parse_token_fast(account, metadata.clone()) {
51 return Some(event);
52 }
53
54 parse_token_with_extensions(account, metadata)
56}
57
58#[inline]
62fn parse_mint_fast(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
63 const MINT_SIZE: usize = 82;
64 const SUPPLY_OFFSET: usize = 36;
65 const DECIMALS_OFFSET: usize = 44;
66
67 if account.data.len() < MINT_SIZE {
68 return None;
69 }
70
71 let supply_bytes: [u8; 8] = account.data[SUPPLY_OFFSET..SUPPLY_OFFSET + 8].try_into().ok()?;
72 let supply = u64::from_le_bytes(supply_bytes);
73 let decimals = account.data[DECIMALS_OFFSET];
74
75 let event = TokenInfoEvent {
76 metadata,
77 pubkey: account.pubkey,
78 executable: account.executable,
79 lamports: account.lamports,
80 owner: account.owner,
81 rent_epoch: account.rent_epoch,
82 supply,
83 decimals,
84 };
85
86 Some(DexEvent::TokenInfo(event))
87}
88
89#[inline]
93fn parse_token_fast(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
94 const TOKEN_ACCOUNT_SIZE: usize = 165;
95 const AMOUNT_OFFSET: usize = 64;
96
97 if account.data.len() < AMOUNT_OFFSET + 8 {
98 return None;
99 }
100
101 if account.data.len() != TOKEN_ACCOUNT_SIZE {
103 return None;
104 }
105
106 let amount_bytes: [u8; 8] = account.data[AMOUNT_OFFSET..AMOUNT_OFFSET + 8].try_into().ok()?;
107 let amount = u64::from_le_bytes(amount_bytes);
108
109 let event = TokenAccountEvent {
110 metadata,
111 pubkey: account.pubkey,
112 executable: account.executable,
113 lamports: account.lamports,
114 owner: account.owner,
115 rent_epoch: account.rent_epoch,
116 amount: Some(amount),
117 token_owner: account.owner,
118 };
119
120 Some(DexEvent::TokenAccount(event))
121}
122
123fn parse_token_with_extensions(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
127 if account.data.len() >= Mint2022::LEN {
129 if let Ok(mint_state) = StateWithExtensions::<Mint2022>::unpack(&account.data) {
130 let event = TokenInfoEvent {
131 metadata,
132 pubkey: account.pubkey,
133 executable: account.executable,
134 lamports: account.lamports,
135 owner: account.owner,
136 rent_epoch: account.rent_epoch,
137 supply: mint_state.base.supply,
138 decimals: mint_state.base.decimals,
139 };
140 return Some(DexEvent::TokenInfo(event));
141 }
142 }
143
144 if account.data.len() >= Mint::LEN {
146 if let Ok(mint) = Mint::unpack_from_slice(&account.data) {
147 let event = TokenInfoEvent {
148 metadata,
149 pubkey: account.pubkey,
150 executable: account.executable,
151 lamports: account.lamports,
152 owner: account.owner,
153 rent_epoch: account.rent_epoch,
154 supply: mint.supply,
155 decimals: mint.decimals,
156 };
157 return Some(DexEvent::TokenInfo(event));
158 }
159 }
160
161 if account.owner.to_bytes() == spl_token_2022::ID.to_bytes() {
163 if let Ok(account_state) = StateWithExtensions::<Account2022>::unpack(&account.data) {
164 let token_owner = Pubkey::new_from_array(account_state.base.owner.to_bytes());
166 let event = TokenAccountEvent {
167 metadata,
168 pubkey: account.pubkey,
169 executable: account.executable,
170 lamports: account.lamports,
171 owner: account.owner,
172 rent_epoch: account.rent_epoch,
173 amount: Some(account_state.base.amount),
174 token_owner,
175 };
176 return Some(DexEvent::TokenAccount(event));
177 }
178 }
179
180 if let Ok(token_account) = Account::unpack(&account.data) {
182 let token_owner = Pubkey::new_from_array(token_account.owner.to_bytes());
184 let event = TokenAccountEvent {
185 metadata,
186 pubkey: account.pubkey,
187 executable: account.executable,
188 lamports: account.lamports,
189 owner: account.owner,
190 rent_epoch: account.rent_epoch,
191 amount: Some(token_account.amount),
192 token_owner,
193 };
194 return Some(DexEvent::TokenAccount(event));
195 }
196
197 None
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn test_parse_mint_fast() {
206 let mut data = vec![0u8; 82];
208 data[36..44].copy_from_slice(&1000000u64.to_le_bytes());
210 data[44] = 6;
212
213 let account = AccountData {
214 pubkey: Pubkey::new_unique(),
215 executable: false,
216 lamports: 1000000,
217 owner: Pubkey::new_from_array(spl_token::ID.to_bytes()),
218 rent_epoch: 0,
219 data,
220 };
221
222 let metadata = EventMetadata::default();
223 let event = parse_mint_fast(&account, metadata);
224
225 assert!(event.is_some());
226 if let Some(DexEvent::TokenInfo(info)) = event {
227 assert_eq!(info.supply, 1000000);
228 assert_eq!(info.decimals, 6);
229 }
230 }
231
232 #[test]
233 fn test_parse_token_fast() {
234 let mut data = vec![0u8; 165];
236 data[64..72].copy_from_slice(&5000u64.to_le_bytes());
238
239 let account = AccountData {
240 pubkey: Pubkey::new_unique(),
241 executable: false,
242 lamports: 2039280,
243 owner: Pubkey::new_from_array(spl_token::ID.to_bytes()),
244 rent_epoch: 0,
245 data,
246 };
247
248 let metadata = EventMetadata::default();
249 let event = parse_token_fast(&account, metadata);
250
251 assert!(event.is_some());
252 if let Some(DexEvent::TokenAccount(token_account)) = event {
253 assert_eq!(token_account.amount, Some(5000));
254 }
255 }
256}