Skip to main content

sol_shred_sdk/parser/
pumpfun.rs

1use 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
38/// PumpFun / Raydium LaunchLab parser for decoded Solana transactions.
39pub 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}