Skip to main content

sol_parser_sdk/core/events/
raw_instruction.rs

1//! Lossless outer instruction payload, without assumptions about program behavior.
2use super::EventMetadata;
3use serde::{Deserialize, Serialize};
4use solana_sdk::pubkey::Pubkey;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct RawInstructionEvent {
8    pub metadata: EventMetadata,
9    /// DEX associations from this instruction only, not the transaction-wide key table.
10    #[serde(default)]
11    pub dexes: Vec<RawInstructionDex>,
12    pub program_id: Pubkey,
13    pub instruction_index: usize,
14    /// Instruction account order, including duplicates. None means an unresolved
15    /// ALT address, never the System Program or an invented placeholder.
16    pub accounts: Vec<Option<Pubkey>>,
17    /// Indices into the transaction's static + loaded account key table.
18    pub account_indices: Vec<u8>,
19    /// Unmodified bytes, including any program-defined discriminator.
20    pub instruction_data: Vec<u8>,
21}
22
23/// Stable DEX identifiers shared by every language binding.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
25pub enum RawInstructionDexType {
26    #[serde(rename = "pumpfun")]
27    PumpFun,
28    #[serde(rename = "pumpswap")]
29    PumpSwap,
30    #[serde(rename = "raydium_launchlab")]
31    RaydiumLaunchlab,
32    #[serde(rename = "raydium_cpmm")]
33    RaydiumCpmm,
34    #[serde(rename = "raydium_clmm")]
35    RaydiumClmm,
36    #[serde(rename = "raydium_amm_v4")]
37    RaydiumAmmV4,
38    #[serde(rename = "orca_whirlpool")]
39    OrcaWhirlpool,
40    #[serde(rename = "meteora_pools")]
41    MeteoraPools,
42    #[serde(rename = "meteora_damm_v2")]
43    MeteoraDammV2,
44    #[serde(rename = "meteora_dlmm")]
45    MeteoraDlmm,
46    #[serde(rename = "meteora_dbc")]
47    MeteoraDbc,
48}
49
50impl RawInstructionDexType {
51    pub const fn as_str(self) -> &'static str {
52        match self {
53            Self::PumpFun => "pumpfun",
54            Self::PumpSwap => "pumpswap",
55            Self::RaydiumLaunchlab => "raydium_launchlab",
56            Self::RaydiumCpmm => "raydium_cpmm",
57            Self::RaydiumClmm => "raydium_clmm",
58            Self::RaydiumAmmV4 => "raydium_amm_v4",
59            Self::OrcaWhirlpool => "orca_whirlpool",
60            Self::MeteoraPools => "meteora_pools",
61            Self::MeteoraDammV2 => "meteora_damm_v2",
62            Self::MeteoraDlmm => "meteora_dlmm",
63            Self::MeteoraDbc => "meteora_dbc",
64        }
65    }
66}
67
68/// A DEX program directly targeted or referenced by this outer instruction.
69/// A reference is not proof of an executed CPI. Unknown wrapper layouts cannot
70/// establish a pool address; pool remains None rather than guessing an index.
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
72pub struct RawInstructionDex {
73    pub dex_type: RawInstructionDexType,
74    pub program_id: Pubkey,
75    pub pool: Option<Pubkey>,
76}
77
78pub fn raw_instruction_dexes(
79    program_id: Pubkey,
80    accounts: &[Option<Pubkey>],
81) -> Vec<RawInstructionDex> {
82    use crate::grpc::program_ids::*;
83    let known = [
84        (RawInstructionDexType::PumpFun, PUMPFUN_PROGRAM),
85        (RawInstructionDexType::PumpSwap, PUMPSWAP_PROGRAM),
86        (RawInstructionDexType::RaydiumLaunchlab, RAYDIUM_LAUNCHLAB_PROGRAM),
87        (RawInstructionDexType::RaydiumCpmm, RAYDIUM_CPMM_PROGRAM),
88        (RawInstructionDexType::RaydiumClmm, RAYDIUM_CLMM_PROGRAM),
89        (RawInstructionDexType::RaydiumAmmV4, RAYDIUM_AMM_V4_PROGRAM),
90        (RawInstructionDexType::OrcaWhirlpool, ORCA_WHIRLPOOL_PROGRAM),
91        (RawInstructionDexType::MeteoraPools, METEORA_POOLS_PROGRAM),
92        (RawInstructionDexType::MeteoraDammV2, METEORA_DAMM_V2_PROGRAM),
93        (RawInstructionDexType::MeteoraDlmm, METEORA_DLMM_PROGRAM),
94        (RawInstructionDexType::MeteoraDbc, METEORA_DBC_PROGRAM),
95    ];
96    known
97        .into_iter()
98        .filter(|(_, key)| program_id == *key || accounts.contains(&Some(*key)))
99        .map(|(name, key)| RawInstructionDex { dex_type: name, program_id: key, pool: None })
100        .collect()
101}
102
103pub fn raw_instruction_matches_filter(
104    dexes: &[RawInstructionDex],
105    filter: &crate::grpc::EventTypeFilter,
106) -> bool {
107    use crate::grpc::EventType;
108    if filter.exclude_types.as_ref().is_some_and(|types| types.contains(&EventType::RawInstruction))
109    {
110        return false;
111    }
112    if filter.include_only.as_ref().is_some_and(|types| types.contains(&EventType::RawInstruction))
113    {
114        return true;
115    }
116    if dexes.is_empty() {
117        return filter.include_only.is_none();
118    }
119    dexes.iter().any(|dex| {
120        let event = match dex.dex_type {
121            RawInstructionDexType::PumpFun => EventType::PumpFunTrade,
122            RawInstructionDexType::PumpSwap => EventType::PumpSwapTrade,
123            RawInstructionDexType::RaydiumLaunchlab => EventType::RaydiumLaunchlabTrade,
124            RawInstructionDexType::RaydiumCpmm => EventType::RaydiumCpmmSwap,
125            RawInstructionDexType::RaydiumClmm => EventType::RaydiumClmmSwap,
126            RawInstructionDexType::RaydiumAmmV4 => EventType::RaydiumAmmV4Swap,
127            RawInstructionDexType::OrcaWhirlpool => EventType::OrcaWhirlpoolSwap,
128            RawInstructionDexType::MeteoraPools => EventType::MeteoraPoolsSwap,
129            RawInstructionDexType::MeteoraDammV2 => EventType::MeteoraDammV2Swap,
130            RawInstructionDexType::MeteoraDlmm => EventType::MeteoraDlmmSwap,
131            RawInstructionDexType::MeteoraDbc => EventType::MeteoraDbcSwap,
132        };
133        filter.should_include(event)
134    })
135}
136
137#[cfg(test)]
138mod enum_tests {
139    use super::*;
140    #[test]
141    fn dex_identifiers_round_trip_without_changing_json() {
142        for (kind, value) in [
143            (RawInstructionDexType::PumpFun, "pumpfun"),
144            (RawInstructionDexType::PumpSwap, "pumpswap"),
145            (RawInstructionDexType::RaydiumLaunchlab, "raydium_launchlab"),
146            (RawInstructionDexType::RaydiumCpmm, "raydium_cpmm"),
147            (RawInstructionDexType::RaydiumClmm, "raydium_clmm"),
148            (RawInstructionDexType::RaydiumAmmV4, "raydium_amm_v4"),
149            (RawInstructionDexType::OrcaWhirlpool, "orca_whirlpool"),
150            (RawInstructionDexType::MeteoraPools, "meteora_pools"),
151            (RawInstructionDexType::MeteoraDammV2, "meteora_damm_v2"),
152            (RawInstructionDexType::MeteoraDlmm, "meteora_dlmm"),
153            (RawInstructionDexType::MeteoraDbc, "meteora_dbc"),
154        ] {
155            assert_eq!(serde_json::to_value(kind).unwrap(), value);
156            assert_eq!(
157                serde_json::from_value::<RawInstructionDexType>(serde_json::json!(value)).unwrap(),
158                kind
159            );
160        }
161        assert!(serde_json::from_str::<RawInstructionDexType>("\"typo\"").is_err());
162    }
163}