This commit is contained in:
2026-05-21 09:46:54 +02:00
parent 62831a0abe
commit 6176c5d4cd
17 changed files with 1272 additions and 116 deletions

View File

@@ -55,6 +55,7 @@ pub use pump_swap::PumpSwapTradeDecoded;
pub use raydium_amm_v4::RaydiumAmmV4DecodedEvent;
pub use raydium_amm_v4::RaydiumAmmV4Decoder;
pub use raydium_amm_v4::RaydiumAmmV4Initialize2PoolDecoded;
pub use raydium_amm_v4::RaydiumAmmV4SwapDecoded;
pub use raydium_clmm::RaydiumClmmDecodedEvent;
pub use raydium_clmm::RaydiumClmmSwapV2Decoded;
pub use raydium_clmm::decode_raydium_clmm_instruction;

File diff suppressed because it is too large Load Diff

View File

@@ -562,6 +562,24 @@ impl DexDecodeService {
)
.await;
},
crate::RaydiumAmmV4DecodedEvent::Swap(event) => {
return self
.materialize_named_dex_event(
transaction,
event.transaction_id,
event.instruction_id,
"raydium_amm_v4",
event.program_id.clone(),
"raydium_amm_v4.swap",
Some(event.pool_account.clone()),
None,
Some(event.token_a_mint.clone()),
Some(event.token_b_mint.clone()),
None,
event.payload_json.clone(),
)
.await;
},
}
}

View File

@@ -88,6 +88,9 @@ impl DexDetectService {
crate::dex_detection_route::DexDetectionRoute::RaydiumAmmV4Initialize2Pool => {
self.detect_raydium_initialize2_pool(&transaction, decoded_event).await
},
crate::dex_detection_route::DexDetectionRoute::RaydiumAmmV4Trade => {
self.detect_raydium_amm_v4_trade(&transaction, decoded_event).await
},
crate::dex_detection_route::DexDetectionRoute::RaydiumCpmmTrade => {
self.detect_raydium_cpmm_trade(&transaction, decoded_event).await
},
@@ -522,6 +525,60 @@ impl DexDetectService {
.await;
}
async fn detect_raydium_amm_v4_trade(
&self,
transaction: &crate::ChainTransactionDto,
decoded_event: &crate::DexDecodedEventDto,
) -> Result<crate::DexPoolDetectionResult, crate::Error> {
let dex_id_result =
crate::dex_catalog::ensure_known_dex(self.database.as_ref(), "raydium_amm_v4").await;
let dex_id = match dex_id_result {
Ok(dex_id) => dex_id,
Err(error) => return Err(error),
};
let payload_value_result = parse_payload_json(decoded_event.payload_json.as_str());
let payload_value = match payload_value_result {
Ok(payload_value) => payload_value,
Err(error) => return Err(error),
};
let base_vault_address = extract_payload_string_field(&payload_value, "baseVault");
let quote_vault_address = extract_payload_string_field(&payload_value, "quoteVault");
let input_result =
crate::dex_pool_materialization::DexPoolMaterializationInput::from_decoded_event(
decoded_event,
dex_id,
crate::PoolKind::Amm,
crate::PoolStatus::Active,
crate::dex_pool_materialization::DexPoolTokenOrder::AlreadyBaseQuote,
base_vault_address,
quote_vault_address,
transaction.source_endpoint_name.clone(),
);
let input = match input_result {
Ok(input) => input,
Err(error) => return Err(error),
};
let detection_result =
crate::dex_pool_materialization::materialize_dex_pool(self.database.as_ref(), &input)
.await;
let detection_result = match detection_result {
Ok(detection_result) => detection_result,
Err(error) => return Err(error),
};
let signal_result = self
.record_pool_detection_signals(
transaction,
"signal.dex.raydium_amm_v4",
&detection_result,
payload_value,
)
.await;
if let Err(error) = signal_result {
return Err(error);
}
return Ok(detection_result);
}
async fn detect_raydium_clmm_trade(
&self,
transaction: &crate::ChainTransactionDto,

View File

@@ -7,6 +7,8 @@
pub(crate) enum DexDetectionRoute {
/// Raydium AMM v4 initialize2 pool route.
RaydiumAmmV4Initialize2Pool,
/// Raydium AMM v4 trade route.
RaydiumAmmV4Trade,
/// Raydium CPMM trade route.
RaydiumCpmmTrade,
/// Raydium CLMM trade route.
@@ -45,6 +47,9 @@ pub(crate) fn dex_detection_route(
crate::dex_detection_route::DexDetectionRoute::RaydiumAmmV4Initialize2Pool,
);
},
("raydium_amm_v4", "raydium_amm_v4.swap") => {
return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumAmmV4Trade);
},
("raydium_cpmm", "raydium_cpmm.swap_base_input") => {
return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumCpmmTrade);
},

View File

@@ -235,16 +235,16 @@ const DEX_SUPPORT_MATRIX_ENTRIES: &[DexSupportMatrixEntry] = &[
program_id: Some(crate::RAYDIUM_AMM_V4_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: false,
observed: true,
decoded: true,
materialized: true,
trade_candidate: true,
candle_candidate: true,
pair_candidate: true,
pool_candidate: true,
status: "partial",
confidence: "medium",
skip_reason: Some("not_observed_in_0_7_28_replay"),
status: "supported",
confidence: "high",
skip_reason: None,
catalog_enabled: true,
},
DexSupportMatrixEntry {
@@ -949,6 +949,22 @@ mod tests {
assert_eq!(raydium_entry.code, "raydium_amm_v4");
}
#[test]
fn matrix_marks_raydium_amm_v4_supported_after_0_7_41() {
let entry = match crate::dex_support_matrix_entry_by_code("raydium_amm_v4") {
Some(entry) => entry,
None => panic!("expected raydium_amm_v4 matrix entry"),
};
assert!(entry.observed);
assert!(entry.decoded);
assert!(entry.materialized);
assert!(entry.trade_candidate);
assert!(entry.candle_candidate);
assert_eq!(entry.status, "supported");
assert_eq!(entry.confidence, "high");
assert_eq!(entry.skip_reason, None);
}
#[test]
fn matrix_marks_partial_meteora_damm_v1_correctly() {
let entry = match crate::dex_support_matrix_entry_by_code("meteora_damm_v1") {

View File

@@ -923,6 +923,8 @@ pub use dex::RaydiumAmmV4DecodedEvent;
pub use dex::RaydiumAmmV4Decoder;
/// Decoded Raydium AmmV4 initialize2 pool event.
pub use dex::RaydiumAmmV4Initialize2PoolDecoded;
/// Decoded Raydium AMM v4 swap event.
pub use dex::RaydiumAmmV4SwapDecoded;
/// Decoded Raydium CLMM event.
pub use dex::RaydiumClmmDecodedEvent;
/// Decoded Raydium CLMM swap_v2 instruction.

View File

@@ -310,6 +310,20 @@ impl LocalPipelineValidationConfig {
return config;
}
/// Builds the `0.7.41` Raydium AMM v4 swap decoder validation config.
///
/// This profile keeps the Raydium surface diagnostics and labels validation
/// runs produced after the AMM v4 swap decoder is active. Missing AMM v4
/// remains a warning so empty or unrelated local corpora stay inspectable.
pub fn v0_7_41_raydium_amm_v4_swap_decoder() -> Self {
let mut config = Self::v0_7_40_raydium_effective_surfaces();
config.profile_code = "0.7.41_raydium_amm_v4_swap_decoder".to_string();
config.expected_dex_codes = vec!["raydium_amm_v4".to_string()];
config.require_all_expected_dexes = false;
config.allow_unexpected_dexes = true;
return config;
}
/// Builds the legacy `0.7.39` launch-surface validation alias.
///
/// The implementation now delegates to the DEX-first profile so callers that
@@ -551,7 +565,8 @@ impl LocalPipelineValidationService {
pub async fn validate_v0_7_39_current_database(
&self,
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
let config = crate::LocalPipelineValidationConfig::v0_7_39_dex_first_effective_swap_surfaces();
let config =
crate::LocalPipelineValidationConfig::v0_7_39_dex_first_effective_swap_surfaces();
return self.validate_current_database(&config).await;
}
@@ -562,6 +577,14 @@ impl LocalPipelineValidationService {
let config = crate::LocalPipelineValidationConfig::v0_7_40_raydium_effective_surfaces();
return self.validate_current_database(&config).await;
}
/// Diagnoses the current database with the `0.7.41` Raydium AMM v4 profile.
pub async fn validate_v0_7_41_current_database(
&self,
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
let config = crate::LocalPipelineValidationConfig::v0_7_41_raydium_amm_v4_swap_decoder();
return self.validate_current_database(&config).await;
}
}
/// Validates a diagnostics summary without performing database access.
@@ -687,7 +710,8 @@ pub fn validate_local_pipeline_diagnostics_summary(
|| config.profile_code == "0.7.38_token_metadata_gap_prioritization"
|| config.profile_code == "0.7.39_dex_first_effective_swap_surfaces"
|| config.profile_code == "0.7.39_launch_surface_origin_baseline"
|| config.profile_code == "0.7.40_raydium_effective_surfaces";
|| config.profile_code == "0.7.40_raydium_effective_surfaces"
|| config.profile_code == "0.7.41_raydium_amm_v4_swap_decoder";
if config.require_all_expected_dexes || missing_expected_dex_is_warning {
for expected_dex_code in &expected_dex_codes {
if !observed_dex_codes.contains(expected_dex_code) {
@@ -1112,27 +1136,25 @@ mod tests {
pair_candle_count: 131,
},
],
raydium_surface_summaries: vec![
crate::LocalRaydiumSurfaceDiagnosticSummaryDto {
dex_code: "raydium_clmm".to_string(),
display_name: "Raydium CLMM".to_string(),
surface_role: "dex_effective".to_string(),
program_id: Some(crate::RAYDIUM_CLMM_PROGRAM_ID.to_string()),
program_id_status: "known".to_string(),
status: "supported".to_string(),
catalog_enabled: true,
instruction_count: 106,
transaction_count: 101,
decoded_event_count: 106,
trade_event_count: 101,
pair_candle_count: 131,
latest_slot: Some(1),
latest_signature: Some("raydium_clmm_fixture".to_string()),
observed_in_current_corpus: true,
decoded_in_current_corpus: true,
trade_materialized_in_current_corpus: true,
},
],
raydium_surface_summaries: vec![crate::LocalRaydiumSurfaceDiagnosticSummaryDto {
dex_code: "raydium_clmm".to_string(),
display_name: "Raydium CLMM".to_string(),
surface_role: "dex_effective".to_string(),
program_id: Some(crate::RAYDIUM_CLMM_PROGRAM_ID.to_string()),
program_id_status: "known".to_string(),
status: "supported".to_string(),
catalog_enabled: true,
instruction_count: 106,
transaction_count: 101,
decoded_event_count: 106,
trade_event_count: 101,
pair_candle_count: 131,
latest_slot: Some(1),
latest_signature: Some("raydium_clmm_fixture".to_string()),
observed_in_current_corpus: true,
decoded_in_current_corpus: true,
trade_materialized_in_current_corpus: true,
}],
pair_summaries: vec![],
pair_actionability_summaries: vec![
crate::LocalPairActionabilityDiagnosticSummaryDto {
@@ -1490,7 +1512,8 @@ mod tests {
#[test]
fn validation_accepts_0_7_39_dex_first_effective_swap_surfaces() {
let summary = make_0_7_28_summary_with_meteora();
let config = crate::LocalPipelineValidationConfig::v0_7_39_dex_first_effective_swap_surfaces();
let config =
crate::LocalPipelineValidationConfig::v0_7_39_dex_first_effective_swap_surfaces();
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
assert!(report.validation_passed);
assert_eq!(report.validation_profile_code, "0.7.39_dex_first_effective_swap_surfaces");
@@ -1509,6 +1532,18 @@ mod tests {
assert!(report.expected_dex_codes.contains(&"raydium_amm_v4".to_string()));
}
#[test]
fn validation_accepts_0_7_41_raydium_amm_v4_swap_decoder_profile() {
let summary = make_0_7_28_summary_with_meteora();
let config = crate::LocalPipelineValidationConfig::v0_7_41_raydium_amm_v4_swap_decoder();
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
assert!(report.validation_passed);
assert_eq!(report.validation_profile_code, "0.7.41_raydium_amm_v4_swap_decoder");
assert_eq!(report.blocking_issue_count, 0);
assert!(report.warning_count >= 1);
assert!(report.expected_dex_codes.contains(&"raydium_amm_v4".to_string()));
}
#[test]
fn validation_rejects_0_7_33_pair_trading_readiness_mismatch() {
let mut summary = make_0_7_28_summary_with_meteora();

View File

@@ -96,7 +96,8 @@ pub(crate) async fn resolve_trade_amounts(
return Err(error);
}
}
if (input.decoded_event.event_kind.starts_with("raydium_cpmm.")
if (input.decoded_event.event_kind.starts_with("raydium_amm_v4.")
|| input.decoded_event.event_kind.starts_with("raydium_cpmm.")
|| input.decoded_event.event_kind.starts_with("raydium_clmm."))
&& (base_amount_raw.is_none()
|| quote_amount_raw.is_none()
@@ -114,6 +115,21 @@ pub(crate) async fn resolve_trade_amounts(
return Err(error);
}
}
if input.decoded_event.event_kind.starts_with("raydium_amm_v4.")
&& (base_amount_raw.is_none() || quote_amount_raw.is_none())
{
let resolution_result = crate::trade_amount_resolution::apply_vault_balance_delta_fallback(
input,
input.base_vault_address,
input.quote_vault_address,
&mut base_amount_raw,
&mut quote_amount_raw,
&mut price_quote_per_base,
);
if let Err(error) = resolution_result {
return Err(error);
}
}
if input.decoded_event.event_kind.starts_with("raydium_cpmm.")
&& (base_amount_raw.is_none() || quote_amount_raw.is_none())
{
@@ -204,6 +220,17 @@ pub(crate) async fn resolve_trade_amounts(
return Err(error);
}
}
if input.decoded_event.event_kind.starts_with("raydium_amm_v4.") {
let vault_side = crate::trade_amount_resolution::infer_trade_side_from_vault_balance_deltas(
input.transaction.meta_json.as_deref(),
input.transaction.transaction_json.as_str(),
input.base_vault_address,
input.quote_vault_address,
);
if vault_side.is_some() {
resolved_trade_side = vault_side;
}
}
if input.decoded_event.event_kind.starts_with("meteora_dlmm.") {
let vault_side = crate::trade_amount_resolution::infer_trade_side_from_vault_balance_deltas(
input.transaction.meta_json.as_deref(),