This commit is contained in:
2026-05-19 11:14:20 +02:00
parent 3f6d2e9f7f
commit 3da01156a0
22 changed files with 1137 additions and 308 deletions

View File

@@ -34,6 +34,7 @@ pub use dtos::LocalDecodedEventDiagnosticSummaryDto;
pub use dtos::LocalDexDiagnosticSummaryDto;
pub use dtos::LocalDuplicateDecodedEventTradeDiagnosticSampleDto;
pub use dtos::LocalEventClassificationDiagnosticSummaryDto;
pub use dtos::LocalLaunchOriginDiagnosticSampleDto;
pub use dtos::LocalMissingTradeEventDiagnosticSampleDto;
pub use dtos::LocalMissingTradeEventReasonSummaryDto;
pub use dtos::LocalMultiTradeSignaturePairDiagnosticSampleDto;
@@ -44,6 +45,7 @@ pub use dtos::LocalPairGapDiagnosticSampleDto;
pub use dtos::LocalPairTradingReadinessDiagnosticSummaryDto;
pub use dtos::LocalPipelineDiagnosticCountersDto;
pub use dtos::LocalPipelineDiagnosticSummaryDto;
pub use dtos::LocalPoolOriginDiagnosticSampleDto;
pub use dtos::LocalTokenMetadataGapDiagnosticSampleDto;
pub use dtos::ObservedTokenDto;
pub use dtos::OnchainObservationDto;
@@ -160,6 +162,7 @@ pub use queries::query_liquidity_events_upsert;
pub use queries::query_local_decoded_event_diagnostic_list_summaries;
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;
pub use queries::query_local_missing_trade_event_diagnostic_list_samples;
pub use queries::query_local_missing_trade_event_reason_list_summaries;
pub use queries::query_local_multi_trade_signature_pair_diagnostic_list_samples;
@@ -171,6 +174,7 @@ pub use queries::query_local_pair_without_candle_diagnostic_list_samples;
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_token_metadata_gap_diagnostic_list_samples;
pub use queries::query_observed_tokens_get_by_mint;
pub use queries::query_observed_tokens_list;

View File

@@ -49,6 +49,7 @@ pub(crate) use local_pipeline_diagnostics::LocalDecodedEventDiagnosticSummaryRow
pub(crate) use local_pipeline_diagnostics::LocalDexDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalDuplicateDecodedEventTradeDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalEventClassificationDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalLaunchOriginDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalMissingTradeEventDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalMissingTradeEventReasonSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalMultiTradeSignaturePairDiagnosticSampleRow;
@@ -58,6 +59,7 @@ pub(crate) use local_pipeline_diagnostics::LocalPairDiagnosticSummaryRow;
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::LocalTokenMetadataGapDiagnosticSampleRow;
pub use analysis_signal::AnalysisSignalDto;
@@ -79,6 +81,7 @@ pub use local_pipeline_diagnostics::LocalDecodedEventDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalDexDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalDuplicateDecodedEventTradeDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalEventClassificationDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalLaunchOriginDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalMissingTradeEventDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalMissingTradeEventReasonSummaryDto;
pub use local_pipeline_diagnostics::LocalMultiTradeSignaturePairDiagnosticSampleDto;
@@ -89,6 +92,7 @@ pub use local_pipeline_diagnostics::LocalPairGapDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalPairTradingReadinessDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticCountersDto;
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPoolOriginDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalTokenMetadataGapDiagnosticSampleDto;
pub use observed_token::ObservedTokenDto;
pub use onchain_observation::OnchainObservationDto;

View File

@@ -138,6 +138,10 @@ pub struct LocalPipelineDiagnosticSummaryDto {
/// Missing trade events grouped by diagnostic reason.
pub missing_trade_event_reason_summaries:
std::vec::Vec<crate::LocalMissingTradeEventReasonSummaryDto>,
/// Samples of launch-origin attributions.
pub launch_origin_samples: std::vec::Vec<crate::LocalLaunchOriginDiagnosticSampleDto>,
/// Samples of pool-origin rows and their optional launch linkage.
pub pool_origin_samples: std::vec::Vec<crate::LocalPoolOriginDiagnosticSampleDto>,
/// Prioritized samples of tokens whose display metadata is still incomplete.
pub token_metadata_gap_samples: std::vec::Vec<crate::LocalTokenMetadataGapDiagnosticSampleDto>,
/// Total pairs with only non-actionable missing trade events.
@@ -714,6 +718,60 @@ pub struct LocalPairGapDiagnosticSampleDto {
pub pair_candle_count: i64,
}
/// Sample of a launch-origin attribution.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalLaunchOriginDiagnosticSampleDto {
/// Launch attribution id.
pub launch_attribution_id: i64,
/// Launch surface code.
pub launch_surface_code: std::string::String,
/// Launch surface display name.
pub launch_surface_name: std::string::String,
/// Transaction signature.
pub transaction_signature: std::string::String,
/// Decoded event id.
pub decoded_event_id: i64,
/// Protocol that materialized the decoded event.
pub protocol_name: std::string::String,
/// Match kind used to attribute the launch surface.
pub match_kind: std::string::String,
/// Matched key value used to attribute the launch surface.
pub matched_value: std::string::String,
/// Optional related pool id.
pub pool_id: std::option::Option<i64>,
/// Optional related pool address.
pub pool_address: std::option::Option<std::string::String>,
/// Optional related pair id.
pub pair_id: std::option::Option<i64>,
/// Optional related pair symbol.
pub pair_symbol: std::option::Option<std::string::String>,
}
/// Sample of a pool-origin row.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalPoolOriginDiagnosticSampleDto {
/// Pool-origin id.
pub pool_origin_id: i64,
/// Effective DEX code attached to the pool.
pub dex_code: std::string::String,
/// Pool id.
pub pool_id: i64,
/// Pool address.
pub pool_address: std::string::String,
/// Optional pair id.
pub pair_id: std::option::Option<i64>,
/// Optional pair symbol.
pub pair_symbol: std::option::Option<std::string::String>,
/// Optional launch surface code linked through launch attribution.
pub launch_surface_code: std::option::Option<std::string::String>,
/// Founding transaction signature.
pub founding_signature: std::string::String,
/// Founding effective DEX/protocol name.
pub founding_protocol_name: std::string::String,
/// Founding decoded event kind.
pub founding_event_kind: std::string::String,
}
/// Prioritized sample of an incomplete token metadata row.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalTokenMetadataGapDiagnosticSampleDto {
@@ -835,6 +893,38 @@ pub(crate) struct LocalPairGapDiagnosticSampleRow {
pub(crate) pair_candle_count: i64,
}
/// SQL row for launch-origin diagnostic samples.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalLaunchOriginDiagnosticSampleRow {
pub(crate) launch_attribution_id: i64,
pub(crate) launch_surface_code: std::string::String,
pub(crate) launch_surface_name: std::string::String,
pub(crate) transaction_signature: std::string::String,
pub(crate) decoded_event_id: i64,
pub(crate) protocol_name: std::string::String,
pub(crate) match_kind: std::string::String,
pub(crate) matched_value: std::string::String,
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) pair_symbol: std::option::Option<std::string::String>,
}
/// SQL row for pool-origin diagnostic samples.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalPoolOriginDiagnosticSampleRow {
pub(crate) pool_origin_id: i64,
pub(crate) dex_code: std::string::String,
pub(crate) pool_id: i64,
pub(crate) pool_address: std::string::String,
pub(crate) pair_id: std::option::Option<i64>,
pub(crate) pair_symbol: std::option::Option<std::string::String>,
pub(crate) launch_surface_code: std::option::Option<std::string::String>,
pub(crate) founding_signature: std::string::String,
pub(crate) founding_protocol_name: std::string::String,
pub(crate) founding_event_kind: std::string::String,
}
/// SQL row for incomplete token metadata samples.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalTokenMetadataGapDiagnosticSampleRow {

View File

@@ -91,6 +91,7 @@ pub use liquidity_event::query_liquidity_events_upsert;
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;
pub use local_pipeline_diagnostics::query_local_launch_origin_diagnostic_list_samples;
pub use local_pipeline_diagnostics::query_local_missing_trade_event_diagnostic_list_samples;
pub use local_pipeline_diagnostics::query_local_missing_trade_event_reason_list_summaries;
pub use local_pipeline_diagnostics::query_local_multi_trade_signature_pair_diagnostic_list_samples;
@@ -102,6 +103,7 @@ pub use local_pipeline_diagnostics::query_local_pair_without_candle_diagnostic_l
pub use local_pipeline_diagnostics::query_local_pair_without_trade_diagnostic_list_samples;
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_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

@@ -1678,6 +1678,139 @@ LIMIT ?
}
}
/// Lists samples of launch-origin attributions.
pub async fn query_local_launch_origin_diagnostic_list_samples(
database: &crate::Database,
limit: i64,
) -> Result<std::vec::Vec<crate::LocalLaunchOriginDiagnosticSampleDto>, crate::Error> {
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let rows_result = sqlx::query_as::<
sqlx::Sqlite,
crate::db::dtos::LocalLaunchOriginDiagnosticSampleRow,
>(
r#"
SELECT
la.id AS launch_attribution_id,
ls.code AS launch_surface_code,
ls.name AS launch_surface_name,
ct.signature AS transaction_signature,
la.decoded_event_id AS decoded_event_id,
la.protocol_name AS protocol_name,
la.match_kind AS match_kind,
la.matched_value AS matched_value,
la.pool_id AS pool_id,
p.address AS pool_address,
la.pair_id AS pair_id,
pair.symbol AS pair_symbol
FROM k_sol_launch_attributions la
JOIN k_sol_launch_surfaces ls ON ls.id = la.launch_surface_id
JOIN k_sol_chain_transactions ct ON ct.id = la.transaction_id
LEFT JOIN k_sol_pools p ON p.id = la.pool_id
LEFT JOIN k_sol_pairs pair ON pair.id = la.pair_id
ORDER BY la.id DESC
LIMIT ?
"#,
)
.bind(limit)
.fetch_all(pool)
.await;
let rows = match rows_result {
Ok(rows) => rows,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list launch-origin diagnostic samples on sqlite: {}",
error
)));
},
};
let mut samples = std::vec::Vec::new();
for row in rows {
samples.push(crate::LocalLaunchOriginDiagnosticSampleDto {
launch_attribution_id: row.launch_attribution_id,
launch_surface_code: row.launch_surface_code,
launch_surface_name: row.launch_surface_name,
transaction_signature: row.transaction_signature,
decoded_event_id: row.decoded_event_id,
protocol_name: row.protocol_name,
match_kind: row.match_kind,
matched_value: row.matched_value,
pool_id: row.pool_id,
pool_address: row.pool_address,
pair_id: row.pair_id,
pair_symbol: row.pair_symbol,
});
}
return Ok(samples);
},
}
}
/// Lists samples of pool-origin rows and their optional launch linkage.
pub async fn query_local_pool_origin_diagnostic_list_samples(
database: &crate::Database,
limit: i64,
) -> Result<std::vec::Vec<crate::LocalPoolOriginDiagnosticSampleDto>, crate::Error> {
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let rows_result = sqlx::query_as::<
sqlx::Sqlite,
crate::db::dtos::LocalPoolOriginDiagnosticSampleRow,
>(
r#"
SELECT
po.id AS pool_origin_id,
d.code AS dex_code,
po.pool_id AS pool_id,
p.address AS pool_address,
po.pair_id AS pair_id,
pair.symbol AS pair_symbol,
ls.code AS launch_surface_code,
po.founding_signature AS founding_signature,
po.founding_protocol_name AS founding_protocol_name,
po.founding_event_kind AS founding_event_kind
FROM k_sol_pool_origins po
JOIN k_sol_dexes d ON d.id = po.dex_id
JOIN k_sol_pools p ON p.id = po.pool_id
LEFT JOIN k_sol_pairs pair ON pair.id = po.pair_id
LEFT JOIN k_sol_launch_attributions la ON la.id = po.launch_attribution_id
LEFT JOIN k_sol_launch_surfaces ls ON ls.id = la.launch_surface_id
ORDER BY po.id DESC
LIMIT ?
"#,
)
.bind(limit)
.fetch_all(pool)
.await;
let rows = match rows_result {
Ok(rows) => rows,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list pool-origin diagnostic samples on sqlite: {}",
error
)));
},
};
let mut samples = std::vec::Vec::new();
for row in rows {
samples.push(crate::LocalPoolOriginDiagnosticSampleDto {
pool_origin_id: row.pool_origin_id,
dex_code: row.dex_code,
pool_id: row.pool_id,
pool_address: row.pool_address,
pair_id: row.pair_id,
pair_symbol: row.pair_symbol,
launch_surface_code: row.launch_surface_code,
founding_signature: row.founding_signature,
founding_protocol_name: row.founding_protocol_name,
founding_event_kind: row.founding_event_kind,
});
}
return Ok(samples);
},
}
}
/// Lists prioritized samples of tokens whose metadata is still incomplete.
pub async fn query_local_token_metadata_gap_diagnostic_list_samples(
database: &crate::Database,

View File

@@ -573,6 +573,27 @@ const DEX_SUPPORT_MATRIX_ENTRIES: &[DexSupportMatrixEntry] = &[
skip_reason: Some("program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "bonk_fun",
display_name: "Bonk.fun",
family: "bonk",
version: "unknown",
surface_type: "launch",
program_id: None,
router_program_id: None,
program_id_status: "unknown",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: true,
pool_candidate: true,
status: "planned",
confidence: "low",
skip_reason: Some("program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "okx_dex",
display_name: "OKX DEX",
@@ -806,6 +827,7 @@ mod tests {
"meteora_damm_v2",
"bags",
"bonk",
"bonk_fun",
"okx_dex",
"boop_fun",
"moonshot",
@@ -825,8 +847,11 @@ mod tests {
#[test]
fn matrix_does_not_invent_program_ids_for_unverified_planned_surfaces() {
let codes = [
"raydium_launchpad",
"bags",
"letsbonk",
"bonk",
"bonk_fun",
"okx_dex",
"boop_fun",
"moonshot",
@@ -879,7 +904,19 @@ mod tests {
#[test]
fn matrix_marks_launch_surfaces_as_launch_or_bonding_curve() {
let codes = ["pump_fun", "raydium_launchlab", "bags", "moonshot", "moonit"];
let codes = [
"pump_fun",
"raydium_launchlab",
"raydium_launchpad",
"letsbonk",
"bonk_fun",
"bags",
"moonshot",
"moonit",
"boop_fun",
"believe",
"heaven",
];
for code in codes {
let entry = match crate::dex_support_matrix_entry_by_code(code) {
Some(entry) => entry,
@@ -894,6 +931,33 @@ mod tests {
}
}
#[test]
fn matrix_keeps_0_7_39_launch_surfaces_non_trade_materialized() {
let codes = [
"raydium_launchlab",
"raydium_launchpad",
"letsbonk",
"bonk_fun",
"bags",
"moonshot",
"moonit",
"boop_fun",
"believe",
"heaven",
];
for code in codes {
let entry = match crate::dex_support_matrix_entry_by_code(code) {
Some(entry) => entry,
None => panic!("missing matrix entry for {}", code),
};
assert!(!entry.decoded, "{} must not be decoded without corpus", code);
assert!(!entry.materialized, "{} must not materialize launch rows as trades", code);
assert!(!entry.trade_candidate, "{} must not create trade candidates", code);
assert!(!entry.candle_candidate, "{} must not create candle candidates", code);
assert!(!entry.catalog_enabled, "{} must not be inserted as enabled DEX", code);
}
}
#[test]
fn matrix_dto_preserves_core_fields() {
let entry = match crate::dex_support_matrix_entry_by_code("pump_swap") {

View File

@@ -2,6 +2,77 @@
//! Launch surface attribution service.
#[derive(Debug, Copy, Clone)]
struct BuiltinLaunchSurfaceSpec {
code: &'static str,
name: &'static str,
protocol_family: &'static str,
enabled: bool,
}
const BUILTIN_LAUNCH_SURFACE_SPECS: &[BuiltinLaunchSurfaceSpec] = &[
BuiltinLaunchSurfaceSpec {
code: "raydium_launchlab",
name: "Raydium LaunchLab",
protocol_family: "raydium",
enabled: true,
},
BuiltinLaunchSurfaceSpec {
code: "raydium_launchpad",
name: "Raydium Launchpad",
protocol_family: "raydium",
enabled: true,
},
BuiltinLaunchSurfaceSpec {
code: "letsbonk",
name: "LetsBonk / Bonk.fun",
protocol_family: "bonk",
enabled: true,
},
BuiltinLaunchSurfaceSpec {
code: "bonk_fun",
name: "Bonk.fun",
protocol_family: "bonk",
enabled: true,
},
BuiltinLaunchSurfaceSpec {
code: "bags",
name: "Bags",
protocol_family: "bags",
enabled: true,
},
BuiltinLaunchSurfaceSpec {
code: "moonshot",
name: "Moonshot",
protocol_family: "moonshot",
enabled: true,
},
BuiltinLaunchSurfaceSpec {
code: "moonit",
name: "Moonit",
protocol_family: "moonit",
enabled: true,
},
BuiltinLaunchSurfaceSpec {
code: "boop_fun",
name: "Boop.fun",
protocol_family: "boop",
enabled: true,
},
BuiltinLaunchSurfaceSpec {
code: "believe",
name: "Believe",
protocol_family: "believe",
enabled: true,
},
BuiltinLaunchSurfaceSpec {
code: "heaven",
name: "Heaven",
protocol_family: "heaven",
enabled: false,
},
];
/// Result of one launch surface attribution.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LaunchAttributionResult {
@@ -33,57 +104,121 @@ impl LaunchOriginService {
return Self { database, persistence };
}
/// Ensures that the built-in `moonit` launch surface exists and returns its id.
pub async fn ensure_moonit_surface(&self) -> Result<i64, crate::Error> {
let existing_result =
crate::query_launch_surfaces_get_by_code(self.database.as_ref(), "moonit").await;
let existing_option = match existing_result {
Ok(existing_option) => existing_option,
/// Ensures that every built-in launch surface tracked by `0.7.39` exists.
pub async fn ensure_target_launch_surfaces(&self) -> Result<std::vec::Vec<i64>, crate::Error> {
let mut surface_ids = std::vec::Vec::new();
for spec in BUILTIN_LAUNCH_SURFACE_SPECS {
let surface_id_result = self.ensure_builtin_launch_surface(spec.code).await;
let surface_id = match surface_id_result {
Ok(surface_id) => surface_id,
Err(error) => return Err(error),
};
surface_ids.push(surface_id);
}
return Ok(surface_ids);
}
/// Ensures that one built-in launch surface exists and returns its id.
pub async fn ensure_builtin_launch_surface(&self, code: &str) -> Result<i64, crate::Error> {
let spec = match find_builtin_launch_surface_spec(code) {
Some(spec) => spec,
None => {
return Err(crate::Error::InvalidState(format!(
"unknown built-in launch surface '{}'",
code
)));
},
};
let surface_id_result = self.ensure_launch_surface_from_spec(spec).await;
let surface_id = match surface_id_result {
Ok(surface_id) => surface_id,
Err(error) => return Err(error),
};
let surface_id = match existing_option {
Some(existing) => match existing.id {
Some(surface_id) => surface_id,
None => {
return Err(crate::Error::InvalidState(
"moonit launch surface has no internal id".to_string(),
));
},
},
None => {
let dto = crate::LaunchSurfaceDto::new(
"moonit".to_string(),
"Moonit".to_string(),
Some("launchpad".to_string()),
true,
);
let insert_result =
crate::query_launch_surfaces_upsert(self.database.as_ref(), &dto).await;
match insert_result {
Ok(surface_id) => surface_id,
Err(error) => return Err(error),
}
},
};
let suffix_key_result = crate::query_launch_surface_keys_upsert(
self.database.as_ref(),
&crate::LaunchSurfaceKeyDto::new(
surface_id,
"token_mint_suffix".to_string(),
"moon".to_string(),
),
)
.await;
if let Err(error) = suffix_key_result {
return Err(error);
if spec.code == "moonit" {
let suffix_key_result = crate::query_launch_surface_keys_upsert(
self.database.as_ref(),
&crate::LaunchSurfaceKeyDto::new(
surface_id,
"token_mint_suffix".to_string(),
"moon".to_string(),
),
)
.await;
if let Err(error) = suffix_key_result {
return Err(error);
}
}
return Ok(surface_id);
}
/// Ensures that the built-in `moonit` launch surface exists and returns its id.
pub async fn ensure_moonit_surface(&self) -> Result<i64, crate::Error> {
return self.ensure_builtin_launch_surface("moonit").await;
}
/// Ensures that the built-in `bags` launch surface exists and returns its id.
pub async fn ensure_bags_surface(&self) -> Result<i64, crate::Error> {
return self.ensure_builtin_launch_surface("bags").await;
}
/// Registers one generic launch-surface mapping from verified corpus/API evidence.
pub async fn register_launch_surface_mapping(
&self,
surface_code: &str,
token_mint: std::option::Option<&str>,
config_account: std::option::Option<&str>,
pool_account: std::option::Option<&str>,
creator: std::option::Option<&str>,
) -> Result<i64, crate::Error> {
let surface_id_result = self.ensure_builtin_launch_surface(surface_code).await;
let surface_id = match surface_id_result {
Ok(surface_id) => surface_id,
Err(error) => return Err(error),
};
let key_specs = [
("token_mint", token_mint),
("config_account", config_account),
("pool_account", pool_account),
("creator", creator),
];
let mut inserted_key_count = 0_u32;
for (match_kind, match_value) in key_specs {
let match_value = match match_value {
Some(match_value) => match_value,
None => continue,
};
if match_value.trim().is_empty() {
continue;
}
let key_result = crate::query_launch_surface_keys_upsert(
self.database.as_ref(),
&crate::LaunchSurfaceKeyDto::new(
surface_id,
match_kind.to_string(),
match_value.to_string(),
),
)
.await;
if let Err(error) = key_result {
return Err(error);
}
inserted_key_count += 1;
}
if inserted_key_count == 0 {
return Err(crate::Error::InvalidState(format!(
"launch surface '{}' mapping must include at least one non-empty key",
surface_code
)));
}
return Ok(surface_id);
}
async fn ensure_launch_surface_from_spec(
&self,
spec: BuiltinLaunchSurfaceSpec,
) -> Result<i64, crate::Error> {
let existing_result =
crate::query_launch_surfaces_get_by_code(self.database.as_ref(), "bags").await;
crate::query_launch_surfaces_get_by_code(self.database.as_ref(), spec.code).await;
let existing_option = match existing_result {
Ok(existing_option) => existing_option,
Err(error) => return Err(error),
@@ -92,17 +227,18 @@ impl LaunchOriginService {
Some(existing) => match existing.id {
Some(surface_id) => return Ok(surface_id),
None => {
return Err(crate::Error::InvalidState(
"bags launch surface has no internal id".to_string(),
));
return Err(crate::Error::InvalidState(format!(
"{} launch surface has no internal id",
spec.code
)));
},
},
None => {
let dto = crate::LaunchSurfaceDto::new(
"bags".to_string(),
"Bags".to_string(),
Some("launchpad".to_string()),
true,
spec.code.to_string(),
spec.name.to_string(),
Some(spec.protocol_family.to_string()),
spec.enabled,
);
return crate::query_launch_surfaces_upsert(self.database.as_ref(), &dto).await;
},
@@ -117,51 +253,19 @@ impl LaunchOriginService {
dbc_pool_key: std::option::Option<std::string::String>,
damm_v2_pool_key: std::option::Option<std::string::String>,
) -> Result<i64, crate::Error> {
let surface_id_result = self.ensure_bags_surface().await;
let surface_id_result = self
.register_launch_surface_mapping(
"bags",
Some(token_mint),
dbc_config_key.as_deref(),
dbc_pool_key.as_deref(),
None,
)
.await;
let surface_id = match surface_id_result {
Ok(surface_id) => surface_id,
Err(error) => return Err(error),
};
let token_key_result = crate::query_launch_surface_keys_upsert(
self.database.as_ref(),
&crate::LaunchSurfaceKeyDto::new(
surface_id,
"token_mint".to_string(),
token_mint.to_string(),
),
)
.await;
if let Err(error) = token_key_result {
return Err(error);
}
if let Some(dbc_config_key) = dbc_config_key {
let key_result = crate::query_launch_surface_keys_upsert(
self.database.as_ref(),
&crate::LaunchSurfaceKeyDto::new(
surface_id,
"config_account".to_string(),
dbc_config_key,
),
)
.await;
if let Err(error) = key_result {
return Err(error);
}
}
if let Some(dbc_pool_key) = dbc_pool_key {
let key_result = crate::query_launch_surface_keys_upsert(
self.database.as_ref(),
&crate::LaunchSurfaceKeyDto::new(
surface_id,
"pool_account".to_string(),
dbc_pool_key,
),
)
.await;
if let Err(error) = key_result {
return Err(error);
}
}
if let Some(damm_v2_pool_key) = damm_v2_pool_key {
let key_result = crate::query_launch_surface_keys_upsert(
self.database.as_ref(),
@@ -209,8 +313,8 @@ impl LaunchOriginService {
)));
},
};
let ensure_moonit_result = self.ensure_moonit_surface().await;
if let Err(error) = ensure_moonit_result {
let ensure_surfaces_result = self.ensure_target_launch_surfaces().await;
if let Err(error) = ensure_surfaces_result {
return Err(error);
}
let decoded_events_result = crate::query_dex_decoded_events_list_by_transaction_id(
@@ -512,6 +616,15 @@ impl LaunchOriginService {
}
}
fn find_builtin_launch_surface_spec(code: &str) -> std::option::Option<BuiltinLaunchSurfaceSpec> {
for spec in BUILTIN_LAUNCH_SURFACE_SPECS {
if spec.code == code {
return Some(*spec);
}
}
return None;
}
#[derive(Debug, Clone)]
struct MatchedLaunchSurface {
launch_surface_id: i64,
@@ -866,4 +979,79 @@ mod tests {
assert_eq!(listed[0].match_kind, "token_mint_suffix".to_string());
assert_eq!(listed[0].matched_value, "ExampleTokenmoon".to_string());
}
#[tokio::test]
async fn ensure_target_launch_surfaces_seeds_0_7_39_surfaces_without_program_ids() {
let database = make_database().await;
let service = crate::LaunchOriginService::new(database.clone());
let result = service.ensure_target_launch_surfaces().await;
let surface_ids = match result {
Ok(surface_ids) => surface_ids,
Err(error) => panic!("target launch surfaces must seed: {}", error),
};
assert_eq!(surface_ids.len(), 10);
let required_codes = [
"raydium_launchlab",
"raydium_launchpad",
"letsbonk",
"bonk_fun",
"bags",
"moonshot",
"moonit",
"boop_fun",
"believe",
"heaven",
];
for code in required_codes {
let surface_result =
crate::query_launch_surfaces_get_by_code(database.as_ref(), code).await;
let surface_option = match surface_result {
Ok(surface_option) => surface_option,
Err(error) => panic!("surface fetch must succeed: {}", error),
};
assert!(surface_option.is_some(), "missing launch surface {}", code);
}
let heaven_surface =
match crate::query_launch_surfaces_get_by_code(database.as_ref(), "heaven").await {
Ok(Some(surface)) => surface,
Ok(None) => panic!("heaven surface must exist"),
Err(error) => panic!("heaven surface fetch must succeed: {}", error),
};
assert!(!heaven_surface.is_enabled);
}
#[tokio::test]
async fn attribute_transaction_by_signature_detects_generic_launch_surface_mapping() {
let database = make_database().await;
let service = crate::LaunchOriginService::new(database.clone());
let register_result = service
.register_launch_surface_mapping(
"raydium_launchlab",
Some("DbcDetectTokenA111"),
Some("DbcDetectConfig111"),
None,
None,
)
.await;
if let Err(error) = register_result {
panic!("launchlab mapping registration must succeed: {}", error);
}
seed_decoded_meteora_dbc_event(database.clone(), "sig-launch-origin-launchlab-1").await;
let result = service
.attribute_transaction_by_signature("sig-launch-origin-launchlab-1")
.await;
let results = match result {
Ok(results) => results,
Err(error) => panic!("launchlab attribution must succeed: {}", error),
};
assert_eq!(results.len(), 1);
assert!(results[0].created_attribution);
let surface_result =
crate::query_launch_surfaces_get_by_code(database.as_ref(), "raydium_launchlab").await;
let surface = match surface_result {
Ok(Some(surface)) => surface,
Ok(None) => panic!("raydium_launchlab surface must exist"),
Err(error) => panic!("surface fetch must succeed: {}", error),
};
assert_eq!(surface.id, Some(results[0].launch_surface_id));
}
}

View File

@@ -146,6 +146,8 @@ pub use constants::ARBITRAGE_BOT_6MWVT_PROGRAM_ID;
/// Associated Token Account program identifier. ("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL").
/// @see solana_sdk::pubkey::Pubkey = spl_associated_token_account_interface::program::ID
pub use constants::ASSOCIATED_TOKEN_PROGRAM_ID;
/// Canonical Bonk token mint identifier.
pub use constants::BONK_MINT_ID;
/// BPF Loader program identifier. ("BPFLoader1111111111111111111111111111111111").
/// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::bpf_loader_deprecated::ID
pub use constants::BPF_LOADER_DEPRECATED_PROGRAM_ID;
@@ -171,6 +173,8 @@ pub use constants::FLUXBEAM_PROGRAM_ID;
/// Incinerator program identifier. ("1nc1nerator11111111111111111111111111111111").
/// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::incinerator::ID
pub use constants::INCINERATOR_PROGRAM_ID;
/// Canonical Jupiter governance token mint identifier.
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;
@@ -191,6 +195,8 @@ pub use constants::ORCA_WHIRLPOOLS_PROGRAM_ID;
pub use constants::PUMP_FUN_PROGRAM_ID;
/// PumpSwap / PumpAMM program id. ("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA").
pub use constants::PUMP_SWAP_PROGRAM_ID;
/// Canonical Raydium token mint identifier.
pub use constants::RAY_MINT_ID;
/// Raydium AMM routing program id. ("routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS").
pub use constants::RAYDIUM_AMM_ROUTING_PROGRAM_ID;
/// Raydium AmmV4 program id. ("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8").
@@ -263,19 +269,13 @@ pub use constants::SYSVAR_SLOT_HISTORY_PROGRAM_ID;
/// Sysvar Stake History program identifier. ("SysvarStakeHistory1111111111111111111111111").
/// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::sysvar::stake_history::ID
pub use constants::SYSVAR_STAKE_HISTORY_PROGRAM_ID;
/// Vote program identifier. ("Vote111111111111111111111111111111111111111").
/// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::vote::ID
pub use constants::VOTE_PROGRAM_ID;
/// Canonical Bonk token mint identifier.
pub use constants::BONK_MINT_ID;
/// Canonical Jupiter governance token mint identifier.
pub use constants::JUP_MINT_ID;
/// Canonical Raydium token mint identifier.
pub use constants::RAY_MINT_ID;
/// Canonical Solana USDC mint identifier.
pub use constants::USDC_MINT_ID;
/// Canonical Solana USDT mint identifier.
pub use constants::USDT_MINT_ID;
/// Vote program identifier. ("Vote111111111111111111111111111111111111111").
/// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::vote::ID
pub use constants::VOTE_PROGRAM_ID;
/// Wrapped SOL mint identifier. ("So11111111111111111111111111111111111111112").
/// @see solana_sdk::pubkey::Pubkey = spl_token_interface::native_mint::ID
pub use constants::WSOL_MINT_ID;
@@ -365,6 +365,8 @@ pub use db::LocalDexDiagnosticSummaryDto;
pub use db::LocalDuplicateDecodedEventTradeDiagnosticSampleDto;
/// Local decoded-event classification diagnostics summary.
pub use db::LocalEventClassificationDiagnosticSummaryDto;
/// Sample of a launch-origin attribution.
pub use db::LocalLaunchOriginDiagnosticSampleDto;
/// Sample of a decoded trade candidate without linked trade event.
pub use db::LocalMissingTradeEventDiagnosticSampleDto;
/// Missing trade event diagnostics grouped by reason.
@@ -385,6 +387,8 @@ pub use db::LocalPairTradingReadinessDiagnosticSummaryDto;
pub use db::LocalPipelineDiagnosticCountersDto;
/// Local pipeline diagnostics summary.
pub use db::LocalPipelineDiagnosticSummaryDto;
/// Sample of a pool-origin row and optional launch linkage.
pub use db::LocalPoolOriginDiagnosticSampleDto;
/// Prioritized sample of an incomplete token metadata row.
pub use db::LocalTokenMetadataGapDiagnosticSampleDto;
/// Source family for one on-chain observation.
@@ -603,6 +607,8 @@ pub use db::query_local_decoded_event_diagnostic_list_summaries;
pub use db::query_local_duplicate_decoded_event_trade_diagnostic_list_samples;
/// Lists local decoded-event classification diagnostic summaries.
pub use db::query_local_event_classification_diagnostic_list_summaries;
/// Lists launch-origin diagnostic samples.
pub use db::query_local_launch_origin_diagnostic_list_samples;
/// Lists samples of decoded trade candidates without linked trade event.
pub use db::query_local_missing_trade_event_diagnostic_list_samples;
/// Lists missing trade events grouped by diagnostic reason.
@@ -625,6 +631,8 @@ pub use db::query_local_pair_without_trade_diagnostic_list_samples;
pub use db::query_local_pipeline_diagnostic_get_counters;
/// Lists local DEX diagnostic summaries.
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 prioritized token metadata gap diagnostic samples.
pub use db::query_local_token_metadata_gap_diagnostic_list_samples;
/// Reads one observed token by mint.

View File

@@ -75,6 +75,25 @@ impl LocalPipelineDiagnosticsService {
Ok(summaries) => summaries,
Err(error) => return Err(error),
};
let launch_origin_samples_result =
crate::query_local_launch_origin_diagnostic_list_samples(
self.database.as_ref(),
sample_limit,
)
.await;
let launch_origin_samples = match launch_origin_samples_result {
Ok(samples) => samples,
Err(error) => return Err(error),
};
let pool_origin_samples_result = crate::query_local_pool_origin_diagnostic_list_samples(
self.database.as_ref(),
sample_limit,
)
.await;
let pool_origin_samples = match pool_origin_samples_result {
Ok(samples) => samples,
Err(error) => return Err(error),
};
let token_metadata_gap_samples_result =
crate::query_local_token_metadata_gap_diagnostic_list_samples(
self.database.as_ref(),
@@ -216,6 +235,8 @@ impl LocalPipelineDiagnosticsService {
decoded_event_summaries,
event_classification_summaries,
missing_trade_event_reason_summaries,
launch_origin_samples,
pool_origin_samples,
token_metadata_gap_samples,
non_actionable_pair_count: counters.non_actionable_pair_count,
non_actionable_pair_summaries,

View File

@@ -277,6 +277,19 @@ impl LocalPipelineValidationConfig {
config.profile_code = "0.7.38_token_metadata_gap_prioritization".to_string();
return config;
}
/// Builds the `0.7.39` launch surface origin baseline validation config.
///
/// This profile keeps the `0.7.38` metadata-gap semantics and requires the
/// static DEX matrix invariants that prevent planned launch surfaces from
/// being treated as priced trade/candle producers before corpus-backed
/// decoders are available.
pub fn v0_7_39_launch_surface_origin_baseline() -> Self {
let mut config = Self::v0_7_38_token_metadata_gap_prioritization();
config.profile_code = "0.7.39_launch_surface_origin_baseline".to_string();
config.require_dex_support_matrix_semantics = true;
return config;
}
}
/// A single local pipeline validation issue.
@@ -505,6 +518,14 @@ impl LocalPipelineValidationService {
crate::LocalPipelineValidationConfig::v0_7_38_token_metadata_gap_prioritization();
return self.validate_current_database(&config).await;
}
/// Diagnoses the current database with the `0.7.39` launch-origin baseline profile.
pub async fn validate_v0_7_39_current_database(
&self,
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
let config = crate::LocalPipelineValidationConfig::v0_7_39_launch_surface_origin_baseline();
return self.validate_current_database(&config).await;
}
}
/// Validates a diagnostics summary without performing database access.
@@ -627,7 +648,8 @@ pub fn validate_local_pipeline_diagnostics_summary(
|| config.profile_code == "0.7.35_non_trade_fee_reward_admin"
|| config.profile_code == "0.7.36_meteora_family_consolidation"
|| 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.38_token_metadata_gap_prioritization"
|| config.profile_code == "0.7.39_launch_surface_origin_baseline";
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) {
@@ -875,6 +897,23 @@ fn validate_dex_support_matrix_semantics(
blocking: true,
});
}
if (entry.status == "planned" || entry.status == "to_verify" || entry.status == "unknown")
&& entry.surface_type == "launch"
&& (entry.decoded
|| entry.materialized
|| entry.trade_candidate
|| entry.candle_candidate)
{
issues.push(crate::LocalPipelineValidationIssueDto {
code: "inactive_launch_surface_matrix_entry_actionable".to_string(),
message: format!(
"inactive launch surface '{}' must not be decoded, materialized, or priced as trade/candle candidate",
entry_code
),
subject: Some(entry_code.clone()),
blocking: true,
});
}
}
}
@@ -1061,6 +1100,8 @@ mod tests {
decoded_event_summaries: vec![],
event_classification_summaries: vec![],
missing_trade_event_reason_summaries: vec![],
launch_origin_samples: vec![],
pool_origin_samples: vec![],
token_metadata_gap_samples: vec![],
non_actionable_pair_summaries: vec![],
missing_trade_event_samples: vec![],
@@ -1355,6 +1396,16 @@ mod tests {
assert_eq!(summary.token_metadata_gap_samples.len(), 1);
}
#[test]
fn validation_accepts_0_7_39_launch_surface_origin_baseline() {
let summary = make_0_7_28_summary_with_meteora();
let config = crate::LocalPipelineValidationConfig::v0_7_39_launch_surface_origin_baseline();
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
assert!(report.validation_passed);
assert_eq!(report.validation_profile_code, "0.7.39_launch_surface_origin_baseline");
assert_eq!(report.blocking_issue_count, 0);
}
#[test]
fn validation_rejects_0_7_33_pair_trading_readiness_mismatch() {
let mut summary = make_0_7_28_summary_with_meteora();