Skip to main content

sol_parser_sdk/core/events/
trade_source.rs

1//! Uniform provenance access for supported trade/swap event families.
2use super::{DexEvent, RawInstructionPayload, TradeParseStatus};
3use solana_sdk::pubkey::Pubkey;
4
5#[cfg(test)]
6mod tests {
7    use super::*;
8    use crate::core::common_filler::fill_trade_execution_source;
9    use crate::instr::program_ids::*;
10    use yellowstone_grpc_proto::prelude::{
11        CompiledInstruction, Message, Transaction, TransactionStatusMeta,
12    };
13
14    #[test]
15    fn every_trade_family_preserves_exact_outer_payload_and_direct_calls_stay_native() {
16        let wrapper = Pubkey::new_unique();
17        let loaded = Pubkey::new_unique();
18        for (mut event, native) in [
19            (DexEvent::PumpFunTrade(Default::default()), PUMPFUN_PROGRAM_ID),
20            (DexEvent::PumpFunBuy(Default::default()), PUMPFUN_PROGRAM_ID),
21            (DexEvent::PumpFunSell(Default::default()), PUMPFUN_PROGRAM_ID),
22            (DexEvent::PumpFunBuyExactSolIn(Default::default()), PUMPFUN_PROGRAM_ID),
23            (DexEvent::PumpSwapTrade(Default::default()), PUMPSWAP_PROGRAM_ID),
24            (DexEvent::PumpSwapBuy(Default::default()), PUMPSWAP_PROGRAM_ID),
25            (DexEvent::PumpSwapSell(Default::default()), PUMPSWAP_PROGRAM_ID),
26            (DexEvent::RaydiumLaunchlabTrade(Default::default()), RAYDIUM_LAUNCHLAB_PROGRAM_ID),
27            (DexEvent::RaydiumCpmmSwap(Default::default()), RAYDIUM_CPMM_PROGRAM_ID),
28            (DexEvent::RaydiumClmmSwap(Default::default()), RAYDIUM_CLMM_PROGRAM_ID),
29            (DexEvent::RaydiumAmmV4Swap(Default::default()), RAYDIUM_AMM_V4_PROGRAM_ID),
30            (DexEvent::OrcaWhirlpoolSwap(Default::default()), ORCA_WHIRLPOOL_PROGRAM_ID),
31            (DexEvent::MeteoraPoolsSwap(Default::default()), METEORA_POOLS_PROGRAM_ID),
32            (DexEvent::MeteoraDammV2Swap(Default::default()), METEORA_DAMM_V2_PROGRAM_ID),
33            (DexEvent::MeteoraDlmmSwap(Default::default()), METEORA_DLMM_PROGRAM_ID),
34            (DexEvent::MeteoraDbcSwap(Default::default()), METEORA_DBC_PROGRAM_ID),
35        ] {
36            let transaction = Some(Transaction {
37                message: Some(Message {
38                    account_keys: vec![wrapper.to_bytes().to_vec(), native.to_bytes().to_vec()],
39                    instructions: vec![
40                        CompiledInstruction {
41                            program_id_index: 0,
42                            accounts: vec![1, 2, 2, 250],
43                            data: vec![7, 9],
44                        },
45                        CompiledInstruction {
46                            program_id_index: 1,
47                            accounts: vec![],
48                            data: vec![8],
49                        },
50                    ],
51                    ..Default::default()
52                }),
53                ..Default::default()
54            });
55            let meta = TransactionStatusMeta {
56                loaded_writable_addresses: vec![loaded.to_bytes().to_vec()],
57                ..Default::default()
58            };
59            let mut direct = event.clone();
60            fill_trade_execution_source(&mut direct, 1, &meta, &transaction);
61            assert!(direct.third_program().is_none());
62            assert_eq!(direct.trade_parse_status(), Some(TradeParseStatus::ExecutionConfirmed));
63            fill_trade_execution_source(&mut event, 0, &meta, &transaction);
64            let raw = event.third_program().unwrap();
65            assert_eq!(raw.program_id, wrapper);
66            assert_eq!(raw.instruction_index, 0);
67            assert_eq!(raw.account_indices, vec![1, 2, 2, 250]);
68            assert_eq!(raw.accounts, vec![Some(native), Some(loaded), Some(loaded), None]);
69            assert_eq!(raw.instruction_data, vec![7, 9]);
70            let json = serde_json::to_value(&event).unwrap();
71            let restored: DexEvent = serde_json::from_value(json).unwrap();
72            assert_eq!(restored.third_program(), Some(raw));
73            assert_eq!(restored.trade_parse_status(), Some(TradeParseStatus::ExecutionConfirmed));
74        }
75    }
76}
77
78impl DexEvent {
79    /// Original outer router instruction, when exact invocation provenance exists.
80    pub fn third_program(&self) -> Option<&RawInstructionPayload> {
81        match self {
82            Self::PumpFunTrade(t)
83            | Self::PumpFunBuy(t)
84            | Self::PumpFunSell(t)
85            | Self::PumpFunBuyExactSolIn(t) => t.third_program.as_ref(),
86            Self::PumpSwapTrade(t) => t.third_program.as_ref(),
87            Self::PumpSwapBuy(t) => t.third_program.as_ref(),
88            Self::PumpSwapSell(t) => t.third_program.as_ref(),
89            Self::RaydiumLaunchlabTrade(t) => t.third_program.as_ref(),
90            Self::RaydiumCpmmSwap(t) => t.third_program.as_ref(),
91            Self::RaydiumClmmSwap(t) => t.third_program.as_ref(),
92            Self::RaydiumAmmV4Swap(t) => t.third_program.as_ref(),
93            Self::OrcaWhirlpoolSwap(t) => t.third_program.as_ref(),
94            Self::MeteoraPoolsSwap(t) => t.third_program.as_ref(),
95            Self::MeteoraDammV2Swap(t) => t.third_program.as_ref(),
96            Self::MeteoraDlmmSwap(t) => t.third_program.as_ref(),
97            Self::MeteoraDbcSwap(t) => t.third_program.as_ref(),
98            _ => None,
99        }
100    }
101
102    /// Evidence level for a trade; non-trade events return None.
103    pub fn trade_parse_status(&self) -> Option<TradeParseStatus> {
104        match self {
105            Self::PumpFunTrade(t)
106            | Self::PumpFunBuy(t)
107            | Self::PumpFunSell(t)
108            | Self::PumpFunBuyExactSolIn(t) => Some(t.parse_status),
109            Self::PumpSwapTrade(t) => Some(t.parse_status),
110            Self::PumpSwapBuy(t) => Some(t.parse_status),
111            Self::PumpSwapSell(t) => Some(t.parse_status),
112            Self::RaydiumLaunchlabTrade(t) => Some(t.parse_status),
113            Self::RaydiumCpmmSwap(t) => Some(t.parse_status),
114            Self::RaydiumClmmSwap(t) => Some(t.parse_status),
115            Self::RaydiumAmmV4Swap(t) => Some(t.parse_status),
116            Self::OrcaWhirlpoolSwap(t) => Some(t.parse_status),
117            Self::MeteoraPoolsSwap(t) => Some(t.parse_status),
118            Self::MeteoraDammV2Swap(t) => Some(t.parse_status),
119            Self::MeteoraDlmmSwap(t) => Some(t.parse_status),
120            Self::MeteoraDbcSwap(t) => Some(t.parse_status),
121            _ => None,
122        }
123    }
124
125    pub(crate) fn trade_source_mut(
126        &mut self,
127    ) -> Option<(&mut Option<RawInstructionPayload>, &mut TradeParseStatus, Pubkey)> {
128        use crate::instr::program_ids::*;
129        match self {
130            Self::PumpFunTrade(t)
131            | Self::PumpFunBuy(t)
132            | Self::PumpFunSell(t)
133            | Self::PumpFunBuyExactSolIn(t) => {
134                Some((&mut t.third_program, &mut t.parse_status, PUMPFUN_PROGRAM_ID))
135            }
136            Self::PumpSwapTrade(t) => {
137                Some((&mut t.third_program, &mut t.parse_status, PUMPSWAP_PROGRAM_ID))
138            }
139            Self::PumpSwapBuy(t) => {
140                Some((&mut t.third_program, &mut t.parse_status, PUMPSWAP_PROGRAM_ID))
141            }
142            Self::PumpSwapSell(t) => {
143                Some((&mut t.third_program, &mut t.parse_status, PUMPSWAP_PROGRAM_ID))
144            }
145            Self::RaydiumLaunchlabTrade(t) => {
146                Some((&mut t.third_program, &mut t.parse_status, RAYDIUM_LAUNCHLAB_PROGRAM_ID))
147            }
148            Self::RaydiumCpmmSwap(t) => {
149                Some((&mut t.third_program, &mut t.parse_status, RAYDIUM_CPMM_PROGRAM_ID))
150            }
151            Self::RaydiumClmmSwap(t) => {
152                Some((&mut t.third_program, &mut t.parse_status, RAYDIUM_CLMM_PROGRAM_ID))
153            }
154            Self::RaydiumAmmV4Swap(t) => {
155                Some((&mut t.third_program, &mut t.parse_status, RAYDIUM_AMM_V4_PROGRAM_ID))
156            }
157            Self::OrcaWhirlpoolSwap(t) => {
158                Some((&mut t.third_program, &mut t.parse_status, ORCA_WHIRLPOOL_PROGRAM_ID))
159            }
160            Self::MeteoraPoolsSwap(t) => {
161                Some((&mut t.third_program, &mut t.parse_status, METEORA_POOLS_PROGRAM_ID))
162            }
163            Self::MeteoraDammV2Swap(t) => {
164                Some((&mut t.third_program, &mut t.parse_status, METEORA_DAMM_V2_PROGRAM_ID))
165            }
166            Self::MeteoraDlmmSwap(t) => {
167                Some((&mut t.third_program, &mut t.parse_status, METEORA_DLMM_PROGRAM_ID))
168            }
169            Self::MeteoraDbcSwap(t) => {
170                Some((&mut t.third_program, &mut t.parse_status, METEORA_DBC_PROGRAM_ID))
171            }
172            _ => None,
173        }
174    }
175}