1pub mod nonce;
2pub mod orca_whirlpool;
3pub mod program_ids;
4pub mod pumpswap;
5pub mod raydium_clmm;
6pub mod raydium_cpmm;
7pub mod rpc_wallet;
8pub mod token;
9pub mod utils;
10use crate::core::events::EventMetadata;
11use crate::grpc::EventTypeFilter;
12use crate::DexEvent;
13pub use nonce::parse_nonce_account;
14use program_ids::*;
15pub use pumpswap::{
16 parse_global_config as parse_pumpswap_global_config, parse_pool as parse_pumpswap_pool,
17};
18pub use rpc_wallet::rpc_resolve_user_wallet_pubkey;
19pub use token::parse_token_account;
20pub use token::AccountData;
21pub use utils::*;
22
23#[inline(always)]
24fn filter_parsed_event(
25 event: Option<DexEvent>,
26 event_type_filter: Option<&EventTypeFilter>,
27) -> Option<DexEvent> {
28 let event = event?;
29 if event_type_filter.map(|f| f.should_include_dex_event(&event)).unwrap_or(true) {
30 Some(event)
31 } else {
32 None
33 }
34}
35
36pub fn parse_account_unified(
37 account: &AccountData,
38 metadata: EventMetadata,
39 event_type_filter: Option<&EventTypeFilter>,
40) -> Option<DexEvent> {
41 if account.data.is_empty() {
42 return None;
43 }
44
45 if let Some(filter) = event_type_filter {
47 if let Some(ref include_only) = filter.include_only {
48 let should_parse = include_only.iter().any(|t| {
50 use crate::grpc::EventType;
51 matches!(
52 t,
53 EventType::TokenAccount
54 | EventType::TokenInfo
55 | EventType::NonceAccount
56 | EventType::AccountPumpFunGlobal
57 | EventType::AccountPumpFunBondingCurve
58 | EventType::AccountPumpFunFeeConfig
59 | EventType::AccountPumpFunSharingConfig
60 | EventType::AccountPumpFunGlobalVolumeAccumulator
61 | EventType::AccountPumpFunUserVolumeAccumulator
62 | EventType::AccountPumpSwapGlobalConfig
63 | EventType::AccountPumpSwapPool
64 | EventType::AccountRaydiumClmmAmmConfig
65 | EventType::AccountRaydiumClmmPoolState
66 | EventType::AccountRaydiumClmmTickArrayState
67 | EventType::AccountRaydiumCpmmAmmConfig
68 | EventType::AccountRaydiumCpmmPoolState
69 | EventType::AccountOrcaWhirlpool
70 | EventType::AccountOrcaPosition
71 | EventType::AccountOrcaTickArray
72 | EventType::AccountOrcaFeeTier
73 | EventType::AccountOrcaWhirlpoolsConfig
74 )
75 });
76 if !should_parse {
77 return None;
78 }
79 }
80 }
81
82 if account.owner == PUMPSWAP_PROGRAM_ID {
83 let should_parse = event_type_filter.is_none_or(|filter| {
84 filter.should_include(crate::grpc::EventType::AccountPumpSwapGlobalConfig)
85 || filter.should_include(crate::grpc::EventType::AccountPumpSwapPool)
86 });
87 if should_parse {
88 let event = filter_parsed_event(
89 parse_pumpswap_account(account, metadata.clone()),
90 event_type_filter,
91 );
92 if event.is_some() {
93 return event;
94 }
95 }
96 return None;
97 }
98 if account.owner == crate::instr::program_ids::RAYDIUM_CLMM_PROGRAM_ID {
99 let should_parse = event_type_filter.is_none_or(|filter| {
100 filter.should_include(crate::grpc::EventType::AccountRaydiumClmmAmmConfig)
101 || filter.should_include(crate::grpc::EventType::AccountRaydiumClmmPoolState)
102 || filter.should_include(crate::grpc::EventType::AccountRaydiumClmmTickArrayState)
103 });
104 if should_parse {
105 let event = filter_parsed_event(
106 raydium_clmm::parse_account(account, metadata.clone()),
107 event_type_filter,
108 );
109 if event.is_some() {
110 return event;
111 }
112 }
113 return None;
114 }
115 if account.owner == crate::instr::program_ids::RAYDIUM_CPMM_PROGRAM_ID {
116 let should_parse = event_type_filter.is_none_or(|filter| {
117 filter.should_include(crate::grpc::EventType::AccountRaydiumCpmmAmmConfig)
118 || filter.should_include(crate::grpc::EventType::AccountRaydiumCpmmPoolState)
119 });
120 if should_parse {
121 let event = filter_parsed_event(
122 raydium_cpmm::parse_account(account, metadata.clone()),
123 event_type_filter,
124 );
125 if event.is_some() {
126 return event;
127 }
128 }
129 return None;
130 }
131 if account.owner == crate::instr::program_ids::ORCA_WHIRLPOOL_PROGRAM_ID {
132 let should_parse = event_type_filter.is_none_or(|filter| {
133 filter.should_include(crate::grpc::EventType::AccountOrcaWhirlpool)
134 || filter.should_include(crate::grpc::EventType::AccountOrcaPosition)
135 || filter.should_include(crate::grpc::EventType::AccountOrcaTickArray)
136 || filter.should_include(crate::grpc::EventType::AccountOrcaFeeTier)
137 || filter.should_include(crate::grpc::EventType::AccountOrcaWhirlpoolsConfig)
138 });
139 if should_parse {
140 let event = filter_parsed_event(
141 orca_whirlpool::parse_account(account, metadata.clone()),
142 event_type_filter,
143 );
144 if event.is_some() {
145 return event;
146 }
147 }
148 return None;
149 }
150 if account.owner == crate::grpc::program_ids::PUMPFUN_PROGRAM
151 || account.owner == crate::instr::program_ids::PUMP_FEES_PROGRAM_ID
152 {
153 let should_parse = event_type_filter.is_none_or(|filter| {
154 filter.should_include(crate::grpc::EventType::AccountPumpFunGlobal)
155 || filter.should_include(crate::grpc::EventType::AccountPumpFunBondingCurve)
156 || filter.should_include(crate::grpc::EventType::AccountPumpFunFeeConfig)
157 || filter.should_include(crate::grpc::EventType::AccountPumpFunSharingConfig)
158 || filter
159 .should_include(crate::grpc::EventType::AccountPumpFunGlobalVolumeAccumulator)
160 || filter
161 .should_include(crate::grpc::EventType::AccountPumpFunUserVolumeAccumulator)
162 });
163 if should_parse {
164 let event = filter_parsed_event(
165 parse_pumpfun_account(account, metadata.clone()),
166 event_type_filter,
167 );
168 if event.is_some() {
169 return event;
170 }
171 }
172 return None;
173 }
174 if nonce::is_nonce_account(&account.data) {
175 if let Some(filter) = event_type_filter {
177 if !filter.should_include(crate::grpc::EventType::NonceAccount) {
178 return None;
179 }
180 }
181 return parse_nonce_account(account, metadata);
182 }
183 if let Some(filter) = event_type_filter {
185 let includes_token = filter.should_include(crate::grpc::EventType::TokenAccount)
186 || filter.should_include(crate::grpc::EventType::TokenInfo);
187 if !includes_token {
188 return None;
189 }
190 }
191 filter_parsed_event(parse_token_account(account, metadata), event_type_filter)
192}
193
194fn parse_pumpswap_account(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
195 if pumpswap::is_global_config_account(&account.data) {
197 return pumpswap::parse_global_config(account, metadata);
198 }
199 if pumpswap::is_pool_account(&account.data) {
200 return pumpswap::parse_pool(account, metadata);
201 }
202 None
203}
204
205fn parse_pumpfun_account(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
206 use crate::core::events::{
207 PumpFeesConfigStatus, PumpFeesFeeTier, PumpFeesFees, PumpFeesShareholder,
208 PumpFunBondingCurve, PumpFunBondingCurveAccountEvent, PumpFunFeeConfig,
209 PumpFunFeeConfigAccountEvent, PumpFunGlobal, PumpFunGlobalAccountEvent,
210 PumpFunGlobalVolumeAccumulator, PumpFunGlobalVolumeAccumulatorAccountEvent,
211 PumpFunSharingConfig, PumpFunSharingConfigAccountEvent, PumpFunUserVolumeAccumulator,
212 PumpFunUserVolumeAccumulatorAccountEvent,
213 };
214
215 const GLOBAL_DISCRIMINATOR: &[u8; 8] = &[167, 232, 232, 177, 200, 108, 114, 127];
216 const BONDING_CURVE_DISCRIMINATOR: &[u8; 8] = &[23, 183, 248, 55, 96, 216, 172, 96];
217 const FEE_CONFIG_DISCRIMINATOR: &[u8; 8] = &[143, 52, 146, 187, 219, 123, 76, 155];
218 const GLOBAL_VOLUME_ACCUMULATOR_DISCRIMINATOR: &[u8; 8] =
219 &[202, 42, 246, 43, 142, 190, 30, 255];
220 const SHARING_CONFIG_DISCRIMINATOR: &[u8; 8] = &[216, 74, 9, 0, 56, 140, 93, 75];
221 const USER_VOLUME_ACCUMULATOR_DISCRIMINATOR: &[u8; 8] = &[86, 255, 112, 14, 102, 53, 154, 250];
222 const MAX_FEE_TIERS: usize = 64;
223 const MAX_SHAREHOLDERS: usize = 64;
224
225 fn read_i64(data: &[u8], offset: &mut usize) -> Option<i64> {
226 let value = i64::from_le_bytes(data.get(*offset..*offset + 8)?.try_into().ok()?);
227 *offset += 8;
228 Some(value)
229 }
230 fn read_u32(data: &[u8], offset: &mut usize) -> Option<u32> {
231 let value = u32::from_le_bytes(data.get(*offset..*offset + 4)?.try_into().ok()?);
232 *offset += 4;
233 Some(value)
234 }
235 fn read_u64(data: &[u8], offset: &mut usize) -> Option<u64> {
236 let value = read_u64_le(data, *offset)?;
237 *offset += 8;
238 Some(value)
239 }
240 fn read_u128(data: &[u8], offset: &mut usize) -> Option<u128> {
241 let value = u128::from_le_bytes(data.get(*offset..*offset + 16)?.try_into().ok()?);
242 *offset += 16;
243 Some(value)
244 }
245 fn read_pk(data: &[u8], offset: &mut usize) -> Option<solana_sdk::pubkey::Pubkey> {
246 let value = read_pubkey(data, *offset)?;
247 *offset += 32;
248 Some(value)
249 }
250 fn read_fees(data: &[u8], offset: &mut usize) -> Option<PumpFeesFees> {
251 Some(PumpFeesFees {
252 lp_fee_bps: read_u64(data, offset)?,
253 protocol_fee_bps: read_u64(data, offset)?,
254 creator_fee_bps: read_u64(data, offset)?,
255 })
256 }
257 fn read_fee_tiers(data: &[u8], offset: &mut usize) -> Option<Vec<PumpFeesFeeTier>> {
258 let len = read_u32(data, offset)? as usize;
259 if len > MAX_FEE_TIERS {
260 return None;
261 }
262 let mut out = Vec::with_capacity(len);
263 for _ in 0..len {
264 out.push(PumpFeesFeeTier {
265 market_cap_lamports_threshold: read_u128(data, offset)?,
266 fees: read_fees(data, offset)?,
267 });
268 }
269 Some(out)
270 }
271 fn read_shareholders(data: &[u8], offset: &mut usize) -> Option<Vec<PumpFeesShareholder>> {
272 let len = read_u32(data, offset)? as usize;
273 if len > MAX_SHAREHOLDERS {
274 return None;
275 }
276 let mut out = Vec::with_capacity(len);
277 for _ in 0..len {
278 let address = read_pk(data, offset)?;
279 let share_bps = u16::from_le_bytes(data.get(*offset..*offset + 2)?.try_into().ok()?);
280 *offset += 2;
281 out.push(PumpFeesShareholder { address, share_bps });
282 }
283 Some(out)
284 }
285 fn read_status(data: &[u8], offset: &mut usize) -> Option<PumpFeesConfigStatus> {
286 let value = *data.get(*offset)?;
287 *offset += 1;
288 match value {
289 0 => Some(PumpFeesConfigStatus::Paused),
290 1 => Some(PumpFeesConfigStatus::Active),
291 _ => None,
292 }
293 }
294
295 if has_discriminator(&account.data, FEE_CONFIG_DISCRIMINATOR) {
296 let data = &account.data[8..];
297 let mut offset = 0usize;
298 let fee_config = PumpFunFeeConfig {
299 bump: *data.get(offset)?,
300 admin: {
301 offset += 1;
302 read_pk(data, &mut offset)?
303 },
304 flat_fees: read_fees(data, &mut offset)?,
305 fee_tiers: read_fee_tiers(data, &mut offset)?,
306 stable_fee_tiers: read_fee_tiers(data, &mut offset)?,
307 };
308 return Some(DexEvent::PumpFunFeeConfigAccount(PumpFunFeeConfigAccountEvent {
309 metadata,
310 pubkey: account.pubkey,
311 fee_config,
312 }));
313 }
314
315 if has_discriminator(&account.data, SHARING_CONFIG_DISCRIMINATOR) {
316 let data = &account.data[8..];
317 let mut offset = 0usize;
318 let bump = *data.get(offset)?;
319 offset += 1;
320 let version = *data.get(offset)?;
321 offset += 1;
322 let status = read_status(data, &mut offset)?;
323 let mint = read_pk(data, &mut offset)?;
324 let admin = read_pk(data, &mut offset)?;
325 let admin_revoked = *data.get(offset)? != 0;
326 offset += 1;
327 let shareholders = read_shareholders(data, &mut offset)?;
328 return Some(DexEvent::PumpFunSharingConfigAccount(PumpFunSharingConfigAccountEvent {
329 metadata,
330 pubkey: account.pubkey,
331 sharing_config: PumpFunSharingConfig {
332 bump,
333 version,
334 status,
335 mint,
336 admin,
337 admin_revoked,
338 shareholders,
339 },
340 }));
341 }
342
343 if has_discriminator(&account.data, GLOBAL_VOLUME_ACCUMULATOR_DISCRIMINATOR) {
344 let data = &account.data[8..];
345 let mut offset = 0usize;
346 let start_time = read_i64(data, &mut offset)?;
347 let end_time = read_i64(data, &mut offset)?;
348 let seconds_in_a_day = read_i64(data, &mut offset)?;
349 let mint = read_pk(data, &mut offset)?;
350 let mut total_token_supply = [0u64; 30];
351 for value in &mut total_token_supply {
352 *value = read_u64(data, &mut offset)?;
353 }
354 let mut sol_volumes = [0u64; 30];
355 for value in &mut sol_volumes {
356 *value = read_u64(data, &mut offset)?;
357 }
358 return Some(DexEvent::PumpFunGlobalVolumeAccumulatorAccount(
359 PumpFunGlobalVolumeAccumulatorAccountEvent {
360 metadata,
361 pubkey: account.pubkey,
362 global_volume_accumulator: PumpFunGlobalVolumeAccumulator {
363 start_time,
364 end_time,
365 seconds_in_a_day,
366 mint,
367 total_token_supply,
368 sol_volumes,
369 },
370 },
371 ));
372 }
373
374 if has_discriminator(&account.data, USER_VOLUME_ACCUMULATOR_DISCRIMINATOR) {
375 let data = &account.data[8..];
376 let mut offset = 0usize;
377 let user = read_pk(data, &mut offset)?;
378 let needs_claim = *data.get(offset)? != 0;
379 offset += 1;
380 let total_unclaimed_tokens = read_u64(data, &mut offset)?;
381 let total_claimed_tokens = read_u64(data, &mut offset)?;
382 let current_sol_volume = read_u64(data, &mut offset)?;
383 let last_update_timestamp = read_i64(data, &mut offset)?;
384 let has_total_claimed_tokens = *data.get(offset)? != 0;
385 offset += 1;
386 let cashback_earned = read_u64(data, &mut offset)?;
387 let total_cashback_claimed = read_u64(data, &mut offset)?;
388 let stable_cashback_earned = read_u64(data, &mut offset)?;
389 let total_stable_cashback_claimed = read_u64(data, &mut offset)?;
390 return Some(DexEvent::PumpFunUserVolumeAccumulatorAccount(
391 PumpFunUserVolumeAccumulatorAccountEvent {
392 metadata,
393 pubkey: account.pubkey,
394 user_volume_accumulator: PumpFunUserVolumeAccumulator {
395 user,
396 needs_claim,
397 total_unclaimed_tokens,
398 total_claimed_tokens,
399 current_sol_volume,
400 last_update_timestamp,
401 has_total_claimed_tokens,
402 cashback_earned,
403 total_cashback_claimed,
404 stable_cashback_earned,
405 total_stable_cashback_claimed,
406 },
407 },
408 ));
409 }
410 if has_discriminator(&account.data, BONDING_CURVE_DISCRIMINATOR) {
411 let data = &account.data[8..];
412 let mut offset = 0usize;
413 let virtual_token_reserves = read_u64_le(data, offset)?;
414 offset += 8;
415 let virtual_quote_reserves = read_u64_le(data, offset)?;
416 offset += 8;
417 let real_token_reserves = read_u64_le(data, offset)?;
418 offset += 8;
419 let real_quote_reserves = read_u64_le(data, offset)?;
420 offset += 8;
421 let token_total_supply = read_u64_le(data, offset)?;
422 offset += 8;
423 let complete = read_u8(data, offset)? != 0;
424 offset += 1;
425 let creator = read_pubkey(data, offset)?;
426 offset += 32;
427 let is_mayhem_mode = read_u8(data, offset)? != 0;
428 offset += 1;
429 let is_cashback_coin = read_u8(data, offset)? != 0;
430 offset += 1;
431 let quote_mint = read_pubkey(data, offset)?;
432
433 return Some(DexEvent::PumpFunBondingCurveAccount(PumpFunBondingCurveAccountEvent {
434 metadata,
435 pubkey: account.pubkey,
436 bonding_curve: PumpFunBondingCurve {
437 virtual_token_reserves,
438 virtual_quote_reserves,
439 real_token_reserves,
440 real_quote_reserves,
441 token_total_supply,
442 complete,
443 creator,
444 is_mayhem_mode,
445 is_cashback_coin,
446 quote_mint,
447 },
448 }));
449 }
450 if !has_discriminator(&account.data, GLOBAL_DISCRIMINATOR) {
451 return None;
452 }
453
454 let data = &account.data[8..];
455 let mut offset = 0usize;
456 let initialized = read_u8(data, offset)? != 0;
457 offset += 1;
458 let authority = read_pubkey(data, offset)?;
459 offset += 32;
460 let fee_recipient = read_pubkey(data, offset)?;
461 offset += 32;
462 let initial_virtual_token_reserves = read_u64_le(data, offset)?;
463 offset += 8;
464 let initial_virtual_sol_reserves = read_u64_le(data, offset)?;
465 offset += 8;
466 let initial_real_token_reserves = read_u64_le(data, offset)?;
467 offset += 8;
468 let token_total_supply = read_u64_le(data, offset)?;
469 offset += 8;
470 let fee_basis_points = read_u64_le(data, offset)?;
471 offset += 8;
472 let withdraw_authority = read_pubkey(data, offset)?;
473 offset += 32;
474 let enable_migrate = read_u8(data, offset)? != 0;
475 offset += 1;
476 let pool_migration_fee = read_u64_le(data, offset)?;
477 offset += 8;
478 let creator_fee_basis_points = read_u64_le(data, offset)?;
479 offset += 8;
480 let mut fee_recipients = [solana_sdk::pubkey::Pubkey::default(); 7];
481 for fee_recipient in &mut fee_recipients {
482 *fee_recipient = read_pubkey(data, offset)?;
483 offset += 32;
484 }
485 let set_creator_authority = read_pubkey(data, offset)?;
486 offset += 32;
487 let admin_set_creator_authority = read_pubkey(data, offset)?;
488 offset += 32;
489 let create_v2_enabled = read_u8(data, offset)? != 0;
490 offset += 1;
491 let whitelist_pda = read_pubkey(data, offset)?;
492 offset += 32;
493 let reserved_fee_recipient = read_pubkey(data, offset)?;
494 offset += 32;
495 let mayhem_mode_enabled = read_u8(data, offset)? != 0;
496 offset += 1;
497 let mut reserved_fee_recipients = [solana_sdk::pubkey::Pubkey::default(); 7];
498 for reserved_fee_recipient in &mut reserved_fee_recipients {
499 *reserved_fee_recipient = read_pubkey(data, offset)?;
500 offset += 32;
501 }
502 let is_cashback_enabled = read_u8(data, offset)? != 0;
503 offset += 1;
504 let buyback_fee_recipients = {
505 let mut keys = [solana_sdk::pubkey::Pubkey::default(); 8];
506 for key in &mut keys {
507 *key = read_pubkey(data, offset)?;
508 offset += 32;
509 }
510 keys
511 };
512 let buyback_basis_points = read_u64_le(data, offset)?;
513 offset += 8;
514 let initial_virtual_quote_reserves = read_u64_le(data, offset)?;
515 offset += 8;
516 let whitelisted_quote_mints = {
517 let mut keys = [solana_sdk::pubkey::Pubkey::default(); 1];
518 keys[0] = read_pubkey(data, offset)?;
519 keys
520 };
521
522 let global = PumpFunGlobal {
523 initialized,
524 authority,
525 fee_recipient,
526 initial_virtual_token_reserves,
527 initial_virtual_sol_reserves,
528 initial_real_token_reserves,
529 token_total_supply,
530 fee_basis_points,
531 withdraw_authority,
532 enable_migrate,
533 pool_migration_fee,
534 creator_fee_basis_points,
535 fee_recipients,
536 set_creator_authority,
537 admin_set_creator_authority,
538 create_v2_enabled,
539 whitelist_pda,
540 reserved_fee_recipient,
541 mayhem_mode_enabled,
542 reserved_fee_recipients,
543 is_cashback_enabled,
544 buyback_fee_recipients,
545 buyback_basis_points,
546 initial_virtual_quote_reserves,
547 whitelisted_quote_mints,
548 };
549
550 Some(DexEvent::PumpFunGlobalAccount(PumpFunGlobalAccountEvent {
551 metadata,
552 pubkey: account.pubkey,
553 global,
554 }))
555}
556
557#[cfg(test)]
558mod tests {
559 use super::*;
560 use crate::grpc::{EventType, EventTypeFilter};
561 use solana_sdk::pubkey::Pubkey;
562 use solana_sdk::signature::Signature;
563
564 fn metadata() -> EventMetadata {
565 EventMetadata {
566 signature: Signature::default(),
567 slot: 1,
568 tx_index: 0,
569 block_time_us: 0,
570 grpc_recv_us: 0,
571 recent_blockhash: None,
572 }
573 }
574
575 fn push_pk(out: &mut Vec<u8>, seed: u8) -> Pubkey {
576 let key = Pubkey::new_from_array([seed; 32]);
577 out.extend_from_slice(key.as_ref());
578 key
579 }
580
581 #[test]
582 fn parse_pumpfun_bonding_curve_reads_quote_fields() {
583 let mut data = vec![23, 183, 248, 55, 96, 216, 172, 96];
584 data.extend_from_slice(&100u64.to_le_bytes());
585 data.extend_from_slice(&4_292_000_000u64.to_le_bytes());
586 data.extend_from_slice(&200u64.to_le_bytes());
587 data.extend_from_slice(&3_000_000_000u64.to_le_bytes());
588 data.extend_from_slice(&1_000u64.to_le_bytes());
589 data.push(1);
590 let creator = push_pk(&mut data, 7);
591 data.push(1);
592 data.push(0);
593 let quote_mint = push_pk(&mut data, 8);
594 let account = AccountData {
595 pubkey: Pubkey::new_unique(),
596 executable: false,
597 lamports: 0,
598 owner: crate::grpc::program_ids::PUMPFUN_PROGRAM,
599 rent_epoch: 0,
600 data,
601 };
602 let filter = EventTypeFilter::include_only(vec![EventType::AccountPumpFunBondingCurve]);
603
604 let ev = parse_account_unified(&account, metadata(), Some(&filter)).expect("event");
605
606 match ev {
607 DexEvent::PumpFunBondingCurveAccount(e) => {
608 assert_eq!(e.bonding_curve.virtual_quote_reserves, 4_292_000_000);
609 assert_eq!(e.bonding_curve.real_quote_reserves, 3_000_000_000);
610 assert_eq!(e.bonding_curve.creator, creator);
611 assert_eq!(e.bonding_curve.quote_mint, quote_mint);
612 assert!(e.bonding_curve.complete);
613 assert!(e.bonding_curve.is_mayhem_mode);
614 assert!(!e.bonding_curve.is_cashback_coin);
615 }
616 other => panic!("expected bonding curve account, got {other:?}"),
617 }
618 }
619
620 #[test]
621 fn parse_unified_routes_cpmm_and_orca_account_filters() {
622 let mut cpmm = Vec::with_capacity(8 + raydium_cpmm::AMM_CONFIG_SIZE);
623 cpmm.extend_from_slice(raydium_cpmm::discriminators::AMM_CONFIG);
624 cpmm.push(1);
625 cpmm.push(0);
626 cpmm.extend_from_slice(&42u16.to_le_bytes());
627 for value in [10u64, 20, 30, 40] {
628 cpmm.extend_from_slice(&value.to_le_bytes());
629 }
630 push_pk(&mut cpmm, 1);
631 push_pk(&mut cpmm, 2);
632 cpmm.extend_from_slice(&50u64.to_le_bytes());
633 for value in 0u64..15 {
634 cpmm.extend_from_slice(&value.to_le_bytes());
635 }
636 let cpmm_account = AccountData {
637 pubkey: Pubkey::new_unique(),
638 executable: false,
639 lamports: 0,
640 owner: crate::instr::program_ids::RAYDIUM_CPMM_PROGRAM_ID,
641 rent_epoch: 0,
642 data: cpmm,
643 };
644 let cpmm_filter =
645 EventTypeFilter::include_only(vec![EventType::AccountRaydiumCpmmAmmConfig]);
646 assert!(matches!(
647 parse_account_unified(&cpmm_account, metadata(), Some(&cpmm_filter)),
648 Some(DexEvent::RaydiumCpmmAmmConfigAccount(_))
649 ));
650 let cpmm_mismatch_filter =
651 EventTypeFilter::include_only(vec![EventType::AccountRaydiumCpmmPoolState]);
652 assert!(
653 parse_account_unified(&cpmm_account, metadata(), Some(&cpmm_mismatch_filter)).is_none()
654 );
655
656 let mut fee_tier = Vec::with_capacity(8 + orca_whirlpool::FEE_TIER_SIZE);
657 fee_tier.extend_from_slice(orca_whirlpool::discriminators::FEE_TIER);
658 push_pk(&mut fee_tier, 3);
659 fee_tier.extend_from_slice(&64u16.to_le_bytes());
660 fee_tier.extend_from_slice(&300u16.to_le_bytes());
661 let orca_account = AccountData {
662 pubkey: Pubkey::new_unique(),
663 executable: false,
664 lamports: 0,
665 owner: crate::instr::program_ids::ORCA_WHIRLPOOL_PROGRAM_ID,
666 rent_epoch: 0,
667 data: fee_tier,
668 };
669 let orca_filter = EventTypeFilter::include_only(vec![EventType::AccountOrcaFeeTier]);
670 assert!(matches!(
671 parse_account_unified(&orca_account, metadata(), Some(&orca_filter)),
672 Some(DexEvent::OrcaFeeTierAccount(_))
673 ));
674 let orca_exclude_filter =
675 EventTypeFilter::exclude_types(vec![EventType::AccountOrcaFeeTier]);
676 assert!(
677 parse_account_unified(&orca_account, metadata(), Some(&orca_exclude_filter)).is_none()
678 );
679 }
680
681 #[test]
682 fn parse_unified_filters_token_info_and_token_account_separately() {
683 let mut data = vec![0u8; 82];
684 data[36..44].copy_from_slice(&1_000_000u64.to_le_bytes());
685 data[44] = 6;
686 let account = AccountData {
687 pubkey: Pubkey::new_unique(),
688 executable: false,
689 lamports: 0,
690 owner: Pubkey::new_from_array(spl_token::ID.to_bytes()),
691 rent_epoch: 0,
692 data,
693 };
694
695 let token_info_filter = EventTypeFilter::include_only(vec![EventType::TokenInfo]);
696 assert!(matches!(
697 parse_account_unified(&account, metadata(), Some(&token_info_filter)),
698 Some(DexEvent::TokenInfo(_))
699 ));
700
701 let token_account_filter = EventTypeFilter::include_only(vec![EventType::TokenAccount]);
702 assert!(parse_account_unified(&account, metadata(), Some(&token_account_filter)).is_none());
703 }
704
705 #[test]
706 fn parse_unified_known_dex_owner_does_not_fall_through_to_token_parser() {
707 let mut data = vec![0u8; 82];
708 data[36..44].copy_from_slice(&1_000_000u64.to_le_bytes());
709 data[44] = 6;
710 let account = AccountData {
711 pubkey: Pubkey::new_unique(),
712 executable: false,
713 lamports: 0,
714 owner: crate::grpc::program_ids::PUMPFUN_PROGRAM,
715 rent_epoch: 0,
716 data,
717 };
718
719 assert!(parse_account_unified(&account, metadata(), None).is_none());
720 }
721}