diff --git a/kb_lib/src/dex/meteora_dbc.rs b/kb_lib/src/dex/meteora_dbc.rs index db41fc6..bff833a 100644 --- a/kb_lib/src/dex/meteora_dbc.rs +++ b/kb_lib/src/dex/meteora_dbc.rs @@ -62,6 +62,13 @@ pub enum KbMeteoraDbcDecodedEvent { Swap(KbMeteoraDbcSwapDecoded), } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum KbMeteoraDbcInstructionKind { + CreatePool, + Swap, + Unknown, +} + /// Meteora DBC decoder. #[derive(Debug, Clone, Default)] pub struct KbMeteoraDbcDecoder; @@ -129,25 +136,8 @@ impl KbMeteoraDbcDecoder { Ok(parsed_json) => parsed_json, Err(error) => return Err(error), }; - let is_create_pool = kb_log_messages_contain_any_keyword( - &log_messages, - &[ - "create_pool", - "createpool", - "initialize_pool", - "initializepool", - "launch_pool", - ], - ) || kb_value_contains_any_key( - parsed_json.as_ref(), - &[ - "poolConfig", - "migrationQuoteThreshold", - "curveConfig", - "dbcConfig", - ], - ); - let is_swap = kb_log_messages_contain_any_keyword(&log_messages, &["swap2", "swap"]); + let instruction_kind = + kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages); let pool_account = kb_extract_string_by_candidate_keys( parsed_json.as_ref(), &["pool", "poolAccount", "poolState", "virtualPool", "poolKey"], @@ -173,10 +163,11 @@ impl KbMeteoraDbcDecoder { &["creator", "poolCreator", "owner", "user"], ) .or_else(|| kb_extract_account(&accounts, 4)); - if is_create_pool { + if instruction_kind == KbMeteoraDbcInstructionKind::CreatePool { let payload_json = serde_json::json!({ "decoder": "meteora_dbc", "eventKind": "create_pool", + "classifiedInstructionKind": "create_pool", "signature": transaction.signature, "instructionId": instruction_id, "instructionIndex": instruction.instruction_index, @@ -205,11 +196,12 @@ impl KbMeteoraDbcDecoder { )); continue; } - if is_swap { + if instruction_kind == KbMeteoraDbcInstructionKind::Swap { let trade_side = kb_infer_trade_side(&log_messages); let payload_json = serde_json::json!({ "decoder": "meteora_dbc", "eventKind": "swap", + "classifiedInstructionKind": "swap", "signature": transaction.signature, "instructionId": instruction_id, "instructionIndex": instruction.instruction_index, @@ -442,6 +434,56 @@ fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTra crate::KbSwapTradeSide::Unknown } +fn kb_classify_instruction_kind( + parsed_json: std::option::Option<&serde_json::Value>, + log_messages: &[std::string::String], +) -> KbMeteoraDbcInstructionKind { + let parsed_instruction_name = kb_extract_string_by_candidate_keys( + parsed_json, + &["instruction", "instructionName", "type", "name"], + ); + if let Some(parsed_instruction_name) = parsed_instruction_name { + let normalized = kb_normalize_log_text(parsed_instruction_name.as_str()); + if normalized.contains("createpool") + || normalized.contains("initializepool") + || normalized.contains("launchpool") + { + return KbMeteoraDbcInstructionKind::CreatePool; + } + if normalized == "swap" || normalized == "swap2" { + return KbMeteoraDbcInstructionKind::Swap; + } + } + let has_create_config = kb_value_contains_any_key( + parsed_json, + &[ + "poolConfig", + "migrationQuoteThreshold", + "curveConfig", + "dbcConfig", + ], + ); + if has_create_config { + return KbMeteoraDbcInstructionKind::CreatePool; + } + if kb_log_messages_contain_any_keyword( + log_messages, + &[ + "create_pool", + "createpool", + "initialize_pool", + "initializepool", + "launch_pool", + ], + ) { + return KbMeteoraDbcInstructionKind::CreatePool; + } + if kb_log_messages_contain_any_keyword(log_messages, &["swap2", "swap"]) { + return KbMeteoraDbcInstructionKind::Swap; + } + KbMeteoraDbcInstructionKind::Unknown +} + #[cfg(test)] mod tests { fn make_create_transaction() -> crate::KbChainTransactionDto { @@ -624,4 +666,50 @@ mod tests { } } } + + #[test] + fn meteora_dbc_quote_mint_alone_does_not_trigger_create_pool() { + let decoder = crate::KbMeteoraDbcDecoder::new(); + let transaction = make_swap_transaction(); + let mut instruction = make_swap_instruction(); + instruction.parsed_json = Some( + serde_json::json!({ + "info": { + "quoteMint": "So11111111111111111111111111111111111111112" + } + }) + .to_string(), + ); + let decoded_result = decoder.decode_transaction(&transaction, &[instruction]); + let decoded = match decoded_result { + Ok(decoded) => decoded, + Err(error) => panic!("decode must succeed: {}", error), + }; + assert_eq!(decoded.len(), 1); + match &decoded[0] { + crate::KbMeteoraDbcDecodedEvent::Swap(_) => {} + crate::KbMeteoraDbcDecodedEvent::CreatePool(_) => { + panic!("unexpected create event") + } + } + } + + #[test] + fn meteora_dbc_pool_config_triggers_create_pool() { + let decoder = crate::KbMeteoraDbcDecoder::new(); + let transaction = make_create_transaction(); + let instruction = make_create_instruction(); + let decoded_result = decoder.decode_transaction(&transaction, &[instruction]); + let decoded = match decoded_result { + Ok(decoded) => decoded, + Err(error) => panic!("decode must succeed: {}", error), + }; + assert_eq!(decoded.len(), 1); + match &decoded[0] { + crate::KbMeteoraDbcDecodedEvent::CreatePool(_) => {} + crate::KbMeteoraDbcDecodedEvent::Swap(_) => { + panic!("unexpected swap event") + } + } + } }