0.7.28 - final

This commit is contained in:
2026-05-12 15:04:04 +02:00
parent 7f130dba6b
commit 4f6a4806e2
34 changed files with 4020 additions and 199 deletions

View File

@@ -12,6 +12,7 @@ pub struct DexDecodeService {
pump_swap_decoder: crate::PumpSwapDecoder,
orca_whirlpools_decoder: crate::OrcaWhirlpoolsDecoder,
meteora_dbc_decoder: crate::MeteoraDbcDecoder,
meteora_dlmm_decoder: crate::MeteoraDlmmDecoder,
meteora_damm_v1_decoder: crate::MeteoraDammV1Decoder,
meteora_damm_v2_decoder: crate::MeteoraDammV2Decoder,
fluxbeam_decoder: crate::FluxbeamDecoder,
@@ -30,6 +31,7 @@ impl DexDecodeService {
pump_swap_decoder: crate::PumpSwapDecoder::new(),
orca_whirlpools_decoder: crate::OrcaWhirlpoolsDecoder::new(),
meteora_dbc_decoder: crate::MeteoraDbcDecoder::new(),
meteora_dlmm_decoder: crate::MeteoraDlmmDecoder::new(),
meteora_damm_v1_decoder: crate::MeteoraDammV1Decoder::new(),
meteora_damm_v2_decoder: crate::MeteoraDammV2Decoder::new(),
fluxbeam_decoder: crate::FluxbeamDecoder::new(),
@@ -98,8 +100,7 @@ impl DexDecodeService {
}
let append_result = append_persisted_events_result(
&mut persisted,
self.decode_and_persist_meteora_damm_v2_events(&transaction, &instructions)
.await,
self.decode_and_persist_meteora_dlmm_events(&transaction, &instructions).await,
);
if let Err(error) = append_result {
return Err(error);
@@ -112,6 +113,14 @@ impl DexDecodeService {
if let Err(error) = append_result {
return Err(error);
}
let append_result = append_persisted_events_result(
&mut persisted,
self.decode_and_persist_meteora_damm_v2_events(&transaction, &instructions)
.await,
);
if let Err(error) = append_result {
return Err(error);
}
let append_result = append_persisted_events_result(
&mut persisted,
self.decode_and_persist_orca_whirlpools_events(&transaction, &instructions)
@@ -311,6 +320,96 @@ impl DexDecodeService {
}
}
async fn persist_meteora_dbc_event(
&self,
transaction: &crate::ChainTransactionDto,
decoded_event: &crate::MeteoraDbcDecodedEvent,
) -> Result<crate::DexDecodedEventDto, crate::Error> {
match decoded_event {
crate::MeteoraDbcDecodedEvent::CreatePool(event) => {
return self
.materialize_named_dex_event(
transaction,
event.transaction_id,
event.instruction_id,
"meteora_dbc",
event.program_id.clone(),
"meteora_dbc.create_pool",
event.pool_account.clone(),
None,
event.token_a_mint.clone(),
event.token_b_mint.clone(),
event.config_account.clone(),
event.payload_json.clone(),
)
.await;
},
crate::MeteoraDbcDecodedEvent::Swap(event) => {
return self
.materialize_named_dex_event(
transaction,
event.transaction_id,
event.instruction_id,
"meteora_dbc",
event.program_id.clone(),
"meteora_dbc.swap",
event.pool_account.clone(),
None,
event.token_a_mint.clone(),
event.token_b_mint.clone(),
None,
event.payload_json.clone(),
)
.await;
},
}
}
async fn persist_meteora_dlmm_event(
&self,
transaction: &crate::ChainTransactionDto,
decoded_event: &crate::MeteoraDlmmDecodedEvent,
) -> Result<crate::DexDecodedEventDto, crate::Error> {
match decoded_event {
crate::MeteoraDlmmDecodedEvent::CreatePool(event) => {
return self
.materialize_named_dex_event(
transaction,
event.transaction_id,
event.instruction_id,
"meteora_dlmm",
event.program_id.clone(),
"meteora_dlmm.create_pool",
event.pool_account.clone(),
None,
event.token_a_mint.clone(),
event.token_b_mint.clone(),
event.config_account.clone(),
event.payload_json.clone(),
)
.await;
},
crate::MeteoraDlmmDecodedEvent::Swap(event) => {
return self
.materialize_named_dex_event(
transaction,
event.transaction_id,
event.instruction_id,
"meteora_dlmm",
event.program_id.clone(),
"meteora_dlmm.swap",
event.pool_account.clone(),
None,
event.token_a_mint.clone(),
event.token_b_mint.clone(),
None,
event.payload_json.clone(),
)
.await;
},
}
}
async fn persist_meteora_damm_v1_event(
&self,
transaction: &crate::ChainTransactionDto,
@@ -336,6 +435,8 @@ impl DexDecodeService {
.await;
},
crate::MeteoraDammV1DecodedEvent::Swap(event) => {
let enrichment_payload_json =
prepare_meteora_damm_v1_swap_payload_for_classification(event);
return self
.materialize_named_dex_event(
transaction,
@@ -349,7 +450,7 @@ impl DexDecodeService {
event.token_a_mint.clone(),
event.token_b_mint.clone(),
None,
event.payload_json.clone(),
enrichment_payload_json,
)
.await;
},
@@ -401,51 +502,6 @@ impl DexDecodeService {
}
}
async fn persist_meteora_dbc_event(
&self,
transaction: &crate::ChainTransactionDto,
decoded_event: &crate::MeteoraDbcDecodedEvent,
) -> Result<crate::DexDecodedEventDto, crate::Error> {
match decoded_event {
crate::MeteoraDbcDecodedEvent::CreatePool(event) => {
return self
.materialize_named_dex_event(
transaction,
event.transaction_id,
event.instruction_id,
"meteora_dbc",
event.program_id.clone(),
"meteora_dbc.create_pool",
event.pool_account.clone(),
None,
event.token_a_mint.clone(),
event.token_b_mint.clone(),
event.config_account.clone(),
event.payload_json.clone(),
)
.await;
},
crate::MeteoraDbcDecodedEvent::Swap(event) => {
return self
.materialize_named_dex_event(
transaction,
event.transaction_id,
event.instruction_id,
"meteora_dbc",
event.program_id.clone(),
"meteora_dbc.swap",
event.pool_account.clone(),
None,
event.token_a_mint.clone(),
event.token_b_mint.clone(),
None,
event.payload_json.clone(),
)
.await;
},
}
}
async fn persist_raydium_amm_v4_event(
&self,
transaction: &crate::ChainTransactionDto,
@@ -900,6 +956,29 @@ impl DexDecodeService {
return Ok(persisted);
}
async fn decode_and_persist_meteora_dlmm_events(
&self,
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
let decoded_result =
self.meteora_dlmm_decoder.decode_transaction(transaction, instructions);
let decoded_events = match decoded_result {
Ok(decoded_events) => decoded_events,
Err(error) => return Err(error),
};
let mut persisted = std::vec::Vec::new();
for decoded_event in &decoded_events {
let persist_result = self.persist_meteora_dlmm_event(transaction, decoded_event).await;
let persisted_event = match persist_result {
Ok(persisted_event) => persisted_event,
Err(error) => return Err(error),
};
persisted.push(persisted_event);
}
return Ok(persisted);
}
async fn decode_and_persist_meteora_damm_v1_events(
&self,
transaction: &crate::ChainTransactionDto,
@@ -1056,6 +1135,38 @@ fn enriched_raydium_payload_value(
return Ok(crate::enrich_dex_decoded_payload(protocol_name, event_kind, payload_value));
}
// Marks Meteora DAMM v1 swaps without direct amounts as non-materializable candidates
// before generic classification metadata is inserted.
fn prepare_meteora_damm_v1_swap_payload_for_classification(
event: &crate::MeteoraDammV1SwapDecoded,
) -> serde_json::Value {
let mut object = match event.payload_json.clone() {
serde_json::Value::Object(object) => object,
other => {
let mut object = serde_json::Map::new();
object.insert("rawPayload".to_string(), other);
object
},
};
let payload_json = serde_json::Value::Object(object.clone());
if crate::dex_event_classification::decoded_payload_has_trade_amount_or_price_payload(
&payload_json,
) {
return serde_json::Value::Object(object);
}
object.insert("tradeCandidate".to_string(), serde_json::Value::Bool(false));
object.insert("candleCandidate".to_string(), serde_json::Value::Bool(false));
object.insert(
"skipTradeReason".to_string(),
serde_json::Value::String("meteora_damm_v1_swap_without_amount_payload".to_string()),
);
object.insert(
"skipCandleReason".to_string(),
serde_json::Value::String("meteora_damm_v1_swap_without_amount_payload".to_string()),
);
return serde_json::Value::Object(object);
}
// Marks incomplete PumpSwap decoded trades as non-materializable candidates before generic
// classification metadata is inserted.
fn prepare_pump_swap_trade_payload_for_classification(
@@ -1313,6 +1424,73 @@ mod tests {
assert_eq!(decoded[0].token_a_mint, Some("MintPF111".to_string()));
}
#[test]
fn prepare_meteora_damm_v1_swap_without_amounts_marks_event_non_actionable() {
let event = crate::MeteoraDammV1SwapDecoded {
transaction_id: 1,
instruction_id: 2,
signature: "sig-damm-v1-no-amounts".to_string(),
program_id: crate::METEORA_DAMM_V1_PROGRAM_ID.to_string(),
trade_side: crate::SwapTradeSide::Unknown,
pool_account: Some("PoolDammV1".to_string()),
token_a_mint: Some("TokenA".to_string()),
token_b_mint: Some("TokenB".to_string()),
payload_json: serde_json::json!({
"decoder": "meteora_damm_v1",
"eventKind": "swap"
}),
};
let prepared_payload =
super::prepare_meteora_damm_v1_swap_payload_for_classification(&event);
let object_option = prepared_payload.as_object();
let object = match object_option {
Some(object) => object,
None => {
panic!("expected prepared payload object");
},
};
assert_eq!(object.get("tradeCandidate"), Some(&serde_json::Value::Bool(false)));
assert_eq!(object.get("candleCandidate"), Some(&serde_json::Value::Bool(false)));
assert_eq!(
object.get("skipTradeReason"),
Some(&serde_json::Value::String(
"meteora_damm_v1_swap_without_amount_payload".to_string()
))
);
}
#[test]
fn prepare_meteora_damm_v1_swap_with_amounts_keeps_event_actionable() {
let event = crate::MeteoraDammV1SwapDecoded {
transaction_id: 1,
instruction_id: 2,
signature: "sig-damm-v1-with-amounts".to_string(),
program_id: crate::METEORA_DAMM_V1_PROGRAM_ID.to_string(),
trade_side: crate::SwapTradeSide::Unknown,
pool_account: Some("PoolDammV1".to_string()),
token_a_mint: Some("TokenA".to_string()),
token_b_mint: Some("TokenB".to_string()),
payload_json: serde_json::json!({
"decoder": "meteora_damm_v1",
"eventKind": "swap",
"baseAmountRaw": "100",
"quoteAmountRaw": "250"
}),
};
let prepared_payload =
super::prepare_meteora_damm_v1_swap_payload_for_classification(&event);
let object_option = prepared_payload.as_object();
let object = match object_option {
Some(object) => object,
None => {
panic!("expected prepared payload object");
},
};
assert_eq!(object.get("tradeCandidate"), None);
assert_eq!(object.get("candleCandidate"), None);
assert_eq!(object.get("skipTradeReason"), None);
}
#[tokio::test]
async fn decode_transaction_by_signature_persists_decoded_pump_swap_event() {
let database = make_database().await;