sol_shred_sdk/parser/
pumpfun.rs1use std::num::NonZeroUsize;
2
3use lru::LruCache;
4use solana_sdk::pubkey::Pubkey;
5use solana_sdk::signature::Signature;
6use solana_sdk::transaction::VersionedTransaction;
7
8use crate::common::logs_data::{
9 BonkCreateTokenInfo, CreateTokenInfo, DexInstruction, TradeInfo, TradeRequest,
10};
11use crate::common::logs_events::PumpfunEvent;
12use crate::common::logs_filters::LogFilter;
13use crate::common::AnyResult;
14use crate::parser::TransactionEventParser;
15
16#[derive(Debug, Clone)]
17pub struct PumpfunParserConfig {
18 pub bot_wallet: Option<Pubkey>,
19 pub dedup_capacity: NonZeroUsize,
20}
21
22impl Default for PumpfunParserConfig {
23 fn default() -> Self {
24 Self {
25 bot_wallet: None,
26 dedup_capacity: NonZeroUsize::new(100_000).expect("non-zero dedup capacity"),
27 }
28 }
29}
30
31impl PumpfunParserConfig {
32 pub fn with_bot_wallet(mut self, bot_wallet: Option<Pubkey>) -> Self {
33 self.bot_wallet = bot_wallet;
34 self
35 }
36}
37
38pub struct PumpfunEventParser {
40 config: PumpfunParserConfig,
41 processed: LruCache<Signature, ()>,
42}
43
44impl PumpfunEventParser {
45 pub fn new(config: PumpfunParserConfig) -> Self {
46 Self {
47 processed: LruCache::new(config.dedup_capacity),
48 config,
49 }
50 }
51
52 #[inline]
53 pub fn clear_dedup(&mut self) {
54 self.processed.clear();
55 }
56
57 pub fn process_transaction<F>(
58 &mut self,
59 transaction: &VersionedTransaction,
60 slot: u64,
61 mut callback: F,
62 ) -> AnyResult<usize>
63 where
64 F: FnMut(PumpfunEvent),
65 {
66 let Some(signature) = transaction.signatures.first().cloned() else {
67 return Ok(0);
68 };
69
70 if self.processed.put(signature, ()).is_some() {
71 return Ok(0);
72 }
73
74 let instructions =
75 LogFilter::parse_compiled_instruction(transaction, self.config.bot_wallet)?;
76 let mut emitted = 0usize;
77
78 let mut token_info: Option<CreateTokenInfo> = None;
79 let mut dev_trade_info: Option<TradeInfo> = None;
80 let mut bonk_token_info: Option<BonkCreateTokenInfo> = None;
81 let mut bonk_trade_info: Option<TradeRequest> = None;
82
83 for instruction in instructions {
84 match instruction {
85 DexInstruction::CreateToken(mut token) => {
86 let (limit, price, fee_merchant, fee) = LogFilter::parse_tip_info(transaction);
87 token.slot = slot;
88 token.unit_limit = limit.unwrap_or(0);
89 token.unit_price = price.unwrap_or(0);
90 token.fee_merchant = fee_merchant.unwrap_or_default();
91 token.fee = fee.unwrap_or(0);
92 token_info = Some(token);
93 }
94 DexInstruction::BonkCreateToken(mut token) => {
95 let (limit, price, fee_merchant, fee) = LogFilter::parse_tip_info(transaction);
96 token.unit_limit = limit.unwrap_or(0);
97 token.unit_price = price.unwrap_or(0);
98 token.fee_merchant = fee_merchant.unwrap_or_default();
99 token.fee = fee.unwrap_or(0);
100 bonk_token_info = Some(token);
101 }
102 DexInstruction::BonkTrade(trade_request) => {
103 bonk_trade_info = Some(trade_request);
104 }
105 DexInstruction::UserTrade(mut trade) => {
106 trade.slot = slot;
107 if token_info.is_some() {
108 merge_trade(&mut dev_trade_info, trade);
109 } else {
110 callback(PumpfunEvent::NewUserTrade(trade));
111 emitted += 1;
112 }
113 }
114 DexInstruction::BotTrade(mut trade) => {
115 trade.slot = slot;
116 callback(PumpfunEvent::NewBotTrade(trade));
117 emitted += 1;
118 }
119 _ => {}
120 }
121 }
122
123 match (token_info, dev_trade_info, bonk_token_info, bonk_trade_info) {
124 (Some(token), Some(trade), None, None) => {
125 callback(PumpfunEvent::NewToken2 { token, trade });
126 emitted += 1;
127 }
128 (Some(token), None, None, None) => {
129 callback(PumpfunEvent::NewToken(token));
130 emitted += 1;
131 }
132 (None, None, Some(token), Some(trade)) => {
133 callback(PumpfunEvent::NewBonkToken { token, trade });
134 emitted += 1;
135 }
136 _ => {}
137 }
138
139 Ok(emitted)
140 }
141}
142
143impl Default for PumpfunEventParser {
144 fn default() -> Self {
145 Self::new(PumpfunParserConfig::default())
146 }
147}
148
149impl TransactionEventParser for PumpfunEventParser {
150 type Event = PumpfunEvent;
151
152 #[inline]
153 fn parse_transaction_events<F>(
154 &mut self,
155 transaction: &VersionedTransaction,
156 slot: u64,
157 _tx_index: u64,
158 _recv_us: i64,
159 emit: F,
160 ) -> AnyResult<usize>
161 where
162 F: FnMut(Self::Event),
163 {
164 self.process_transaction(transaction, slot, emit)
165 }
166}
167
168fn merge_trade(target: &mut Option<TradeInfo>, trade: TradeInfo) {
169 if let Some(existing) = target {
170 existing.sol_amount = existing.sol_amount.saturating_add(trade.sol_amount);
171 existing.token_amount = existing.token_amount.saturating_add(trade.token_amount);
172 } else {
173 *target = Some(trade);
174 }
175}