pool/swap better detection

This commit is contained in:
2026-04-28 20:24:50 +02:00
parent 574f90ddee
commit b6aa14b62d

View File

@@ -62,6 +62,13 @@ pub enum KbMeteoraDbcDecodedEvent {
Swap(KbMeteoraDbcSwapDecoded), Swap(KbMeteoraDbcSwapDecoded),
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum KbMeteoraDbcInstructionKind {
CreatePool,
Swap,
Unknown,
}
/// Meteora DBC decoder. /// Meteora DBC decoder.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct KbMeteoraDbcDecoder; pub struct KbMeteoraDbcDecoder;
@@ -129,25 +136,8 @@ impl KbMeteoraDbcDecoder {
Ok(parsed_json) => parsed_json, Ok(parsed_json) => parsed_json,
Err(error) => return Err(error), Err(error) => return Err(error),
}; };
let is_create_pool = kb_log_messages_contain_any_keyword( let instruction_kind =
&log_messages, kb_classify_instruction_kind(parsed_json.as_ref(), &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 pool_account = kb_extract_string_by_candidate_keys( let pool_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(), parsed_json.as_ref(),
&["pool", "poolAccount", "poolState", "virtualPool", "poolKey"], &["pool", "poolAccount", "poolState", "virtualPool", "poolKey"],
@@ -173,10 +163,11 @@ impl KbMeteoraDbcDecoder {
&["creator", "poolCreator", "owner", "user"], &["creator", "poolCreator", "owner", "user"],
) )
.or_else(|| kb_extract_account(&accounts, 4)); .or_else(|| kb_extract_account(&accounts, 4));
if is_create_pool { if instruction_kind == KbMeteoraDbcInstructionKind::CreatePool {
let payload_json = serde_json::json!({ let payload_json = serde_json::json!({
"decoder": "meteora_dbc", "decoder": "meteora_dbc",
"eventKind": "create_pool", "eventKind": "create_pool",
"classifiedInstructionKind": "create_pool",
"signature": transaction.signature, "signature": transaction.signature,
"instructionId": instruction_id, "instructionId": instruction_id,
"instructionIndex": instruction.instruction_index, "instructionIndex": instruction.instruction_index,
@@ -205,11 +196,12 @@ impl KbMeteoraDbcDecoder {
)); ));
continue; continue;
} }
if is_swap { if instruction_kind == KbMeteoraDbcInstructionKind::Swap {
let trade_side = kb_infer_trade_side(&log_messages); let trade_side = kb_infer_trade_side(&log_messages);
let payload_json = serde_json::json!({ let payload_json = serde_json::json!({
"decoder": "meteora_dbc", "decoder": "meteora_dbc",
"eventKind": "swap", "eventKind": "swap",
"classifiedInstructionKind": "swap",
"signature": transaction.signature, "signature": transaction.signature,
"instructionId": instruction_id, "instructionId": instruction_id,
"instructionIndex": instruction.instruction_index, "instructionIndex": instruction.instruction_index,
@@ -442,6 +434,56 @@ fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTra
crate::KbSwapTradeSide::Unknown 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)] #[cfg(test)]
mod tests { mod tests {
fn make_create_transaction() -> crate::KbChainTransactionDto { 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")
}
}
}
} }