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