0.7.43-E5C
This commit is contained in:
@@ -178,6 +178,43 @@ pub const METEORA_DBC_PROGRAM_ID: &str = "dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4Du
|
||||
/// DLMM = Dynamic Liquidity Market Maker.
|
||||
pub const METEORA_DLMM_PROGRAM_ID: &str = "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo";
|
||||
|
||||
/// MetaDAO META active token mint identifier from MetaDAO documentation.
|
||||
pub const METADAO_META_MINT_ID: &str = "METAwkXcqyXKy1AtsSgJ8JiUHwGCafnZL38n3vYmeta";
|
||||
|
||||
/// MetaDAO METAC legacy token mint identifier from MetaDAO documentation.
|
||||
pub const METADAO_METAC_LEGACY_MINT_ID: &str = "METADDFL6wWMWEoKTFJwcThTbUmtarRJZjRpzUvkxhr";
|
||||
|
||||
/// MetaDAO-linked P2P token mint candidate observed on Solscan.
|
||||
///
|
||||
/// This is a token mint, not a DEX program id. It is exposed for discovery only.
|
||||
pub const METADAO_P2P_MINT_ID: &str = "P2PXup1ZvMpCDkJn3PQxtBYgxeCSfH39SFeurGSmeta";
|
||||
|
||||
/// MetaDAO Launchpad v0.7.0 program id from MetaDAO documentation and Solscan.
|
||||
pub const METADAO_LAUNCHPAD_V0_7_0_PROGRAM_ID: &str =
|
||||
"moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM";
|
||||
|
||||
/// MetaDAO Bid Wall v0.7.0 program id from MetaDAO documentation.
|
||||
pub const METADAO_BID_WALL_V0_7_0_PROGRAM_ID: &str =
|
||||
"WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx";
|
||||
|
||||
/// MetaDAO Futarchy v0.6.0 program id from MetaDAO documentation.
|
||||
pub const METADAO_FUTARCHY_V0_6_0_PROGRAM_ID: &str =
|
||||
"FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq";
|
||||
|
||||
/// MetaDAO AMM v0.5.0 program id from MetaDAO documentation.
|
||||
pub const METADAO_AMM_V0_5_0_PROGRAM_ID: &str =
|
||||
"AMMJdEiCCa8mdugg6JPF7gFirmmxisTfDJoSNSUi5zDJ";
|
||||
|
||||
/// Printr program id candidate observed on Solscan.
|
||||
///
|
||||
/// This remains a discovery target until a local corpus confirms the instruction semantics.
|
||||
pub const PRINTR_PROGRAM_ID: &str = "T8HsGYv7sMk3kTnyaRqZrbRPuntYzdh12evXBkprint";
|
||||
|
||||
/// Zora program id candidate observed on Solscan.
|
||||
///
|
||||
/// This remains a discovery target until a local corpus confirms the instruction semantics.
|
||||
pub const ZORA_PROGRAM_ID: &str = "zoRabwLGd5zXaV7Gxacppw8tcceXEiTrSKyNLSaSTUc";
|
||||
|
||||
/// Orca Whirlpools program id. ("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc").
|
||||
pub const ORCA_WHIRLPOOLS_PROGRAM_ID: &str = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
|
||||
|
||||
|
||||
@@ -221,7 +221,76 @@ impl MeteoraDammV1Decoder {
|
||||
continue;
|
||||
}
|
||||
if instruction_kind == MeteoraDammV1InstructionKind::Swap {
|
||||
let trade_side = infer_trade_side(&log_messages);
|
||||
let decoded_amounts_result =
|
||||
crate::meteora_swap_amount_inference::infer_meteora_swap_amounts_from_inner_transfers(
|
||||
transaction,
|
||||
instructions,
|
||||
instruction,
|
||||
pool_account.as_deref(),
|
||||
);
|
||||
let inner_transfer_amounts = match decoded_amounts_result {
|
||||
Ok(decoded_amounts) => decoded_amounts,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let mut amount_resolution_source = "none";
|
||||
let decoded_amounts = match inner_transfer_amounts {
|
||||
Some(decoded_amounts) => {
|
||||
amount_resolution_source = "flattened_cpi_pool_transfer_window";
|
||||
Some(decoded_amounts)
|
||||
},
|
||||
None => {
|
||||
let event_log_amounts_result =
|
||||
crate::meteora_swap_amount_inference::infer_meteora_damm_v1_swap_amounts_from_event_log(
|
||||
transaction,
|
||||
accounts.as_slice(),
|
||||
log_messages.as_slice(),
|
||||
pool_account.as_deref(),
|
||||
);
|
||||
match event_log_amounts_result {
|
||||
Ok(event_log_amounts) => match event_log_amounts {
|
||||
Some(event_log_amounts) => {
|
||||
amount_resolution_source = "meteora_damm_v1_swap_event_log";
|
||||
Some(event_log_amounts)
|
||||
},
|
||||
None => None,
|
||||
},
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
},
|
||||
};
|
||||
let fallback_trade_side = infer_trade_side(&log_messages);
|
||||
let trade_side = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => decoded_amounts.trade_side,
|
||||
None => fallback_trade_side,
|
||||
};
|
||||
let effective_token_a_mint = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => Some(decoded_amounts.base_token_mint.clone()),
|
||||
None => token_a_mint.clone(),
|
||||
};
|
||||
let effective_token_b_mint = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => Some(decoded_amounts.quote_token_mint.clone()),
|
||||
None => token_b_mint.clone(),
|
||||
};
|
||||
let base_vault = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => decoded_amounts.base_vault_address.clone(),
|
||||
None => None,
|
||||
};
|
||||
let quote_vault = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => decoded_amounts.quote_vault_address.clone(),
|
||||
None => None,
|
||||
};
|
||||
let base_amount_raw = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => {
|
||||
serde_json::Value::String(decoded_amounts.base_amount_raw.clone())
|
||||
},
|
||||
None => serde_json::Value::Null,
|
||||
};
|
||||
let quote_amount_raw = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => {
|
||||
serde_json::Value::String(decoded_amounts.quote_amount_raw.clone())
|
||||
},
|
||||
None => serde_json::Value::Null,
|
||||
};
|
||||
let payload_json = serde_json::json!({
|
||||
"decoder": "meteora_damm_v1",
|
||||
"eventKind": "swap",
|
||||
@@ -236,8 +305,15 @@ impl MeteoraDammV1Decoder {
|
||||
"parsed": parsed_json,
|
||||
"logMessages": log_messages,
|
||||
"poolAccount": pool_account,
|
||||
"tokenAMint": token_a_mint,
|
||||
"tokenBMint": token_b_mint,
|
||||
"tokenAMint": effective_token_a_mint.clone(),
|
||||
"tokenBMint": effective_token_b_mint.clone(),
|
||||
"inputTokenAccount": extract_account(&accounts, 1),
|
||||
"outputTokenAccount": extract_account(&accounts, 2),
|
||||
"baseVault": base_vault,
|
||||
"quoteVault": quote_vault,
|
||||
"baseAmountRaw": base_amount_raw,
|
||||
"quoteAmountRaw": quote_amount_raw,
|
||||
"amountResolutionSource": amount_resolution_source,
|
||||
"tradeSide": format!("{:?}", trade_side)
|
||||
});
|
||||
decoded_events.push(crate::MeteoraDammV1DecodedEvent::Swap(
|
||||
@@ -248,8 +324,8 @@ impl MeteoraDammV1Decoder {
|
||||
program_id: program_id.clone(),
|
||||
trade_side,
|
||||
pool_account,
|
||||
token_a_mint,
|
||||
token_b_mint,
|
||||
token_a_mint: effective_token_a_mint,
|
||||
token_b_mint: effective_token_b_mint,
|
||||
payload_json,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -238,9 +238,27 @@ impl MeteoraDammV2Decoder {
|
||||
if instruction_kind == MeteoraDammV2InstructionKind::Swap {
|
||||
let used_swap2 = log_messages_contain_keyword(&log_messages, "swap2")
|
||||
|| value_contains_any_key(parsed_json.as_ref(), &["swap2", "isSwap2"]);
|
||||
let trade_side = infer_trade_side(&log_messages);
|
||||
let has_trade_amount_payload =
|
||||
let decoded_amounts_result =
|
||||
crate::meteora_swap_amount_inference::infer_meteora_swap_amounts_from_inner_transfers(
|
||||
transaction,
|
||||
instructions,
|
||||
instruction,
|
||||
pool_account.as_deref(),
|
||||
);
|
||||
let decoded_amounts = match decoded_amounts_result {
|
||||
Ok(decoded_amounts) => decoded_amounts,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let fallback_trade_side = infer_trade_side(&log_messages);
|
||||
let trade_side = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => decoded_amounts.trade_side,
|
||||
None => fallback_trade_side,
|
||||
};
|
||||
let has_direct_amount_payload =
|
||||
parsed_json_has_trade_amount_or_price_payload(parsed_json.as_ref());
|
||||
let has_inferred_amount_payload = decoded_amounts.is_some();
|
||||
let has_trade_amount_payload =
|
||||
has_direct_amount_payload || has_inferred_amount_payload;
|
||||
let event_actionability = if has_trade_amount_payload {
|
||||
"trade_candidate"
|
||||
} else {
|
||||
@@ -251,6 +269,41 @@ impl MeteoraDammV2Decoder {
|
||||
} else {
|
||||
serde_json::Value::String("swap_without_amount_payload".to_string())
|
||||
};
|
||||
let effective_token_a_mint = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => Some(decoded_amounts.base_token_mint.clone()),
|
||||
None => token_a_mint.clone(),
|
||||
};
|
||||
let effective_token_b_mint = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => Some(decoded_amounts.quote_token_mint.clone()),
|
||||
None => token_b_mint.clone(),
|
||||
};
|
||||
let base_vault = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => decoded_amounts.base_vault_address.clone(),
|
||||
None => None,
|
||||
};
|
||||
let quote_vault = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => decoded_amounts.quote_vault_address.clone(),
|
||||
None => None,
|
||||
};
|
||||
let base_amount_raw = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => {
|
||||
serde_json::Value::String(decoded_amounts.base_amount_raw.clone())
|
||||
},
|
||||
None => serde_json::Value::Null,
|
||||
};
|
||||
let quote_amount_raw = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => {
|
||||
serde_json::Value::String(decoded_amounts.quote_amount_raw.clone())
|
||||
},
|
||||
None => serde_json::Value::Null,
|
||||
};
|
||||
let amount_resolution_source = if has_inferred_amount_payload {
|
||||
"flattened_cpi_pool_transfer_window"
|
||||
} else if has_direct_amount_payload {
|
||||
"parsed_payload"
|
||||
} else {
|
||||
"none"
|
||||
};
|
||||
let payload_json = serde_json::json!({
|
||||
"decoder": "meteora_damm_v2",
|
||||
"eventKind": "swap",
|
||||
@@ -265,6 +318,7 @@ impl MeteoraDammV2Decoder {
|
||||
"candleCandidate": has_trade_amount_payload,
|
||||
"nonTradeUseful": false,
|
||||
"materializationSkipReason": materialization_skip_reason,
|
||||
"amountResolutionSource": amount_resolution_source,
|
||||
"signature": transaction.signature,
|
||||
"instructionId": instruction_id,
|
||||
"instructionIndex": instruction.instruction_index,
|
||||
@@ -272,8 +326,12 @@ impl MeteoraDammV2Decoder {
|
||||
"parsed": parsed_json,
|
||||
"logMessages": log_messages,
|
||||
"poolAccount": pool_account,
|
||||
"tokenAMint": token_a_mint,
|
||||
"tokenBMint": token_b_mint,
|
||||
"tokenAMint": effective_token_a_mint.clone(),
|
||||
"tokenBMint": effective_token_b_mint.clone(),
|
||||
"baseVault": base_vault,
|
||||
"quoteVault": quote_vault,
|
||||
"baseAmountRaw": base_amount_raw,
|
||||
"quoteAmountRaw": quote_amount_raw,
|
||||
"tradeSide": format!("{:?}", trade_side)
|
||||
});
|
||||
decoded_events.push(crate::MeteoraDammV2DecodedEvent::Swap(
|
||||
@@ -284,8 +342,8 @@ impl MeteoraDammV2Decoder {
|
||||
program_id: program_id.clone(),
|
||||
trade_side,
|
||||
pool_account,
|
||||
token_a_mint,
|
||||
token_b_mint,
|
||||
token_a_mint: effective_token_a_mint,
|
||||
token_b_mint: effective_token_b_mint,
|
||||
used_swap2,
|
||||
payload_json,
|
||||
},
|
||||
|
||||
@@ -215,9 +215,27 @@ impl MeteoraDbcDecoder {
|
||||
continue;
|
||||
}
|
||||
if instruction_kind == MeteoraDbcInstructionKind::Swap {
|
||||
let trade_side = infer_trade_side(&log_messages);
|
||||
let has_trade_amount_payload =
|
||||
let decoded_amounts_result =
|
||||
crate::meteora_swap_amount_inference::infer_meteora_swap_amounts_from_inner_transfers(
|
||||
transaction,
|
||||
instructions,
|
||||
instruction,
|
||||
pool_account.as_deref(),
|
||||
);
|
||||
let decoded_amounts = match decoded_amounts_result {
|
||||
Ok(decoded_amounts) => decoded_amounts,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let fallback_trade_side = infer_trade_side(&log_messages);
|
||||
let trade_side = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => decoded_amounts.trade_side,
|
||||
None => fallback_trade_side,
|
||||
};
|
||||
let has_direct_amount_payload =
|
||||
parsed_json_has_trade_amount_or_price_payload(parsed_json.as_ref());
|
||||
let has_inferred_amount_payload = decoded_amounts.is_some();
|
||||
let has_trade_amount_payload =
|
||||
has_direct_amount_payload || has_inferred_amount_payload;
|
||||
let event_actionability = if has_trade_amount_payload {
|
||||
"trade_candidate"
|
||||
} else {
|
||||
@@ -228,6 +246,41 @@ impl MeteoraDbcDecoder {
|
||||
} else {
|
||||
serde_json::Value::String("swap_without_amount_payload".to_string())
|
||||
};
|
||||
let effective_token_a_mint = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => Some(decoded_amounts.base_token_mint.clone()),
|
||||
None => token_a_mint.clone(),
|
||||
};
|
||||
let effective_token_b_mint = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => Some(decoded_amounts.quote_token_mint.clone()),
|
||||
None => token_b_mint.clone(),
|
||||
};
|
||||
let base_vault = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => decoded_amounts.base_vault_address.clone(),
|
||||
None => None,
|
||||
};
|
||||
let quote_vault = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => decoded_amounts.quote_vault_address.clone(),
|
||||
None => None,
|
||||
};
|
||||
let base_amount_raw = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => {
|
||||
serde_json::Value::String(decoded_amounts.base_amount_raw.clone())
|
||||
},
|
||||
None => serde_json::Value::Null,
|
||||
};
|
||||
let quote_amount_raw = match decoded_amounts.as_ref() {
|
||||
Some(decoded_amounts) => {
|
||||
serde_json::Value::String(decoded_amounts.quote_amount_raw.clone())
|
||||
},
|
||||
None => serde_json::Value::Null,
|
||||
};
|
||||
let amount_resolution_source = if has_inferred_amount_payload {
|
||||
"flattened_cpi_pool_transfer_window"
|
||||
} else if has_direct_amount_payload {
|
||||
"parsed_payload"
|
||||
} else {
|
||||
"none"
|
||||
};
|
||||
let payload_json = serde_json::json!({
|
||||
"decoder": "meteora_dbc",
|
||||
"eventKind": "swap",
|
||||
@@ -242,6 +295,7 @@ impl MeteoraDbcDecoder {
|
||||
"candleCandidate": has_trade_amount_payload,
|
||||
"nonTradeUseful": false,
|
||||
"materializationSkipReason": materialization_skip_reason,
|
||||
"amountResolutionSource": amount_resolution_source,
|
||||
"signature": transaction.signature,
|
||||
"instructionId": instruction_id,
|
||||
"instructionIndex": instruction.instruction_index,
|
||||
@@ -249,8 +303,12 @@ impl MeteoraDbcDecoder {
|
||||
"parsed": parsed_json,
|
||||
"logMessages": log_messages,
|
||||
"poolAccount": pool_account,
|
||||
"tokenAMint": token_a_mint,
|
||||
"tokenBMint": token_b_mint,
|
||||
"tokenAMint": effective_token_a_mint.clone(),
|
||||
"tokenBMint": effective_token_b_mint.clone(),
|
||||
"baseVault": base_vault,
|
||||
"quoteVault": quote_vault,
|
||||
"baseAmountRaw": base_amount_raw,
|
||||
"quoteAmountRaw": quote_amount_raw,
|
||||
"tradeSide": format!("{:?}", trade_side)
|
||||
});
|
||||
decoded_events.push(crate::MeteoraDbcDecodedEvent::Swap(
|
||||
@@ -261,8 +319,8 @@ impl MeteoraDbcDecoder {
|
||||
program_id: program_id.clone(),
|
||||
trade_side,
|
||||
pool_account,
|
||||
token_a_mint,
|
||||
token_b_mint,
|
||||
token_a_mint: effective_token_a_mint,
|
||||
token_b_mint: effective_token_b_mint,
|
||||
payload_json,
|
||||
},
|
||||
));
|
||||
|
||||
@@ -82,9 +82,9 @@ pub struct RaydiumAmmV4SwapDecoded {
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum RaydiumAmmV4DecodedEvent {
|
||||
/// `initialize2` pool creation-like event.
|
||||
Initialize2Pool(RaydiumAmmV4Initialize2PoolDecoded),
|
||||
Initialize2Pool(std::boxed::Box<RaydiumAmmV4Initialize2PoolDecoded>),
|
||||
/// Swap event decoded from a direct or inner Raydium AMM v4 instruction.
|
||||
Swap(RaydiumAmmV4SwapDecoded),
|
||||
Swap(std::boxed::Box<RaydiumAmmV4SwapDecoded>),
|
||||
}
|
||||
|
||||
/// Raydium AmmV4 decoder.
|
||||
@@ -165,8 +165,9 @@ impl RaydiumAmmV4Decoder {
|
||||
log_messages.as_slice(),
|
||||
);
|
||||
if let Some(initialize_event) = initialize_event {
|
||||
decoded_events
|
||||
.push(crate::RaydiumAmmV4DecodedEvent::Initialize2Pool(initialize_event));
|
||||
decoded_events.push(crate::RaydiumAmmV4DecodedEvent::Initialize2Pool(
|
||||
std::boxed::Box::new(initialize_event),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -181,7 +182,8 @@ impl RaydiumAmmV4Decoder {
|
||||
token_balances.as_slice(),
|
||||
);
|
||||
match swap_result {
|
||||
Ok(Some(swap)) => decoded_events.push(crate::RaydiumAmmV4DecodedEvent::Swap(swap)),
|
||||
Ok(Some(swap)) => decoded_events
|
||||
.push(crate::RaydiumAmmV4DecodedEvent::Swap(std::boxed::Box::new(swap))),
|
||||
Ok(None) => {},
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
@@ -429,8 +431,8 @@ fn extract_transaction_account_keys(
|
||||
let mut account_keys = std::vec::Vec::new();
|
||||
let values = transaction
|
||||
.get("transaction")
|
||||
.and_then(|value| value.get("message"))
|
||||
.and_then(|value| value.get("accountKeys"))
|
||||
.and_then(|value| return value.get("message"))
|
||||
.and_then(|value| return value.get("accountKeys"))
|
||||
.and_then(serde_json::Value::as_array);
|
||||
if let Some(values) = values {
|
||||
let mut index = 0usize;
|
||||
@@ -467,8 +469,8 @@ fn append_loaded_addresses(
|
||||
key: &str,
|
||||
) {
|
||||
let addresses = meta
|
||||
.and_then(|value| value.get("loadedAddresses"))
|
||||
.and_then(|value| value.get(key))
|
||||
.and_then(|value| return value.get("loadedAddresses"))
|
||||
.and_then(|value| return value.get(key))
|
||||
.and_then(serde_json::Value::as_array);
|
||||
let addresses = match addresses {
|
||||
Some(addresses) => addresses,
|
||||
@@ -511,7 +513,9 @@ fn collect_token_balance_side(
|
||||
is_pre: bool,
|
||||
accumulators: &mut std::vec::Vec<TokenBalanceAccumulator>,
|
||||
) {
|
||||
let values = meta.and_then(|value| value.get(key)).and_then(serde_json::Value::as_array);
|
||||
let values = meta
|
||||
.and_then(|value| return value.get(key))
|
||||
.and_then(serde_json::Value::as_array);
|
||||
let values = match values {
|
||||
Some(values) => values,
|
||||
None => return,
|
||||
@@ -525,12 +529,12 @@ fn collect_token_balance_side(
|
||||
let owner = value
|
||||
.get("owner")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(|text| text.to_string());
|
||||
.map(|text| return text.to_string());
|
||||
let amount = value
|
||||
.get("uiTokenAmount")
|
||||
.and_then(|amount| amount.get("amount"))
|
||||
.and_then(|amount| return amount.get("amount"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(|text| text.to_string());
|
||||
.map(|text| return text.to_string());
|
||||
let account_address = match account_index {
|
||||
Some(account_index) => account_address_by_index(account_keys, account_index),
|
||||
None => None,
|
||||
@@ -652,7 +656,7 @@ fn infer_authority_owned_vault_pair(
|
||||
}
|
||||
if candidates
|
||||
.iter()
|
||||
.any(|candidate: &TokenBalanceRecord| candidate.mint == record.mint)
|
||||
.any(|candidate: &TokenBalanceRecord| return candidate.mint == record.mint)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -708,8 +708,8 @@ fn extract_transaction_account_keys(
|
||||
let mut account_keys = std::vec::Vec::new();
|
||||
let values = transaction
|
||||
.get("transaction")
|
||||
.and_then(|value| value.get("message"))
|
||||
.and_then(|value| value.get("accountKeys"))
|
||||
.and_then(|value| return value.get("message"))
|
||||
.and_then(|value| return value.get("accountKeys"))
|
||||
.and_then(serde_json::Value::as_array);
|
||||
if let Some(values) = values {
|
||||
let mut index = 0usize;
|
||||
@@ -746,8 +746,8 @@ fn append_loaded_addresses(
|
||||
key: &str,
|
||||
) {
|
||||
let addresses = meta
|
||||
.and_then(|value| value.get("loadedAddresses"))
|
||||
.and_then(|value| value.get(key))
|
||||
.and_then(|value| return value.get("loadedAddresses"))
|
||||
.and_then(|value| return value.get(key))
|
||||
.and_then(serde_json::Value::as_array);
|
||||
let addresses = match addresses {
|
||||
Some(addresses) => addresses,
|
||||
@@ -789,7 +789,9 @@ fn collect_token_balance_side(
|
||||
is_pre: bool,
|
||||
accumulators: &mut std::vec::Vec<TokenBalanceAccumulator>,
|
||||
) {
|
||||
let values = meta.and_then(|value| value.get(key)).and_then(serde_json::Value::as_array);
|
||||
let values = meta
|
||||
.and_then(|value| return value.get(key))
|
||||
.and_then(serde_json::Value::as_array);
|
||||
let values = match values {
|
||||
Some(values) => values,
|
||||
None => return,
|
||||
@@ -802,9 +804,9 @@ fn collect_token_balance_side(
|
||||
};
|
||||
let amount = value
|
||||
.get("uiTokenAmount")
|
||||
.and_then(|amount| amount.get("amount"))
|
||||
.and_then(|amount| return amount.get("amount"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(|text| text.to_string());
|
||||
.map(|text| return text.to_string());
|
||||
let account_address = match account_index {
|
||||
Some(account_index) => account_address_by_index(account_keys, account_index),
|
||||
None => None,
|
||||
|
||||
@@ -131,6 +131,14 @@ impl DexDecodeService {
|
||||
if let Err(error) = append_result {
|
||||
return Err(error);
|
||||
}
|
||||
let append_result = append_persisted_events_result(
|
||||
&mut persisted,
|
||||
self.preserve_unmatched_meteora_instruction_audits(&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)
|
||||
@@ -1069,15 +1077,15 @@ impl DexDecodeService {
|
||||
instruction.accounts_json.as_str(),
|
||||
);
|
||||
let token_a_mint = candidate_raydium_mapped_account(
|
||||
mapped_spec.and_then(|spec| spec.token_a_mint_index),
|
||||
mapped_spec.and_then(|spec| return spec.token_a_mint_index),
|
||||
accounts.as_slice(),
|
||||
);
|
||||
let token_b_mint = candidate_raydium_mapped_account(
|
||||
mapped_spec.and_then(|spec| spec.token_b_mint_index),
|
||||
mapped_spec.and_then(|spec| return spec.token_b_mint_index),
|
||||
accounts.as_slice(),
|
||||
);
|
||||
let lp_mint = candidate_raydium_mapped_account(
|
||||
mapped_spec.and_then(|spec| spec.lp_mint_index),
|
||||
mapped_spec.and_then(|spec| return spec.lp_mint_index),
|
||||
accounts.as_slice(),
|
||||
);
|
||||
let persist_result = self
|
||||
@@ -1105,6 +1113,95 @@ impl DexDecodeService {
|
||||
return Ok(persisted);
|
||||
}
|
||||
|
||||
async fn preserve_unmatched_meteora_instruction_audits(
|
||||
&self,
|
||||
transaction: &crate::ChainTransactionDto,
|
||||
instructions: &[crate::ChainInstructionDto],
|
||||
) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
|
||||
let transaction_id = match transaction.id {
|
||||
Some(transaction_id) => transaction_id,
|
||||
None => {
|
||||
return Err(crate::Error::InvalidState(format!(
|
||||
"transaction '{}' has no internal id",
|
||||
transaction.signature
|
||||
)));
|
||||
},
|
||||
};
|
||||
let decoded_events_result = crate::query_dex_decoded_events_list_by_transaction_id(
|
||||
self.database.as_ref(),
|
||||
transaction_id,
|
||||
)
|
||||
.await;
|
||||
let decoded_events = match decoded_events_result {
|
||||
Ok(decoded_events) => decoded_events,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let mut decoded_instruction_ids = std::collections::HashSet::<i64>::new();
|
||||
for decoded_event in &decoded_events {
|
||||
if !decoded_event.protocol_name.starts_with("meteora_") {
|
||||
continue;
|
||||
}
|
||||
if decoded_event.event_kind.ends_with(".instruction_audit") {
|
||||
continue;
|
||||
}
|
||||
let instruction_id = match decoded_event.instruction_id {
|
||||
Some(instruction_id) => instruction_id,
|
||||
None => continue,
|
||||
};
|
||||
decoded_instruction_ids.insert(instruction_id);
|
||||
}
|
||||
let mut persisted = std::vec::Vec::new();
|
||||
for instruction in instructions {
|
||||
let program_id = match instruction.program_id.as_ref() {
|
||||
Some(program_id) => program_id,
|
||||
None => continue,
|
||||
};
|
||||
let audit_spec = match meteora_instruction_audit_spec(program_id.as_str()) {
|
||||
Some(audit_spec) => audit_spec,
|
||||
None => continue,
|
||||
};
|
||||
let instruction_id = match instruction.id {
|
||||
Some(instruction_id) => instruction_id,
|
||||
None => continue,
|
||||
};
|
||||
if decoded_instruction_ids.contains(&instruction_id) {
|
||||
continue;
|
||||
}
|
||||
let accounts = parse_instruction_accounts_vec(instruction.accounts_json.as_str());
|
||||
let payload = build_meteora_instruction_audit_payload(
|
||||
transaction,
|
||||
instruction,
|
||||
audit_spec.protocol_name,
|
||||
audit_spec.event_kind,
|
||||
program_id.as_str(),
|
||||
);
|
||||
let pool_account =
|
||||
candidate_meteora_audit_pool_account(audit_spec, accounts.as_slice());
|
||||
let persist_result = self
|
||||
.materialize_named_dex_event(
|
||||
transaction,
|
||||
transaction_id,
|
||||
instruction_id,
|
||||
audit_spec.protocol_name,
|
||||
program_id.clone(),
|
||||
audit_spec.event_kind,
|
||||
pool_account,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
payload,
|
||||
)
|
||||
.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_pump_fun_events(
|
||||
&self,
|
||||
transaction: &crate::ChainTransactionDto,
|
||||
@@ -1318,6 +1415,13 @@ struct RaydiumInstructionAuditSpec {
|
||||
candidate_pool_account_index: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct MeteoraInstructionAuditSpec {
|
||||
protocol_name: &'static str,
|
||||
event_kind: &'static str,
|
||||
candidate_pool_account_index: std::option::Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct RaydiumMappedNonTradeInstructionSpec {
|
||||
instruction_name: &'static str,
|
||||
@@ -1624,6 +1728,94 @@ fn read_u128_le_from_bytes(data: &[u8], offset: usize) -> std::option::Option<u1
|
||||
return Some(u128::from_le_bytes(bytes));
|
||||
}
|
||||
|
||||
fn meteora_instruction_audit_spec(
|
||||
program_id: &str,
|
||||
) -> std::option::Option<MeteoraInstructionAuditSpec> {
|
||||
if program_id == crate::METEORA_DBC_PROGRAM_ID {
|
||||
return Some(MeteoraInstructionAuditSpec {
|
||||
protocol_name: "meteora_dbc",
|
||||
event_kind: "meteora_dbc.instruction_audit",
|
||||
candidate_pool_account_index: Some(1),
|
||||
});
|
||||
}
|
||||
if program_id == crate::METEORA_DLMM_PROGRAM_ID {
|
||||
return Some(MeteoraInstructionAuditSpec {
|
||||
protocol_name: "meteora_dlmm",
|
||||
event_kind: "meteora_dlmm.instruction_audit",
|
||||
candidate_pool_account_index: Some(0),
|
||||
});
|
||||
}
|
||||
if program_id == crate::METEORA_DAMM_V1_PROGRAM_ID {
|
||||
return Some(MeteoraInstructionAuditSpec {
|
||||
protocol_name: "meteora_damm_v1",
|
||||
event_kind: "meteora_damm_v1.instruction_audit",
|
||||
candidate_pool_account_index: Some(0),
|
||||
});
|
||||
}
|
||||
if program_id == crate::METEORA_DAMM_V2_PROGRAM_ID {
|
||||
return Some(MeteoraInstructionAuditSpec {
|
||||
protocol_name: "meteora_damm_v2",
|
||||
event_kind: "meteora_damm_v2.instruction_audit",
|
||||
candidate_pool_account_index: Some(1),
|
||||
});
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn candidate_meteora_audit_pool_account(
|
||||
audit_spec: MeteoraInstructionAuditSpec,
|
||||
accounts: &[std::string::String],
|
||||
) -> std::option::Option<std::string::String> {
|
||||
let index = match audit_spec.candidate_pool_account_index {
|
||||
Some(index) => index,
|
||||
None => return None,
|
||||
};
|
||||
return accounts.get(index).cloned();
|
||||
}
|
||||
|
||||
fn build_meteora_instruction_audit_payload(
|
||||
transaction: &crate::ChainTransactionDto,
|
||||
instruction: &crate::ChainInstructionDto,
|
||||
protocol_name: &str,
|
||||
event_kind: &str,
|
||||
program_id: &str,
|
||||
) -> serde_json::Value {
|
||||
let accounts = parse_instruction_accounts_value(instruction.accounts_json.as_str());
|
||||
let account_count = match accounts.as_array() {
|
||||
Some(items) => items.len(),
|
||||
None => 0,
|
||||
};
|
||||
let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref());
|
||||
let discriminator_hex = discriminator_hex_from_base58(data_base58.as_deref());
|
||||
let data_prefix = data_base58
|
||||
.as_ref()
|
||||
.map(|value| return value.chars().take(16).collect::<std::string::String>());
|
||||
return serde_json::json!({
|
||||
"decoder": protocol_name,
|
||||
"eventKind": event_kind,
|
||||
"signature": transaction.signature,
|
||||
"instructionId": instruction.id,
|
||||
"instructionIndex": instruction.instruction_index,
|
||||
"innerInstructionIndex": instruction.inner_instruction_index,
|
||||
"innerInstruction": instruction.inner_instruction_index.is_some(),
|
||||
"parentInstructionId": instruction.parent_instruction_id,
|
||||
"programId": program_id,
|
||||
"programFamily": "meteora",
|
||||
"accounts": accounts,
|
||||
"accountCount": account_count,
|
||||
"data": data_base58,
|
||||
"dataPrefix": data_prefix,
|
||||
"discriminatorHex": discriminator_hex,
|
||||
"auditReason": "meteora_instruction_not_decoded_by_specific_decoder",
|
||||
"proofStatus": "unclassified_local_corpus_instruction",
|
||||
"tradeCandidate": false,
|
||||
"candleCandidate": false,
|
||||
"nonTradeUseful": false,
|
||||
"skipTradeReason": "instruction_audit_only",
|
||||
"skipCandleReason": "instruction_audit_only"
|
||||
});
|
||||
}
|
||||
|
||||
fn raydium_instruction_audit_event_kind_by_protocol(
|
||||
protocol_name: &str,
|
||||
) -> std::option::Option<&'static str> {
|
||||
@@ -2774,7 +2966,6 @@ mod tests {
|
||||
assert_eq!(decrease.pool_account_index, Some(3));
|
||||
assert_eq!(decrease.token_a_mint_index, Some(14));
|
||||
assert_eq!(decrease.token_b_mint_index, Some(15));
|
||||
|
||||
let increase = super::raydium_mapped_non_trade_instruction_spec(
|
||||
"raydium_clmm",
|
||||
Some("851d59df45eeb00a"),
|
||||
@@ -2800,7 +2991,6 @@ mod tests {
|
||||
None => panic!("collect_creator_fee discriminator must be mapped"),
|
||||
};
|
||||
assert_eq!(collect_creator_fee.event_kind, "raydium_cpmm.collect_creator_fee");
|
||||
|
||||
let withdraw = super::raydium_mapped_non_trade_instruction_spec(
|
||||
"raydium_cpmm",
|
||||
Some("b712469c946da122"),
|
||||
@@ -2811,7 +3001,6 @@ mod tests {
|
||||
None => panic!("withdraw discriminator must be mapped"),
|
||||
};
|
||||
assert_eq!(withdraw.event_kind, "raydium_cpmm.withdraw");
|
||||
|
||||
let initialize = super::raydium_mapped_non_trade_instruction_spec(
|
||||
"raydium_cpmm",
|
||||
Some("afaf6d1f0d989bed"),
|
||||
|
||||
@@ -375,7 +375,7 @@ impl DexDetectService {
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
) -> Result<crate::DexPoolDetectionResult, crate::Error> {
|
||||
return self
|
||||
.detect_materialized_pool_from_decoded_event(
|
||||
.detect_materialized_pool_from_decoded_event_with_payload_vaults(
|
||||
transaction,
|
||||
decoded_event,
|
||||
"meteora_dbc",
|
||||
@@ -446,7 +446,7 @@ impl DexDetectService {
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
) -> Result<crate::DexPoolDetectionResult, crate::Error> {
|
||||
return self
|
||||
.detect_materialized_pool_from_decoded_event(
|
||||
.detect_materialized_pool_from_decoded_event_with_payload_vaults(
|
||||
transaction,
|
||||
decoded_event,
|
||||
"meteora_damm_v1",
|
||||
@@ -463,7 +463,7 @@ impl DexDetectService {
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
) -> Result<crate::DexPoolDetectionResult, crate::Error> {
|
||||
return self
|
||||
.detect_materialized_pool_from_decoded_event(
|
||||
.detect_materialized_pool_from_decoded_event_with_payload_vaults(
|
||||
transaction,
|
||||
decoded_event,
|
||||
"meteora_damm_v2",
|
||||
@@ -743,6 +743,64 @@ impl DexDetectService {
|
||||
return Ok(detection_result);
|
||||
}
|
||||
|
||||
async fn detect_materialized_pool_from_decoded_event_with_payload_vaults(
|
||||
&self,
|
||||
transaction: &crate::ChainTransactionDto,
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
dex_code: &str,
|
||||
pool_kind: crate::PoolKind,
|
||||
pool_status: crate::PoolStatus,
|
||||
signal_prefix: &str,
|
||||
) -> Result<crate::DexPoolDetectionResult, crate::Error> {
|
||||
let dex_id_result =
|
||||
crate::dex_catalog::ensure_known_dex(self.database.as_ref(), dex_code).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 = extract_payload_string_field(&payload_value, "baseVault");
|
||||
let quote_vault = extract_payload_string_field(&payload_value, "quoteVault");
|
||||
let input_result =
|
||||
crate::dex_pool_materialization::DexPoolMaterializationInput::from_decoded_event(
|
||||
decoded_event,
|
||||
dex_id,
|
||||
pool_kind,
|
||||
pool_status,
|
||||
crate::dex_pool_materialization::DexPoolTokenOrder::ChooseBaseQuoteFromTokenAB,
|
||||
base_vault,
|
||||
quote_vault,
|
||||
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_prefix,
|
||||
&detection_result,
|
||||
payload_value,
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = signal_result {
|
||||
return Err(error);
|
||||
}
|
||||
return Ok(detection_result);
|
||||
}
|
||||
|
||||
async fn record_detection_signal(
|
||||
&self,
|
||||
transaction: &crate::ChainTransactionDto,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -61,6 +61,8 @@ mod local_pipeline_diagnostics;
|
||||
mod local_pipeline_replay;
|
||||
/// Local pipeline validation helpers for non-regression runs.
|
||||
mod local_pipeline_validation;
|
||||
/// Meteora swap amount inference from flattened CPI token transfers.
|
||||
mod meteora_swap_amount_inference;
|
||||
/// Useful non-trade DEX event materialization service.
|
||||
mod non_trade_event_materialization;
|
||||
/// On-chain DEX pair/pool discovery helpers used by Demo3.
|
||||
@@ -182,6 +184,20 @@ pub use constants::JUP_MINT_ID;
|
||||
/// Loader V4 program identifier. ("LoaderV411111111111111111111111111111111111").
|
||||
/// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::loader_v4::ID
|
||||
pub use constants::LOADER_V4_PROGRAM_ID;
|
||||
/// MetaDAO AMM v0.5.0 program id.
|
||||
pub use constants::METADAO_AMM_V0_5_0_PROGRAM_ID;
|
||||
/// MetaDAO Bid Wall v0.7.0 program id.
|
||||
pub use constants::METADAO_BID_WALL_V0_7_0_PROGRAM_ID;
|
||||
/// MetaDAO Futarchy v0.6.0 program id.
|
||||
pub use constants::METADAO_FUTARCHY_V0_6_0_PROGRAM_ID;
|
||||
/// MetaDAO Launchpad v0.7.0 program id.
|
||||
pub use constants::METADAO_LAUNCHPAD_V0_7_0_PROGRAM_ID;
|
||||
/// MetaDAO META active token mint identifier.
|
||||
pub use constants::METADAO_META_MINT_ID;
|
||||
/// MetaDAO METAC legacy token mint identifier.
|
||||
pub use constants::METADAO_METAC_LEGACY_MINT_ID;
|
||||
/// MetaDAO-linked P2P token mint candidate.
|
||||
pub use constants::METADAO_P2P_MINT_ID;
|
||||
/// Meteora DAMM v1 program id. ("Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB").
|
||||
pub use constants::METEORA_DAMM_V1_PROGRAM_ID;
|
||||
/// Meteora DAMM v2 program id. ("cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG").
|
||||
@@ -195,6 +211,8 @@ pub use constants::METEORA_DLMM_PROGRAM_ID;
|
||||
pub use constants::NATIVE_LOADER_PROGRAM_ID;
|
||||
/// Orca Whirlpools program id. ("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc").
|
||||
pub use constants::ORCA_WHIRLPOOLS_PROGRAM_ID;
|
||||
/// Printr program id candidate observed on Solscan.
|
||||
pub use constants::PRINTR_PROGRAM_ID;
|
||||
/// Pump.fun program id. ("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P").
|
||||
pub use constants::PUMP_FUN_PROGRAM_ID;
|
||||
/// PumpSwap / PumpAMM program id. ("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA").
|
||||
@@ -289,6 +307,8 @@ pub use constants::ZK_ELGAMAL_PROOF_PROGRAM_ID;
|
||||
/// Zk Token Proof program identifier. ("ZkTokenProof1111111111111111111111111111111").
|
||||
/// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::zk_token_proof_program::ID
|
||||
pub use constants::ZK_TOKEN_PROOF_PROGRAM_ID;
|
||||
/// Zora program id candidate observed on Solscan.
|
||||
pub use constants::ZORA_PROGRAM_ID;
|
||||
/// Application-facing analysis signal DTO.
|
||||
pub use db::AnalysisSignalDto;
|
||||
/// Persisted analysis signal row.
|
||||
@@ -1114,6 +1134,10 @@ pub use local_pipeline_validation::LocalPipelineValidationRunDto;
|
||||
pub use local_pipeline_validation::LocalPipelineValidationService;
|
||||
/// Validates a diagnostics summary without performing database access.
|
||||
pub use local_pipeline_validation::validate_local_pipeline_diagnostics_summary;
|
||||
/// Result of non-trade event materialization for one transaction.
|
||||
pub use non_trade_event_materialization::NonTradeEventMaterializationResult;
|
||||
/// Materializes useful non-trade decoded DEX events.
|
||||
pub use non_trade_event_materialization::NonTradeEventMaterializationService;
|
||||
/// Candidate account inferred from generic transaction evidence.
|
||||
pub use onchain_dex_pair_discovery::OnchainDexCandidateAccountDto;
|
||||
/// Candidate transaction/instruction observed on-chain for one DEX program id.
|
||||
@@ -1124,6 +1148,8 @@ pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryRequestDto;
|
||||
pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryResultDto;
|
||||
/// On-chain pair/pool discovery service.
|
||||
pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryService;
|
||||
/// Rejected on-chain DEX candidate summary DTO.
|
||||
pub use onchain_dex_pair_discovery::OnchainDexRejectedCandidateSummaryDto;
|
||||
/// Token-balance delta observed in one transaction through Solana transaction metadata.
|
||||
pub use onchain_dex_pair_discovery::OnchainDexTokenBalanceDeltaDto;
|
||||
/// One pair-analytic-signal recording result.
|
||||
@@ -1248,8 +1274,3 @@ pub use ws_manager::WsManagedEndpointSnapshot;
|
||||
pub use ws_manager::WsManager;
|
||||
/// Snapshot of the whole manager state.
|
||||
pub use ws_manager::WsManagerSnapshot;
|
||||
|
||||
/// Result of non-trade event materialization for one transaction.
|
||||
pub use non_trade_event_materialization::NonTradeEventMaterializationResult;
|
||||
/// Materializes useful non-trade decoded DEX events.
|
||||
pub use non_trade_event_materialization::NonTradeEventMaterializationService;
|
||||
|
||||
@@ -344,6 +344,30 @@ impl LocalPipelineValidationConfig {
|
||||
return config;
|
||||
}
|
||||
|
||||
/// Builds the `0.7.43` Meteora effective-surface validation config.
|
||||
///
|
||||
/// This profile restarts from the established Meteora-family validation but
|
||||
/// keeps missing variants as warnings while a fresh corpus is being built.
|
||||
/// It is intentionally strict on global trade/candle invariants: successful
|
||||
/// trade candidates still need materialized trades, failed transactions stay
|
||||
/// non-actionable, and non-trade Meteora events must not feed trades/candles.
|
||||
pub fn v0_7_43_meteora_effective_surfaces() -> Self {
|
||||
let mut config = Self::v0_7_36_meteora_family_consolidation();
|
||||
config.profile_code = "0.7.43_meteora_effective_surfaces".to_string();
|
||||
config.expected_dex_codes = vec![
|
||||
"meteora_dbc".to_string(),
|
||||
"meteora_damm_v1".to_string(),
|
||||
"meteora_damm_v2".to_string(),
|
||||
"meteora_dlmm".to_string(),
|
||||
];
|
||||
config.require_all_expected_dexes = false;
|
||||
config.allow_unexpected_dexes = true;
|
||||
config.require_trade_events_per_dex = false;
|
||||
config.require_candles_per_dex = false;
|
||||
config.require_pair_trading_readiness_semantics = false;
|
||||
return config;
|
||||
}
|
||||
|
||||
/// Builds the legacy `0.7.39` launch-surface validation alias.
|
||||
///
|
||||
/// The implementation now delegates to the DEX-first profile so callers that
|
||||
@@ -474,7 +498,9 @@ impl LocalPipelineValidationService {
|
||||
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
|
||||
let diagnostics_service =
|
||||
crate::LocalPipelineDiagnosticsService::new(self.database.clone());
|
||||
let summary_result = if config.profile_code == "0.7.42_raydium_family_event_coverage" {
|
||||
let summary_result = if config.profile_code == "0.7.42_raydium_family_event_coverage"
|
||||
|| config.profile_code == "0.7.43_meteora_effective_surfaces"
|
||||
{
|
||||
diagnostics_service.diagnose_for_validation().await
|
||||
} else {
|
||||
diagnostics_service.diagnose().await
|
||||
@@ -617,6 +643,14 @@ impl LocalPipelineValidationService {
|
||||
let config = crate::LocalPipelineValidationConfig::v0_7_42_raydium_family_event_coverage();
|
||||
return self.validate_current_database(&config).await;
|
||||
}
|
||||
|
||||
/// Diagnoses the current database with the `0.7.43` Meteora effective-surface profile.
|
||||
pub async fn validate_v0_7_43_current_database(
|
||||
&self,
|
||||
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
|
||||
let config = crate::LocalPipelineValidationConfig::v0_7_43_meteora_effective_surfaces();
|
||||
return self.validate_current_database(&config).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates a diagnostics summary without performing database access.
|
||||
@@ -743,7 +777,8 @@ pub fn validate_local_pipeline_diagnostics_summary(
|
||||
|| 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.41_raydium_amm_v4_swap_decoder";
|
||||
|| config.profile_code == "0.7.41_raydium_amm_v4_swap_decoder"
|
||||
|| config.profile_code == "0.7.43_meteora_effective_surfaces";
|
||||
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) {
|
||||
@@ -1022,8 +1057,7 @@ fn validate_dex_support_matrix_semantics(
|
||||
});
|
||||
}
|
||||
if entry.surface_role == "to_verify"
|
||||
&& (entry.program_id.is_some()
|
||||
|| entry.program_id_status != "to_verify"
|
||||
&& (entry.program_id_status != "to_verify"
|
||||
|| entry.catalog_enabled
|
||||
|| entry.decoded
|
||||
|| entry.materialized
|
||||
@@ -1033,7 +1067,7 @@ fn validate_dex_support_matrix_semantics(
|
||||
issues.push(crate::LocalPipelineValidationIssueDto {
|
||||
code: "to_verify_matrix_entry_promoted_without_proof".to_string(),
|
||||
message: format!(
|
||||
"to-verify surface '{}' must not expose program id, catalog activation, decoding or trade/candle materialization without corpus proof",
|
||||
"to-verify surface '{}' must not expose catalog activation, decoding or trade/candle materialization without corpus proof",
|
||||
entry_code
|
||||
),
|
||||
subject: Some(entry_code.clone()),
|
||||
@@ -1599,6 +1633,31 @@ mod tests {
|
||||
assert!(report.expected_dex_codes.contains(&"raydium_amm_v4".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_accepts_0_7_43_meteora_effective_surfaces_partial_corpus() {
|
||||
let mut summary = make_0_7_28_summary_with_meteora();
|
||||
summary.dex_summaries.retain(|dex_summary| {
|
||||
return dex_summary.dex_code == "meteora_dlmm"
|
||||
|| dex_summary.dex_code == "meteora_damm_v1";
|
||||
});
|
||||
summary.decoded_non_trade_useful_event_count = 7;
|
||||
summary.liquidity_event_count = 4;
|
||||
summary.pool_lifecycle_event_count = 1;
|
||||
summary.fee_event_count = 2;
|
||||
let config = crate::LocalPipelineValidationConfig::v0_7_43_meteora_effective_surfaces();
|
||||
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
|
||||
assert!(report.validation_passed);
|
||||
assert_eq!(report.validation_profile_code, "0.7.43_meteora_effective_surfaces");
|
||||
assert_eq!(report.blocking_issue_count, 0);
|
||||
assert!(report.warning_count >= 2);
|
||||
assert!(report.expected_dex_codes.contains(&"meteora_dbc".to_string()));
|
||||
assert!(report.expected_dex_codes.contains(&"meteora_damm_v1".to_string()));
|
||||
assert!(report.expected_dex_codes.contains(&"meteora_damm_v2".to_string()));
|
||||
assert!(report.expected_dex_codes.contains(&"meteora_dlmm".to_string()));
|
||||
assert_eq!(report.liquidity_event_count, 4);
|
||||
assert_eq!(report.fee_event_count, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_rejects_0_7_33_pair_trading_readiness_mismatch() {
|
||||
let mut summary = make_0_7_28_summary_with_meteora();
|
||||
|
||||
1008
kb_lib/src/meteora_swap_amount_inference.rs
Normal file
1008
kb_lib/src/meteora_swap_amount_inference.rs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user