This commit is contained in:
2026-05-20 23:57:15 +02:00
parent fad7ec5107
commit 62831a0abe
56 changed files with 6603 additions and 114 deletions

View File

@@ -31,6 +31,12 @@ pub use dtos::LaunchSurfaceDto;
pub use dtos::LaunchSurfaceKeyDto;
pub use dtos::LiquidityEventDto;
pub use dtos::LocalDecodedEventDiagnosticSummaryDto;
pub use dtos::LocalDexCorpusDecodedEventSampleDto;
pub use dtos::LocalDexCorpusPoolPairSampleDto;
pub use dtos::LocalDexCorpusSearchRequestDto;
pub use dtos::LocalDexCorpusSearchResultDto;
pub use dtos::LocalDexCorpusSearchSummaryDto;
pub use dtos::LocalDexCorpusTransactionSampleDto;
pub use dtos::LocalDexDiagnosticSummaryDto;
pub use dtos::LocalDuplicateDecodedEventTradeDiagnosticSampleDto;
pub use dtos::LocalEventClassificationDiagnosticSummaryDto;
@@ -46,6 +52,8 @@ pub use dtos::LocalPairTradingReadinessDiagnosticSummaryDto;
pub use dtos::LocalPipelineDiagnosticCountersDto;
pub use dtos::LocalPipelineDiagnosticSummaryDto;
pub use dtos::LocalPoolOriginDiagnosticSampleDto;
pub use dtos::LocalRaydiumProgramInstructionDiagnosticSummaryDto;
pub use dtos::LocalRaydiumSurfaceDiagnosticSummaryDto;
pub use dtos::LocalTokenMetadataGapDiagnosticSampleDto;
pub use dtos::ObservedTokenDto;
pub use dtos::OnchainObservationDto;
@@ -160,6 +168,10 @@ pub use queries::query_launch_surfaces_upsert;
pub use queries::query_liquidity_events_list_recent;
pub use queries::query_liquidity_events_upsert;
pub use queries::query_local_decoded_event_diagnostic_list_summaries;
pub use queries::query_local_dex_corpus_search_get_summary;
pub use queries::query_local_dex_corpus_search_list_decoded_event_samples;
pub use queries::query_local_dex_corpus_search_list_pool_pair_samples;
pub use queries::query_local_dex_corpus_search_list_transaction_samples;
pub use queries::query_local_duplicate_decoded_event_trade_diagnostic_list_samples;
pub use queries::query_local_event_classification_diagnostic_list_summaries;
pub use queries::query_local_launch_origin_diagnostic_list_samples;
@@ -175,6 +187,7 @@ pub use queries::query_local_pair_without_trade_diagnostic_list_samples;
pub use queries::query_local_pipeline_diagnostic_get_counters;
pub use queries::query_local_pipeline_diagnostic_list_summaries;
pub use queries::query_local_pool_origin_diagnostic_list_samples;
pub use queries::query_local_raydium_program_instruction_diagnostic_list_summaries;
pub use queries::query_local_token_metadata_gap_diagnostic_list_samples;
pub use queries::query_observed_tokens_get_by_mint;
pub use queries::query_observed_tokens_list;

View File

@@ -17,6 +17,7 @@ mod launch_attribution;
mod launch_surface;
mod launch_surface_key;
mod liquidity_event;
mod local_dex_corpus_search;
mod local_pipeline_diagnostics;
mod observed_token;
mod onchain_observation;
@@ -45,6 +46,10 @@ mod wallet;
mod wallet_holding;
mod wallet_participation;
pub(crate) use local_dex_corpus_search::LocalDexCorpusDecodedEventSampleRow;
pub(crate) use local_dex_corpus_search::LocalDexCorpusPoolPairSampleRow;
pub(crate) use local_dex_corpus_search::LocalDexCorpusSearchSummaryRow;
pub(crate) use local_dex_corpus_search::LocalDexCorpusTransactionSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalDecodedEventDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalDexDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalDuplicateDecodedEventTradeDiagnosticSampleRow;
@@ -60,6 +65,7 @@ pub(crate) use local_pipeline_diagnostics::LocalPairGapDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalPairTradingReadinessDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPipelineDiagnosticCountersRow;
pub(crate) use local_pipeline_diagnostics::LocalPoolOriginDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalRaydiumProgramInstructionDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalTokenMetadataGapDiagnosticSampleRow;
pub use analysis_signal::AnalysisSignalDto;
@@ -77,6 +83,12 @@ pub use launch_attribution::LaunchAttributionDto;
pub use launch_surface::LaunchSurfaceDto;
pub use launch_surface_key::LaunchSurfaceKeyDto;
pub use liquidity_event::LiquidityEventDto;
pub use local_dex_corpus_search::LocalDexCorpusDecodedEventSampleDto;
pub use local_dex_corpus_search::LocalDexCorpusPoolPairSampleDto;
pub use local_dex_corpus_search::LocalDexCorpusSearchRequestDto;
pub use local_dex_corpus_search::LocalDexCorpusSearchResultDto;
pub use local_dex_corpus_search::LocalDexCorpusSearchSummaryDto;
pub use local_dex_corpus_search::LocalDexCorpusTransactionSampleDto;
pub use local_pipeline_diagnostics::LocalDecodedEventDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalDexDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalDuplicateDecodedEventTradeDiagnosticSampleDto;
@@ -93,6 +105,8 @@ pub use local_pipeline_diagnostics::LocalPairTradingReadinessDiagnosticSummaryDt
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticCountersDto;
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPoolOriginDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalRaydiumProgramInstructionDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalRaydiumSurfaceDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalTokenMetadataGapDiagnosticSampleDto;
pub use observed_token::ObservedTokenDto;
pub use onchain_observation::OnchainObservationDto;

View File

@@ -0,0 +1,281 @@
// file: kb_lib/src/db/dtos/local_dex_corpus_search.rs
//! DTOs for local DEX corpus searches used by Demo3.
/// Search request for local DEX corpus discovery.
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct LocalDexCorpusSearchRequestDto {
/// Optional DEX code or decoded protocol name.
pub dex_code: std::option::Option<std::string::String>,
/// Optional Solana program id.
pub program_id: std::option::Option<std::string::String>,
/// Optional local pair id.
pub pair_id: std::option::Option<i64>,
/// Optional pool account/address.
pub pool_address: std::option::Option<std::string::String>,
/// Optional token mint to match as base, quote or decoded mint.
pub token_mint: std::option::Option<std::string::String>,
/// Optional transaction signature.
pub signature: std::option::Option<std::string::String>,
/// Maximum number of rows to return per sample category.
pub limit: u32,
}
/// Result of a local DEX corpus search.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalDexCorpusSearchResultDto {
/// Normalized request used for the search.
pub request: crate::LocalDexCorpusSearchRequestDto,
/// Aggregate counts for matching local data.
pub summary: crate::LocalDexCorpusSearchSummaryDto,
/// Matching transaction samples.
pub transaction_samples: std::vec::Vec<crate::LocalDexCorpusTransactionSampleDto>,
/// Matching pool/pair samples.
pub pool_pair_samples: std::vec::Vec<crate::LocalDexCorpusPoolPairSampleDto>,
/// Matching decoded event samples.
pub decoded_event_samples: std::vec::Vec<crate::LocalDexCorpusDecodedEventSampleDto>,
}
/// Aggregate counts for a local DEX corpus search.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalDexCorpusSearchSummaryDto {
/// Number of distinct matching transactions.
pub transaction_count: i64,
/// Number of distinct matching instructions.
pub instruction_count: i64,
/// Number of distinct matching decoded DEX events.
pub decoded_event_count: i64,
/// Number of distinct matching pools.
pub pool_count: i64,
/// Number of distinct matching pairs.
pub pair_count: i64,
/// Number of distinct matching trade events.
pub trade_event_count: i64,
/// Number of distinct matching candle rows.
pub pair_candle_count: i64,
/// Number of distinct matching protocol-candidate rows.
pub protocol_candidate_count: i64,
}
/// Matching transaction sample for corpus discovery.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalDexCorpusTransactionSampleDto {
/// Transaction id.
pub transaction_id: i64,
/// Transaction signature.
pub signature: std::string::String,
/// Optional Solana slot.
pub slot: std::option::Option<i64>,
/// Whether the transaction has a non-null error payload.
pub failed: bool,
/// Number of persisted instructions for the transaction.
pub instruction_count: i64,
/// Number of persisted decoded DEX events for the transaction.
pub decoded_event_count: i64,
/// Number of persisted trade events for the transaction.
pub trade_event_count: i64,
/// Comma-separated distinct program ids seen in the transaction.
pub program_ids_csv: std::string::String,
}
/// Matching pool/pair sample for corpus discovery.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalDexCorpusPoolPairSampleDto {
/// Optional pool id.
pub pool_id: std::option::Option<i64>,
/// Optional pool address.
pub pool_address: std::option::Option<std::string::String>,
/// Optional pair id.
pub pair_id: std::option::Option<i64>,
/// Optional DEX code.
pub dex_code: std::option::Option<std::string::String>,
/// Optional pair symbol.
pub pair_symbol: std::option::Option<std::string::String>,
/// Optional base token mint.
pub base_mint: std::option::Option<std::string::String>,
/// Optional base token symbol.
pub base_symbol: std::option::Option<std::string::String>,
/// Optional quote token mint.
pub quote_mint: std::option::Option<std::string::String>,
/// Optional quote token symbol.
pub quote_symbol: std::option::Option<std::string::String>,
/// Number of decoded events attached to the pool.
pub decoded_event_count: i64,
/// Number of trade events attached to the pair.
pub trade_event_count: i64,
/// Number of candle rows attached to the pair.
pub pair_candle_count: i64,
/// Latest known founding or activity signature for the pool/pair.
pub latest_signature: std::option::Option<std::string::String>,
}
/// Matching decoded event sample for corpus discovery.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalDexCorpusDecodedEventSampleDto {
/// Decoded event id.
pub decoded_event_id: i64,
/// Transaction id.
pub transaction_id: i64,
/// Transaction signature.
pub signature: std::string::String,
/// Optional Solana slot.
pub slot: std::option::Option<i64>,
/// Protocol name stored on the decoded event.
pub protocol_name: std::string::String,
/// Program id stored on the decoded event.
pub program_id: std::string::String,
/// Event kind.
pub event_kind: std::string::String,
/// Optional pool account.
pub pool_account: std::option::Option<std::string::String>,
/// Optional token A mint.
pub token_a_mint: std::option::Option<std::string::String>,
/// Optional token B mint.
pub token_b_mint: std::option::Option<std::string::String>,
/// Decoded event category extracted from payload JSON.
pub event_category: std::option::Option<std::string::String>,
/// Decoded event lifecycle kind extracted from payload JSON.
pub event_lifecycle_kind: std::option::Option<std::string::String>,
/// Decoded event actionability extracted from payload JSON.
pub event_actionability: std::option::Option<std::string::String>,
/// Whether the decoded event is a trade candidate.
pub trade_candidate: bool,
/// Whether the decoded event is a candle candidate.
pub candle_candidate: bool,
}
/// SQL row for aggregate counts of a local DEX corpus search.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalDexCorpusSearchSummaryRow {
pub(crate) transaction_count: i64,
pub(crate) instruction_count: i64,
pub(crate) decoded_event_count: i64,
pub(crate) pool_count: i64,
pub(crate) pair_count: i64,
pub(crate) trade_event_count: i64,
pub(crate) pair_candle_count: i64,
pub(crate) protocol_candidate_count: i64,
}
/// SQL row for a matching transaction sample.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalDexCorpusTransactionSampleRow {
pub(crate) transaction_id: i64,
pub(crate) signature: std::string::String,
pub(crate) slot: std::option::Option<i64>,
pub(crate) failed: i64,
pub(crate) instruction_count: i64,
pub(crate) decoded_event_count: i64,
pub(crate) trade_event_count: i64,
pub(crate) program_ids_csv: std::string::String,
}
/// SQL row for a matching pool/pair sample.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalDexCorpusPoolPairSampleRow {
pub(crate) pool_id: std::option::Option<i64>,
pub(crate) pool_address: std::option::Option<std::string::String>,
pub(crate) pair_id: std::option::Option<i64>,
pub(crate) dex_code: std::option::Option<std::string::String>,
pub(crate) pair_symbol: std::option::Option<std::string::String>,
pub(crate) base_mint: std::option::Option<std::string::String>,
pub(crate) base_symbol: std::option::Option<std::string::String>,
pub(crate) quote_mint: std::option::Option<std::string::String>,
pub(crate) quote_symbol: std::option::Option<std::string::String>,
pub(crate) decoded_event_count: i64,
pub(crate) trade_event_count: i64,
pub(crate) pair_candle_count: i64,
pub(crate) latest_signature: std::option::Option<std::string::String>,
}
/// SQL row for a matching decoded event sample.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalDexCorpusDecodedEventSampleRow {
pub(crate) decoded_event_id: i64,
pub(crate) transaction_id: i64,
pub(crate) signature: std::string::String,
pub(crate) slot: std::option::Option<i64>,
pub(crate) protocol_name: std::string::String,
pub(crate) program_id: std::string::String,
pub(crate) event_kind: std::string::String,
pub(crate) pool_account: std::option::Option<std::string::String>,
pub(crate) token_a_mint: std::option::Option<std::string::String>,
pub(crate) token_b_mint: std::option::Option<std::string::String>,
pub(crate) event_category: std::option::Option<std::string::String>,
pub(crate) event_lifecycle_kind: std::option::Option<std::string::String>,
pub(crate) event_actionability: std::option::Option<std::string::String>,
pub(crate) trade_candidate: std::option::Option<i64>,
pub(crate) candle_candidate: std::option::Option<i64>,
}
impl From<LocalDexCorpusSearchSummaryRow> for LocalDexCorpusSearchSummaryDto {
fn from(row: LocalDexCorpusSearchSummaryRow) -> Self {
return Self {
transaction_count: row.transaction_count,
instruction_count: row.instruction_count,
decoded_event_count: row.decoded_event_count,
pool_count: row.pool_count,
pair_count: row.pair_count,
trade_event_count: row.trade_event_count,
pair_candle_count: row.pair_candle_count,
protocol_candidate_count: row.protocol_candidate_count,
};
}
}
impl From<LocalDexCorpusTransactionSampleRow> for LocalDexCorpusTransactionSampleDto {
fn from(row: LocalDexCorpusTransactionSampleRow) -> Self {
return Self {
transaction_id: row.transaction_id,
signature: row.signature,
slot: row.slot,
failed: row.failed != 0,
instruction_count: row.instruction_count,
decoded_event_count: row.decoded_event_count,
trade_event_count: row.trade_event_count,
program_ids_csv: row.program_ids_csv,
};
}
}
impl From<LocalDexCorpusPoolPairSampleRow> for LocalDexCorpusPoolPairSampleDto {
fn from(row: LocalDexCorpusPoolPairSampleRow) -> Self {
return Self {
pool_id: row.pool_id,
pool_address: row.pool_address,
pair_id: row.pair_id,
dex_code: row.dex_code,
pair_symbol: row.pair_symbol,
base_mint: row.base_mint,
base_symbol: row.base_symbol,
quote_mint: row.quote_mint,
quote_symbol: row.quote_symbol,
decoded_event_count: row.decoded_event_count,
trade_event_count: row.trade_event_count,
pair_candle_count: row.pair_candle_count,
latest_signature: row.latest_signature,
};
}
}
impl From<LocalDexCorpusDecodedEventSampleRow> for LocalDexCorpusDecodedEventSampleDto {
fn from(row: LocalDexCorpusDecodedEventSampleRow) -> Self {
return Self {
decoded_event_id: row.decoded_event_id,
transaction_id: row.transaction_id,
signature: row.signature,
slot: row.slot,
protocol_name: row.protocol_name,
program_id: row.program_id,
event_kind: row.event_kind,
pool_account: row.pool_account,
token_a_mint: row.token_a_mint,
token_b_mint: row.token_b_mint,
event_category: row.event_category,
event_lifecycle_kind: row.event_lifecycle_kind,
event_actionability: row.event_actionability,
trade_candidate: row.trade_candidate.unwrap_or(0) != 0,
candle_candidate: row.candle_candidate.unwrap_or(0) != 0,
};
}
}

View File

@@ -122,6 +122,9 @@ pub struct LocalPipelineDiagnosticSummaryDto {
pub pair_without_candle_count: i64,
/// Diagnostics grouped by DEX.
pub dex_summaries: std::vec::Vec<crate::LocalDexDiagnosticSummaryDto>,
/// Raydium surface diagnostics derived from the matrix and observed instructions.
pub raydium_surface_summaries:
std::vec::Vec<crate::LocalRaydiumSurfaceDiagnosticSummaryDto>,
/// Diagnostics grouped by pair.
pub pair_summaries: std::vec::Vec<crate::LocalPairDiagnosticSummaryDto>,
/// Diagnostics grouped by pair materialization/actionability class.
@@ -185,6 +188,60 @@ pub struct LocalDexDiagnosticSummaryDto {
pub pair_candle_count: i64,
}
/// Local diagnostics for one Raydium surface from the DEX-first matrix.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalRaydiumSurfaceDiagnosticSummaryDto {
/// DEX matrix code, such as `raydium_clmm` or `raydium_amm_v4`.
pub dex_code: std::string::String,
/// Human readable surface name.
pub display_name: std::string::String,
/// DEX-first role attached to this surface.
pub surface_role: std::string::String,
/// Program id from the support matrix, when known.
pub program_id: std::option::Option<std::string::String>,
/// Program id verification status from the support matrix.
pub program_id_status: std::string::String,
/// Support status from the support matrix.
pub status: std::string::String,
/// Whether this surface is enabled in the local DEX catalog.
pub catalog_enabled: bool,
/// Total projected instructions for this program in the current database.
pub instruction_count: i64,
/// Total distinct projected transactions for this program in the current database.
pub transaction_count: i64,
/// Total decoded events for this DEX code.
pub decoded_event_count: i64,
/// Total materialized trade events for this DEX code.
pub trade_event_count: i64,
/// Total candle buckets for this DEX code.
pub pair_candle_count: i64,
/// Latest slot where this program was observed, when present.
pub latest_slot: std::option::Option<i64>,
/// Latest signature where this program was observed, when present.
pub latest_signature: std::option::Option<std::string::String>,
/// Whether projected instructions prove this program exists in the current corpus.
pub observed_in_current_corpus: bool,
/// Whether this DEX code has decoded events in the current corpus.
pub decoded_in_current_corpus: bool,
/// Whether this DEX code has materialized trades in the current corpus.
pub trade_materialized_in_current_corpus: bool,
}
/// Projected instruction diagnostics grouped by Raydium program id.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalRaydiumProgramInstructionDiagnosticSummaryDto {
/// Program id.
pub program_id: std::string::String,
/// Total projected instruction rows.
pub instruction_count: i64,
/// Total distinct transactions.
pub transaction_count: i64,
/// Latest slot where the program appears, when present.
pub latest_slot: std::option::Option<i64>,
/// Latest signature where the program appears, when present.
pub latest_signature: std::option::Option<std::string::String>,
}
/// Local pair diagnostics summary.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalPairDiagnosticSummaryDto {
@@ -537,6 +594,16 @@ pub(crate) struct LocalDexDiagnosticSummaryRow {
pub(crate) pair_candle_count: i64,
}
/// SQL row for Raydium program instruction diagnostics.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalRaydiumProgramInstructionDiagnosticSummaryRow {
pub(crate) program_id: std::string::String,
pub(crate) instruction_count: i64,
pub(crate) transaction_count: i64,
pub(crate) latest_slot: std::option::Option<i64>,
pub(crate) latest_signature: std::option::Option<std::string::String>,
}
/// SQL row for local pair diagnostics.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalPairDiagnosticSummaryRow {

View File

@@ -17,6 +17,7 @@ mod launch_attribution;
mod launch_surface;
mod launch_surface_key;
mod liquidity_event;
mod local_dex_corpus_search;
mod local_pipeline_diagnostics;
mod observed_token;
mod onchain_observation;
@@ -88,6 +89,10 @@ pub use launch_surface_key::query_launch_surface_keys_list_by_surface_id;
pub use launch_surface_key::query_launch_surface_keys_upsert;
pub use liquidity_event::query_liquidity_events_list_recent;
pub use liquidity_event::query_liquidity_events_upsert;
pub use local_dex_corpus_search::query_local_dex_corpus_search_get_summary;
pub use local_dex_corpus_search::query_local_dex_corpus_search_list_decoded_event_samples;
pub use local_dex_corpus_search::query_local_dex_corpus_search_list_pool_pair_samples;
pub use local_dex_corpus_search::query_local_dex_corpus_search_list_transaction_samples;
pub use local_pipeline_diagnostics::query_local_decoded_event_diagnostic_list_summaries;
pub use local_pipeline_diagnostics::query_local_duplicate_decoded_event_trade_diagnostic_list_samples;
pub use local_pipeline_diagnostics::query_local_event_classification_diagnostic_list_summaries;
@@ -104,6 +109,7 @@ pub use local_pipeline_diagnostics::query_local_pair_without_trade_diagnostic_li
pub use local_pipeline_diagnostics::query_local_pipeline_diagnostic_get_counters;
pub use local_pipeline_diagnostics::query_local_pipeline_diagnostic_list_summaries;
pub use local_pipeline_diagnostics::query_local_pool_origin_diagnostic_list_samples;
pub use local_pipeline_diagnostics::query_local_raydium_program_instruction_diagnostic_list_summaries;
pub use local_pipeline_diagnostics::query_local_token_metadata_gap_diagnostic_list_samples;
pub use observed_token::query_observed_tokens_get_by_mint;
pub use observed_token::query_observed_tokens_list;

View File

@@ -0,0 +1,548 @@
// file: kb_lib/src/db/queries/local_dex_corpus_search.rs
//! Queries for local DEX corpus searches used by Demo3.
/// Returns aggregate counts for a local DEX corpus search.
pub async fn query_local_dex_corpus_search_get_summary(
database: &crate::Database,
request: &crate::LocalDexCorpusSearchRequestDto,
) -> Result<crate::LocalDexCorpusSearchSummaryDto, crate::Error> {
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let dex_code = normalized_filter_text(request.dex_code.as_deref());
let program_id = normalized_filter_text(request.program_id.as_deref());
let pool_address = normalized_filter_text(request.pool_address.as_deref());
let token_mint = normalized_filter_text(request.token_mint.as_deref());
let signature = normalized_filter_text(request.signature.as_deref());
let pool_address_like = like_filter_text(pool_address.as_str());
let token_mint_like = like_filter_text(token_mint.as_str());
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::db::dtos::LocalDexCorpusSearchSummaryRow>(
r#"
SELECT
COUNT(DISTINCT tx.id) AS transaction_count,
COUNT(DISTINCT ix.id) AS instruction_count,
COUNT(DISTINCT dde.id) AS decoded_event_count,
COUNT(DISTINCT pool.id) AS pool_count,
COUNT(DISTINCT pair.id) AS pair_count,
COUNT(DISTINCT te.id) AS trade_event_count,
COUNT(DISTINCT candle.id) AS pair_candle_count,
COUNT(DISTINCT pc.id) AS protocol_candidate_count
FROM k_sol_chain_transactions tx
LEFT JOIN k_sol_chain_instructions ix
ON ix.transaction_id = tx.id
LEFT JOIN k_sol_dex_decoded_events dde
ON dde.transaction_id = tx.id
LEFT JOIN k_sol_pools pool
ON pool.address = dde.pool_account
LEFT JOIN k_sol_pairs pair
ON pair.pool_id = pool.id
LEFT JOIN k_sol_dexes dex
ON dex.id = pair.dex_id
LEFT JOIN k_sol_tokens base_token
ON base_token.id = pair.base_token_id
LEFT JOIN k_sol_tokens quote_token
ON quote_token.id = pair.quote_token_id
LEFT JOIN k_sol_trade_events te
ON te.transaction_id = tx.id
LEFT JOIN k_sol_pair_candles candle
ON candle.pair_id = pair.id
LEFT JOIN k_sol_protocol_candidates pc
ON pc.transaction_id = tx.id
WHERE
(NULLIF(TRIM(?), '') IS NULL
OR dex.code = ?
OR dde.protocol_name = ?
OR pc.candidate_protocol = ?
OR pc.candidate_surface = ?)
AND (NULLIF(TRIM(?), '') IS NULL
OR ix.program_id = ?
OR dde.program_id = ?
OR pc.program_id = ?)
AND (? IS NULL OR pair.id = ?)
AND (NULLIF(TRIM(?), '') IS NULL
OR pool.address = ?
OR dde.pool_account = ?
OR ix.accounts_json LIKE ?
OR pc.evidence_json LIKE ?)
AND (NULLIF(TRIM(?), '') IS NULL
OR base_token.mint = ?
OR quote_token.mint = ?
OR dde.token_a_mint = ?
OR dde.token_b_mint = ?
OR ix.accounts_json LIKE ?
OR tx.transaction_json LIKE ?
OR pc.evidence_json LIKE ?)
AND (NULLIF(TRIM(?), '') IS NULL OR tx.signature = ?)
"#,
)
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(program_id.clone())
.bind(program_id.clone())
.bind(program_id.clone())
.bind(program_id.clone())
.bind(request.pair_id)
.bind(request.pair_id)
.bind(pool_address.clone())
.bind(pool_address.clone())
.bind(pool_address.clone())
.bind(pool_address_like.clone())
.bind(pool_address_like.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint_like.clone())
.bind(token_mint_like.clone())
.bind(token_mint_like.clone())
.bind(signature.clone())
.bind(signature.clone())
.fetch_one(pool)
.await;
let row = match query_result {
Ok(row) => row,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot query local DEX corpus search summary on sqlite: {}",
error
)));
},
};
return Ok(crate::LocalDexCorpusSearchSummaryDto::from(row));
},
}
}
/// Lists matching transaction samples for a local DEX corpus search.
pub async fn query_local_dex_corpus_search_list_transaction_samples(
database: &crate::Database,
request: &crate::LocalDexCorpusSearchRequestDto,
) -> Result<std::vec::Vec<crate::LocalDexCorpusTransactionSampleDto>, crate::Error> {
let limit = normalized_limit(request.limit);
if limit == 0 {
return Ok(std::vec::Vec::new());
}
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let dex_code = normalized_filter_text(request.dex_code.as_deref());
let program_id = normalized_filter_text(request.program_id.as_deref());
let pool_address = normalized_filter_text(request.pool_address.as_deref());
let token_mint = normalized_filter_text(request.token_mint.as_deref());
let signature = normalized_filter_text(request.signature.as_deref());
let pool_address_like = like_filter_text(pool_address.as_str());
let token_mint_like = like_filter_text(token_mint.as_str());
let query_result = sqlx::query_as::<
sqlx::Sqlite,
crate::db::dtos::LocalDexCorpusTransactionSampleRow,
>(
r#"
SELECT
tx.id AS transaction_id,
tx.signature AS signature,
tx.slot AS slot,
CASE WHEN tx.err_json IS NULL THEN 0 ELSE 1 END AS failed,
(
SELECT COUNT(*)
FROM k_sol_chain_instructions count_ix
WHERE count_ix.transaction_id = tx.id
) AS instruction_count,
(
SELECT COUNT(*)
FROM k_sol_dex_decoded_events count_dde
WHERE count_dde.transaction_id = tx.id
) AS decoded_event_count,
(
SELECT COUNT(*)
FROM k_sol_trade_events count_te
WHERE count_te.transaction_id = tx.id
) AS trade_event_count,
COALESCE((
SELECT GROUP_CONCAT(DISTINCT program_ix.program_id)
FROM k_sol_chain_instructions program_ix
WHERE program_ix.transaction_id = tx.id
AND program_ix.program_id IS NOT NULL
), '') AS program_ids_csv
FROM k_sol_chain_transactions tx
LEFT JOIN k_sol_chain_instructions ix
ON ix.transaction_id = tx.id
LEFT JOIN k_sol_dex_decoded_events dde
ON dde.transaction_id = tx.id
LEFT JOIN k_sol_pools pool
ON pool.address = dde.pool_account
LEFT JOIN k_sol_pairs pair
ON pair.pool_id = pool.id
LEFT JOIN k_sol_dexes dex
ON dex.id = pair.dex_id
LEFT JOIN k_sol_tokens base_token
ON base_token.id = pair.base_token_id
LEFT JOIN k_sol_tokens quote_token
ON quote_token.id = pair.quote_token_id
LEFT JOIN k_sol_protocol_candidates pc
ON pc.transaction_id = tx.id
WHERE
(NULLIF(TRIM(?), '') IS NULL
OR dex.code = ?
OR dde.protocol_name = ?
OR pc.candidate_protocol = ?
OR pc.candidate_surface = ?)
AND (NULLIF(TRIM(?), '') IS NULL
OR ix.program_id = ?
OR dde.program_id = ?
OR pc.program_id = ?)
AND (? IS NULL OR pair.id = ?)
AND (NULLIF(TRIM(?), '') IS NULL
OR pool.address = ?
OR dde.pool_account = ?
OR ix.accounts_json LIKE ?
OR pc.evidence_json LIKE ?)
AND (NULLIF(TRIM(?), '') IS NULL
OR base_token.mint = ?
OR quote_token.mint = ?
OR dde.token_a_mint = ?
OR dde.token_b_mint = ?
OR ix.accounts_json LIKE ?
OR tx.transaction_json LIKE ?
OR pc.evidence_json LIKE ?)
AND (NULLIF(TRIM(?), '') IS NULL OR tx.signature = ?)
GROUP BY tx.id
ORDER BY tx.slot DESC, tx.id DESC
LIMIT ?
"#,
)
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(program_id.clone())
.bind(program_id.clone())
.bind(program_id.clone())
.bind(program_id.clone())
.bind(request.pair_id)
.bind(request.pair_id)
.bind(pool_address.clone())
.bind(pool_address.clone())
.bind(pool_address.clone())
.bind(pool_address_like.clone())
.bind(pool_address_like.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint_like.clone())
.bind(token_mint_like.clone())
.bind(token_mint_like.clone())
.bind(signature.clone())
.bind(signature.clone())
.bind(i64::from(limit))
.fetch_all(pool)
.await;
let rows = match query_result {
Ok(rows) => rows,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list local DEX corpus transaction samples on sqlite: {}",
error
)));
},
};
let mut dtos = std::vec::Vec::new();
for row in rows {
dtos.push(crate::LocalDexCorpusTransactionSampleDto::from(row));
}
return Ok(dtos);
},
}
}
/// Lists matching pool/pair samples for a local DEX corpus search.
pub async fn query_local_dex_corpus_search_list_pool_pair_samples(
database: &crate::Database,
request: &crate::LocalDexCorpusSearchRequestDto,
) -> Result<std::vec::Vec<crate::LocalDexCorpusPoolPairSampleDto>, crate::Error> {
let limit = normalized_limit(request.limit);
if limit == 0 {
return Ok(std::vec::Vec::new());
}
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let dex_code = normalized_filter_text(request.dex_code.as_deref());
let program_id = normalized_filter_text(request.program_id.as_deref());
let pool_address = normalized_filter_text(request.pool_address.as_deref());
let token_mint = normalized_filter_text(request.token_mint.as_deref());
let signature = normalized_filter_text(request.signature.as_deref());
let pool_address_like = like_filter_text(pool_address.as_str());
let token_mint_like = like_filter_text(token_mint.as_str());
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::db::dtos::LocalDexCorpusPoolPairSampleRow>(
r#"
SELECT
pool.id AS pool_id,
pool.address AS pool_address,
pair.id AS pair_id,
dex.code AS dex_code,
pair.symbol AS pair_symbol,
base_token.mint AS base_mint,
base_token.symbol AS base_symbol,
quote_token.mint AS quote_mint,
quote_token.symbol AS quote_symbol,
COUNT(DISTINCT dde.id) AS decoded_event_count,
COUNT(DISTINCT te.id) AS trade_event_count,
COUNT(DISTINCT candle.id) AS pair_candle_count,
MAX(COALESCE(te.signature, tx.signature, pc.signature)) AS latest_signature
FROM k_sol_pools pool
LEFT JOIN k_sol_pairs pair
ON pair.pool_id = pool.id
LEFT JOIN k_sol_dexes dex
ON dex.id = pool.dex_id
LEFT JOIN k_sol_tokens base_token
ON base_token.id = pair.base_token_id
LEFT JOIN k_sol_tokens quote_token
ON quote_token.id = pair.quote_token_id
LEFT JOIN k_sol_dex_decoded_events dde
ON dde.pool_account = pool.address
LEFT JOIN k_sol_chain_transactions tx
ON tx.id = dde.transaction_id
LEFT JOIN k_sol_chain_instructions ix
ON ix.transaction_id = tx.id
LEFT JOIN k_sol_trade_events te
ON te.pair_id = pair.id
LEFT JOIN k_sol_pair_candles candle
ON candle.pair_id = pair.id
LEFT JOIN k_sol_protocol_candidates pc
ON pc.transaction_id = tx.id
WHERE
(NULLIF(TRIM(?), '') IS NULL
OR dex.code = ?
OR dde.protocol_name = ?
OR pc.candidate_protocol = ?
OR pc.candidate_surface = ?)
AND (NULLIF(TRIM(?), '') IS NULL
OR ix.program_id = ?
OR dde.program_id = ?
OR pc.program_id = ?)
AND (? IS NULL OR pair.id = ?)
AND (NULLIF(TRIM(?), '') IS NULL
OR pool.address = ?
OR dde.pool_account = ?
OR ix.accounts_json LIKE ?
OR pc.evidence_json LIKE ?)
AND (NULLIF(TRIM(?), '') IS NULL
OR base_token.mint = ?
OR quote_token.mint = ?
OR dde.token_a_mint = ?
OR dde.token_b_mint = ?
OR ix.accounts_json LIKE ?
OR tx.transaction_json LIKE ?
OR pc.evidence_json LIKE ?)
AND (NULLIF(TRIM(?), '') IS NULL OR tx.signature = ? OR te.signature = ? OR pc.signature = ?)
GROUP BY pool.id, pair.id
ORDER BY MAX(COALESCE(tx.slot, te.slot, pc.slot)) DESC, pool.id DESC
LIMIT ?
"#,
)
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(program_id.clone())
.bind(program_id.clone())
.bind(program_id.clone())
.bind(program_id.clone())
.bind(request.pair_id)
.bind(request.pair_id)
.bind(pool_address.clone())
.bind(pool_address.clone())
.bind(pool_address.clone())
.bind(pool_address_like.clone())
.bind(pool_address_like.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint_like.clone())
.bind(token_mint_like.clone())
.bind(token_mint_like.clone())
.bind(signature.clone())
.bind(signature.clone())
.bind(signature.clone())
.bind(signature.clone())
.bind(i64::from(limit))
.fetch_all(pool)
.await;
let rows = match query_result {
Ok(rows) => rows,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list local DEX corpus pool/pair samples on sqlite: {}",
error
)));
},
};
let mut dtos = std::vec::Vec::new();
for row in rows {
dtos.push(crate::LocalDexCorpusPoolPairSampleDto::from(row));
}
return Ok(dtos);
},
}
}
/// Lists matching decoded-event samples for a local DEX corpus search.
pub async fn query_local_dex_corpus_search_list_decoded_event_samples(
database: &crate::Database,
request: &crate::LocalDexCorpusSearchRequestDto,
) -> Result<std::vec::Vec<crate::LocalDexCorpusDecodedEventSampleDto>, crate::Error> {
let limit = normalized_limit(request.limit);
if limit == 0 {
return Ok(std::vec::Vec::new());
}
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let dex_code = normalized_filter_text(request.dex_code.as_deref());
let program_id = normalized_filter_text(request.program_id.as_deref());
let pool_address = normalized_filter_text(request.pool_address.as_deref());
let token_mint = normalized_filter_text(request.token_mint.as_deref());
let signature = normalized_filter_text(request.signature.as_deref());
let pool_address_like = like_filter_text(pool_address.as_str());
let token_mint_like = like_filter_text(token_mint.as_str());
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::db::dtos::LocalDexCorpusDecodedEventSampleRow>(
r#"
SELECT
dde.id AS decoded_event_id,
tx.id AS transaction_id,
tx.signature AS signature,
tx.slot AS slot,
dde.protocol_name AS protocol_name,
dde.program_id AS program_id,
dde.event_kind AS event_kind,
dde.pool_account AS pool_account,
dde.token_a_mint AS token_a_mint,
dde.token_b_mint AS token_b_mint,
json_extract(dde.payload_json, '$.eventCategory') AS event_category,
json_extract(dde.payload_json, '$.eventLifecycleKind') AS event_lifecycle_kind,
json_extract(dde.payload_json, '$.eventActionability') AS event_actionability,
json_extract(dde.payload_json, '$.tradeCandidate') AS trade_candidate,
json_extract(dde.payload_json, '$.candleCandidate') AS candle_candidate
FROM k_sol_dex_decoded_events dde
JOIN k_sol_chain_transactions tx
ON tx.id = dde.transaction_id
LEFT JOIN k_sol_chain_instructions ix
ON ix.transaction_id = tx.id
LEFT JOIN k_sol_pools pool
ON pool.address = dde.pool_account
LEFT JOIN k_sol_pairs pair
ON pair.pool_id = pool.id
LEFT JOIN k_sol_dexes dex
ON dex.id = pair.dex_id
LEFT JOIN k_sol_tokens base_token
ON base_token.id = pair.base_token_id
LEFT JOIN k_sol_tokens quote_token
ON quote_token.id = pair.quote_token_id
LEFT JOIN k_sol_protocol_candidates pc
ON pc.transaction_id = tx.id
WHERE
(NULLIF(TRIM(?), '') IS NULL
OR dex.code = ?
OR dde.protocol_name = ?
OR pc.candidate_protocol = ?
OR pc.candidate_surface = ?)
AND (NULLIF(TRIM(?), '') IS NULL
OR ix.program_id = ?
OR dde.program_id = ?
OR pc.program_id = ?)
AND (? IS NULL OR pair.id = ?)
AND (NULLIF(TRIM(?), '') IS NULL
OR pool.address = ?
OR dde.pool_account = ?
OR ix.accounts_json LIKE ?
OR pc.evidence_json LIKE ?)
AND (NULLIF(TRIM(?), '') IS NULL
OR base_token.mint = ?
OR quote_token.mint = ?
OR dde.token_a_mint = ?
OR dde.token_b_mint = ?
OR ix.accounts_json LIKE ?
OR tx.transaction_json LIKE ?
OR pc.evidence_json LIKE ?)
AND (NULLIF(TRIM(?), '') IS NULL OR tx.signature = ? OR pc.signature = ?)
GROUP BY dde.id
ORDER BY tx.slot DESC, dde.id DESC
LIMIT ?
"#,
)
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(dex_code.clone())
.bind(program_id.clone())
.bind(program_id.clone())
.bind(program_id.clone())
.bind(program_id.clone())
.bind(request.pair_id)
.bind(request.pair_id)
.bind(pool_address.clone())
.bind(pool_address.clone())
.bind(pool_address.clone())
.bind(pool_address_like.clone())
.bind(pool_address_like.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint.clone())
.bind(token_mint_like.clone())
.bind(token_mint_like.clone())
.bind(token_mint_like.clone())
.bind(signature.clone())
.bind(signature.clone())
.bind(signature.clone())
.bind(i64::from(limit))
.fetch_all(pool)
.await;
let rows = match query_result {
Ok(rows) => rows,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list local DEX corpus decoded-event samples on sqlite: {}",
error
)));
},
};
let mut dtos = std::vec::Vec::new();
for row in rows {
dtos.push(crate::LocalDexCorpusDecodedEventSampleDto::from(row));
}
return Ok(dtos);
},
}
}
fn normalized_limit(limit: u32) -> u32 {
if limit > 200 {
return 200;
}
return limit;
}
fn normalized_filter_text(value: std::option::Option<&str>) -> std::string::String {
let value = match value {
Some(value) => value.trim(),
None => return std::string::String::new(),
};
return value.to_string();
}
fn like_filter_text(value: &str) -> std::string::String {
if value.is_empty() {
return std::string::String::new();
}
return format!("%{}%", value);
}

View File

@@ -584,6 +584,80 @@ ORDER BY dex_code
}
}
/// Lists observed Raydium program instruction diagnostics.
pub async fn query_local_raydium_program_instruction_diagnostic_list_summaries(
database: &crate::Database,
) -> Result<
std::vec::Vec<crate::LocalRaydiumProgramInstructionDiagnosticSummaryDto>,
crate::Error,
> {
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let rows_result = sqlx::query_as::<
sqlx::Sqlite,
crate::db::dtos::LocalRaydiumProgramInstructionDiagnosticSummaryRow,
>(
r#"
SELECT
ix.program_id AS program_id,
COUNT(ix.id) AS instruction_count,
COUNT(DISTINCT tx.signature) AS transaction_count,
MAX(tx.slot) AS latest_slot,
(
SELECT tx_latest.signature
FROM k_sol_chain_instructions ix_latest
JOIN k_sol_chain_transactions tx_latest
ON tx_latest.id = ix_latest.transaction_id
WHERE ix_latest.program_id = ix.program_id
ORDER BY
tx_latest.slot DESC,
tx_latest.id DESC,
ix_latest.instruction_index DESC,
COALESCE(ix_latest.inner_instruction_index, -1) DESC
LIMIT 1
) AS latest_signature
FROM k_sol_chain_instructions ix
JOIN k_sol_chain_transactions tx
ON tx.id = ix.transaction_id
WHERE ix.program_id IN (
'CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C',
'CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK',
'675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8',
'5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h',
'routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS',
'LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj'
)
GROUP BY ix.program_id
ORDER BY transaction_count DESC, instruction_count DESC, program_id
"#,
)
.fetch_all(pool)
.await;
let rows = match rows_result {
Ok(rows) => rows,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list local Raydium program instruction diagnostics on sqlite: {}",
error
)));
},
};
let mut summaries = std::vec::Vec::new();
for row in rows {
summaries.push(crate::LocalRaydiumProgramInstructionDiagnosticSummaryDto {
program_id: row.program_id,
instruction_count: row.instruction_count,
transaction_count: row.transaction_count,
latest_slot: row.latest_slot,
latest_signature: row.latest_signature,
});
}
return Ok(summaries);
},
}
}
/// Lists local pair diagnostic summaries.
pub async fn query_local_pair_diagnostic_list_summaries(
database: &crate::Database,

View File

@@ -53,6 +53,8 @@ mod http_pool;
mod json_rpc_ws;
/// Launch surface attribution service.
mod launch_origin;
/// Local DEX corpus search service used by Demo3.
mod local_dex_corpus_search;
/// Local pipeline diagnostics service.
mod local_pipeline_diagnostics;
/// Local pipeline replay from already persisted raw transaction data.
@@ -61,6 +63,8 @@ mod local_pipeline_replay;
mod local_pipeline_validation;
/// Useful non-trade DEX event materialization service.
mod non_trade_event_materialization;
/// On-chain DEX pair/pool discovery helpers used by Demo3.
mod onchain_dex_pair_discovery;
/// Pair analytic signal service.
mod pair_analytic_signal;
/// Pair-candle aggregation service.
@@ -359,6 +363,18 @@ pub use db::LiquidityEventEntity;
pub use db::LiquidityEventKind;
/// Local decoded-event diagnostics summary.
pub use db::LocalDecodedEventDiagnosticSummaryDto;
/// Local DEX corpus decoded-event sample for Demo3.
pub use db::LocalDexCorpusDecodedEventSampleDto;
/// Local DEX corpus pool/pair sample for Demo3.
pub use db::LocalDexCorpusPoolPairSampleDto;
/// Local DEX corpus search request for Demo3.
pub use db::LocalDexCorpusSearchRequestDto;
/// Local DEX corpus search result for Demo3.
pub use db::LocalDexCorpusSearchResultDto;
/// Local DEX corpus search summary for Demo3.
pub use db::LocalDexCorpusSearchSummaryDto;
/// Local DEX corpus transaction sample for Demo3.
pub use db::LocalDexCorpusTransactionSampleDto;
/// Local DEX diagnostics summary.
pub use db::LocalDexDiagnosticSummaryDto;
/// Sample of duplicated trade rows grouped by decoded event id.
@@ -389,6 +405,10 @@ pub use db::LocalPipelineDiagnosticCountersDto;
pub use db::LocalPipelineDiagnosticSummaryDto;
/// Sample of a pool-origin row and optional launch linkage.
pub use db::LocalPoolOriginDiagnosticSampleDto;
/// Projected instruction diagnostics grouped by Raydium program id.
pub use db::LocalRaydiumProgramInstructionDiagnosticSummaryDto;
/// Raydium DEX-first surface diagnostics.
pub use db::LocalRaydiumSurfaceDiagnosticSummaryDto;
/// Prioritized sample of an incomplete token metadata row.
pub use db::LocalTokenMetadataGapDiagnosticSampleDto;
/// Source family for one on-chain observation.
@@ -603,6 +623,14 @@ pub use db::query_liquidity_events_list_recent;
pub use db::query_liquidity_events_upsert;
/// Lists local decoded-event diagnostic summaries.
pub use db::query_local_decoded_event_diagnostic_list_summaries;
/// Returns aggregate counts for a local DEX corpus search.
pub use db::query_local_dex_corpus_search_get_summary;
/// Lists matching decoded-event samples for a local DEX corpus search.
pub use db::query_local_dex_corpus_search_list_decoded_event_samples;
/// Lists matching pool/pair samples for a local DEX corpus search.
pub use db::query_local_dex_corpus_search_list_pool_pair_samples;
/// Lists matching transaction samples for a local DEX corpus search.
pub use db::query_local_dex_corpus_search_list_transaction_samples;
/// Lists samples of duplicated trade rows by decoded event id.
pub use db::query_local_duplicate_decoded_event_trade_diagnostic_list_samples;
/// Lists local decoded-event classification diagnostic summaries.
@@ -633,6 +661,8 @@ pub use db::query_local_pipeline_diagnostic_get_counters;
pub use db::query_local_pipeline_diagnostic_list_summaries;
/// Lists pool-origin diagnostic samples.
pub use db::query_local_pool_origin_diagnostic_list_samples;
/// Lists observed Raydium program instruction diagnostics.
pub use db::query_local_raydium_program_instruction_diagnostic_list_summaries;
/// Lists prioritized token metadata gap diagnostic samples.
pub use db::query_local_token_metadata_gap_diagnostic_list_samples;
/// Reads one observed token by mint.
@@ -1049,6 +1079,8 @@ pub use json_rpc_ws::parse_json_rpc_ws_incoming_value;
pub use launch_origin::LaunchAttributionResult;
/// Launch surface attribution service.
pub use launch_origin::LaunchOriginService;
/// Local DEX corpus search service used by Demo3.
pub use local_dex_corpus_search::LocalDexCorpusSearchService;
/// Local pipeline diagnostics service.
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticsService;
/// Configuration for a local pipeline replay pass.
@@ -1071,6 +1103,18 @@ 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;
/// 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.
pub use onchain_dex_pair_discovery::OnchainDexPairCandidateDto;
/// Request for on-chain DEX pair/pool discovery.
pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryRequestDto;
/// Result of one on-chain DEX pair/pool discovery run.
pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryResultDto;
/// On-chain pair/pool discovery service.
pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryService;
/// Token-balance delta observed in one transaction through Solana transaction metadata.
pub use onchain_dex_pair_discovery::OnchainDexTokenBalanceDeltaDto;
/// One pair-analytic-signal recording result.
pub use pair_analytic_signal::PairAnalyticSignalResult;
/// Pair analytic signal service.
@@ -1112,6 +1156,8 @@ pub use solana_pubsub_ws::parse_solana_ws_typed_notification;
pub use solana_pubsub_ws::parse_solana_ws_typed_notification_from_event;
/// One pool-backfill result summary.
pub use token_backfill::PoolBackfillResult;
/// One signature-backfill result summary.
pub use token_backfill::SignatureBackfillResult;
/// One token-backfill result summary.
pub use token_backfill::TokenBackfillResult;
/// Historical token backfill service.

View File

@@ -0,0 +1,103 @@
// file: kb_lib/src/local_dex_corpus_search.rs
//! Local DEX corpus search service used by Demo3.
/// Local DEX corpus search service.
#[derive(Debug, Clone)]
pub struct LocalDexCorpusSearchService {
database: std::sync::Arc<crate::Database>,
}
impl LocalDexCorpusSearchService {
/// Creates a new local DEX corpus search service.
pub fn new(database: std::sync::Arc<crate::Database>) -> Self {
return Self { database };
}
/// Searches the local persisted pipeline for DEX corpus candidates.
pub async fn search(
&self,
request: crate::LocalDexCorpusSearchRequestDto,
) -> Result<crate::LocalDexCorpusSearchResultDto, crate::Error> {
let normalized_request = normalize_request(request);
let summary_result = crate::query_local_dex_corpus_search_get_summary(
self.database.as_ref(),
&normalized_request,
)
.await;
let summary = match summary_result {
Ok(summary) => summary,
Err(error) => return Err(error),
};
let transaction_samples_result =
crate::query_local_dex_corpus_search_list_transaction_samples(
self.database.as_ref(),
&normalized_request,
)
.await;
let transaction_samples = match transaction_samples_result {
Ok(transaction_samples) => transaction_samples,
Err(error) => return Err(error),
};
let pool_pair_samples_result = crate::query_local_dex_corpus_search_list_pool_pair_samples(
self.database.as_ref(),
&normalized_request,
)
.await;
let pool_pair_samples = match pool_pair_samples_result {
Ok(pool_pair_samples) => pool_pair_samples,
Err(error) => return Err(error),
};
let decoded_event_samples_result =
crate::query_local_dex_corpus_search_list_decoded_event_samples(
self.database.as_ref(),
&normalized_request,
)
.await;
let decoded_event_samples = match decoded_event_samples_result {
Ok(decoded_event_samples) => decoded_event_samples,
Err(error) => return Err(error),
};
return Ok(crate::LocalDexCorpusSearchResultDto {
request: normalized_request,
summary,
transaction_samples,
pool_pair_samples,
decoded_event_samples,
});
}
}
fn normalize_request(
request: crate::LocalDexCorpusSearchRequestDto,
) -> crate::LocalDexCorpusSearchRequestDto {
let limit = if request.limit == 0 {
50
} else if request.limit > 200 {
200
} else {
request.limit
};
return crate::LocalDexCorpusSearchRequestDto {
dex_code: normalize_optional_text(request.dex_code),
program_id: normalize_optional_text(request.program_id),
pair_id: request.pair_id,
pool_address: normalize_optional_text(request.pool_address),
token_mint: normalize_optional_text(request.token_mint),
signature: normalize_optional_text(request.signature),
limit,
};
}
fn normalize_optional_text(
value: std::option::Option<std::string::String>,
) -> std::option::Option<std::string::String> {
let value = match value {
Some(value) => value.trim().to_string(),
None => return None,
};
if value.is_empty() {
return None;
}
return Some(value);
}

View File

@@ -29,6 +29,20 @@ impl LocalPipelineDiagnosticsService {
Ok(dex_summaries) => dex_summaries,
Err(error) => return Err(error),
};
let raydium_program_instruction_summaries_result =
crate::query_local_raydium_program_instruction_diagnostic_list_summaries(
self.database.as_ref(),
)
.await;
let raydium_program_instruction_summaries =
match raydium_program_instruction_summaries_result {
Ok(summaries) => summaries,
Err(error) => return Err(error),
};
let raydium_surface_summaries = build_raydium_surface_summaries(
&dex_summaries,
&raydium_program_instruction_summaries,
);
let pair_summaries_result =
crate::query_local_pair_diagnostic_list_summaries(self.database.as_ref()).await;
let pair_summaries = match pair_summaries_result {
@@ -229,6 +243,7 @@ impl LocalPipelineDiagnosticsService {
pair_without_trade_count: counters.pair_without_trade_count,
pair_without_candle_count: counters.pair_without_candle_count,
dex_summaries,
raydium_surface_summaries,
pair_summaries,
pair_actionability_summaries,
pair_trading_readiness_summaries,
@@ -248,3 +263,99 @@ impl LocalPipelineDiagnosticsService {
});
}
}
fn build_raydium_surface_summaries(
dex_summaries: &[crate::LocalDexDiagnosticSummaryDto],
program_instruction_summaries: &[crate::LocalRaydiumProgramInstructionDiagnosticSummaryDto],
) -> std::vec::Vec<crate::LocalRaydiumSurfaceDiagnosticSummaryDto> {
let mut summaries = std::vec::Vec::new();
for entry in crate::dex_support_matrix_entries() {
if entry.family != "raydium" {
continue;
}
let dex_summary = find_dex_summary(dex_summaries, entry.code);
let program_summary = match entry.program_id {
Some(program_id) => {
find_raydium_program_summary(program_instruction_summaries, program_id)
},
None => None,
};
let instruction_count = match program_summary {
Some(program_summary) => program_summary.instruction_count,
None => 0,
};
let transaction_count = match program_summary {
Some(program_summary) => program_summary.transaction_count,
None => 0,
};
let latest_slot = match program_summary {
Some(program_summary) => program_summary.latest_slot,
None => None,
};
let latest_signature = match program_summary {
Some(program_summary) => program_summary.latest_signature.clone(),
None => None,
};
let decoded_event_count = match dex_summary {
Some(dex_summary) => dex_summary.decoded_event_count,
None => 0,
};
let trade_event_count = match dex_summary {
Some(dex_summary) => dex_summary.trade_event_count,
None => 0,
};
let pair_candle_count = match dex_summary {
Some(dex_summary) => dex_summary.pair_candle_count,
None => 0,
};
summaries.push(crate::LocalRaydiumSurfaceDiagnosticSummaryDto {
dex_code: entry.code.to_string(),
display_name: entry.display_name.to_string(),
surface_role: entry.surface_role.to_string(),
program_id: match entry.program_id {
Some(program_id) => Some(program_id.to_string()),
None => None,
},
program_id_status: entry.program_id_status.to_string(),
status: entry.status.to_string(),
catalog_enabled: entry.catalog_enabled,
instruction_count,
transaction_count,
decoded_event_count,
trade_event_count,
pair_candle_count,
latest_slot,
latest_signature,
observed_in_current_corpus: instruction_count > 0,
decoded_in_current_corpus: decoded_event_count > 0,
trade_materialized_in_current_corpus: trade_event_count > 0,
});
}
summaries.sort_by(|left, right| return left.dex_code.cmp(&right.dex_code));
return summaries;
}
fn find_dex_summary<'a>(
dex_summaries: &'a [crate::LocalDexDiagnosticSummaryDto],
dex_code: &str,
) -> std::option::Option<&'a crate::LocalDexDiagnosticSummaryDto> {
for summary in dex_summaries {
if summary.dex_code == dex_code {
return Some(summary);
}
}
return None;
}
fn find_raydium_program_summary<'a>(
program_instruction_summaries: &'a [crate::LocalRaydiumProgramInstructionDiagnosticSummaryDto],
program_id: &str,
) -> std::option::Option<&'a crate::LocalRaydiumProgramInstructionDiagnosticSummaryDto> {
for summary in program_instruction_summaries {
if summary.program_id.as_str() == program_id {
return Some(summary);
}
}
return None;
}

View File

@@ -290,6 +290,26 @@ impl LocalPipelineValidationConfig {
return config;
}
/// Builds the `0.7.40` Raydium effective surfaces validation config.
///
/// This profile keeps the DEX-first matrix invariants and focuses expected
/// corpus warnings on Raydium effective swap surfaces. Missing Raydium
/// variants are intentionally warnings so AMM v4 or Stable Swap are not
/// promoted without transaction evidence.
pub fn v0_7_40_raydium_effective_surfaces() -> Self {
let mut config = Self::v0_7_39_dex_first_effective_swap_surfaces();
config.profile_code = "0.7.40_raydium_effective_surfaces".to_string();
config.expected_dex_codes = vec![
"raydium_cpmm".to_string(),
"raydium_clmm".to_string(),
"raydium_amm_v4".to_string(),
"raydium_stable_swap".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
@@ -534,6 +554,14 @@ impl LocalPipelineValidationService {
let config = crate::LocalPipelineValidationConfig::v0_7_39_dex_first_effective_swap_surfaces();
return self.validate_current_database(&config).await;
}
/// Diagnoses the current database with the `0.7.40` Raydium profile.
pub async fn validate_v0_7_40_current_database(
&self,
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
let config = crate::LocalPipelineValidationConfig::v0_7_40_raydium_effective_surfaces();
return self.validate_current_database(&config).await;
}
}
/// Validates a diagnostics summary without performing database access.
@@ -658,7 +686,8 @@ pub fn validate_local_pipeline_diagnostics_summary(
|| config.profile_code == "0.7.37_token_metadata_catalog_enrichment"
|| 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.39_launch_surface_origin_baseline"
|| config.profile_code == "0.7.40_raydium_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) {
@@ -1083,6 +1112,27 @@ 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,
},
],
pair_summaries: vec![],
pair_actionability_summaries: vec![
crate::LocalPairActionabilityDiagnosticSummaryDto {
@@ -1447,6 +1497,18 @@ mod tests {
assert_eq!(report.blocking_issue_count, 0);
}
#[test]
fn validation_accepts_0_7_40_raydium_effective_surfaces_with_missing_variants_as_warnings() {
let summary = make_0_7_28_summary_with_meteora();
let config = crate::LocalPipelineValidationConfig::v0_7_40_raydium_effective_surfaces();
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
assert!(report.validation_passed);
assert_eq!(report.validation_profile_code, "0.7.40_raydium_effective_surfaces");
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();

File diff suppressed because it is too large Load Diff

View File

@@ -84,6 +84,41 @@ pub struct PoolBackfillResult {
pub pair_candle_count: usize,
}
/// One signature-backfill result summary.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SignatureBackfillResult {
/// Input transaction signature.
pub signature: std::string::String,
/// Number of transactions resolved through HTTP during this run.
pub resolved_transaction_count: usize,
/// Number of signatures whose `getTransaction` lookup returned `null`.
pub missing_transaction_count: usize,
/// Total number of decoded DEX events replayed during this run.
pub decoded_event_count: usize,
/// Total number of DEX detection results produced during this run.
pub detection_count: usize,
/// Total number of launch-attribution results produced during this run.
pub launch_attribution_count: usize,
/// Total number of pool-origin results produced during this run.
pub pool_origin_count: usize,
/// Total number of wallet-participation observations produced during this run.
pub wallet_participation_count: usize,
/// Total number of trade-aggregation results produced during this run.
pub trade_event_count: usize,
/// Total number of liquidity event materialization results produced during this run.
pub liquidity_event_count: usize,
/// Total number of pool lifecycle event materialization results produced during this run.
pub pool_lifecycle_event_count: usize,
/// Total number of fee event materialization results produced during this run.
pub fee_event_count: usize,
/// Total number of reward event materialization results produced during this run.
pub reward_event_count: usize,
/// Total number of pool administration event materialization results produced during this run.
pub pool_admin_event_count: usize,
/// Total number of pair-candle aggregation results produced during this run.
pub pair_candle_count: usize,
}
/// Historical token backfill service.
///
/// This service reuses the existing transaction projection and downstream
@@ -681,6 +716,87 @@ impl TokenBackfillService {
return Ok(result);
}
/// Replays one known transaction signature through the existing pipeline.
pub async fn backfill_signature(
&self,
signature: &str,
) -> Result<crate::SignatureBackfillResult, crate::Error> {
let trimmed_signature = signature.trim().to_string();
if trimmed_signature.is_empty() {
return Err(crate::Error::Config("signature must not be empty".to_string()));
}
let replay_result = self.replay_signature(trimmed_signature.clone()).await;
let replay = match replay_result {
Ok(replay) => replay,
Err(error) => return Err(error),
};
self.backfill_missing_token_metadata_best_effort(100).await;
let result = crate::SignatureBackfillResult {
signature: trimmed_signature.clone(),
resolved_transaction_count: replay.resolved_transaction_count,
missing_transaction_count: replay.missing_transaction_count,
decoded_event_count: replay.decoded_event_count,
detection_count: replay.detection_count,
launch_attribution_count: replay.launch_attribution_count,
pool_origin_count: replay.pool_origin_count,
wallet_participation_count: replay.wallet_participation_count,
trade_event_count: replay.trade_event_count,
liquidity_event_count: replay.liquidity_event_count,
pool_lifecycle_event_count: replay.pool_lifecycle_event_count,
fee_event_count: replay.fee_event_count,
reward_event_count: replay.reward_event_count,
pool_admin_event_count: replay.pool_admin_event_count,
pair_candle_count: replay.pair_candle_count,
};
let summary_payload = serde_json::json!({
"signature": result.signature.clone(),
"resolvedTransactionCount": result.resolved_transaction_count,
"missingTransactionCount": result.missing_transaction_count,
"decodedEventCount": result.decoded_event_count,
"detectionCount": result.detection_count,
"launchAttributionCount": result.launch_attribution_count,
"poolOriginCount": result.pool_origin_count,
"walletParticipationCount": result.wallet_participation_count,
"tradeEventCount": result.trade_event_count,
"liquidityEventCount": result.liquidity_event_count,
"poolLifecycleEventCount": result.pool_lifecycle_event_count,
"feeEventCount": result.fee_event_count,
"rewardEventCount": result.reward_event_count,
"poolAdminEventCount": result.pool_admin_event_count,
"pairCandleCount": result.pair_candle_count
});
let observation_result = self
.persistence
.record_observation(&crate::DetectionObservationInput::new(
"signature.backfill.completed".to_string(),
crate::ObservationSourceKind::HttpRpc,
Some(format!("backfill:{}", self.http_role)),
trimmed_signature.clone(),
None,
summary_payload.clone(),
))
.await;
let observation_id = match observation_result {
Ok(observation_id) => observation_id,
Err(error) => return Err(error),
};
let signal_result = self
.persistence
.record_signal(&crate::DetectionSignalInput::new(
"signal.signature.backfill.completed".to_string(),
crate::AnalysisSignalSeverity::Low,
trimmed_signature,
Some(observation_id),
None,
summary_payload,
))
.await;
if let Err(error) = signal_result {
return Err(error);
}
return Ok(result);
}
async fn backfill_missing_token_metadata_best_effort(&self, limit: i64) {
let metadata_result =
self.token_metadata_service.backfill_missing_token_metadata(Some(limit)).await;