This commit is contained in:
2026-05-13 20:11:29 +02:00
parent 693a456e62
commit cfa1ff2289
36 changed files with 2035 additions and 103 deletions

View File

@@ -25,10 +25,10 @@ mod pair_candle;
mod pair_metric;
mod pool;
mod pool_listing;
mod pool_lifecycle_event;
mod pool_origin;
mod pool_token;
mod program_instruction_diagnostic;
mod program_instruction_discriminator_summary;
mod protocol_candidate;
mod protocol_candidate_summary;
mod swap;
@@ -39,22 +39,24 @@ mod trade_event;
mod transaction_classification;
mod wallet;
mod wallet_holding;
mod program_instruction_discriminator_summary;
mod wallet_participation;
pub(crate) use local_pipeline_diagnostics::LocalDecodedEventDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalEventClassificationDiagnosticSummaryRow;
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::LocalMissingTradeEventDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalMissingTradeEventReasonSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalMultiTradeSignaturePairDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalNonActionablePairDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPairActionabilityDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPairDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPairGapDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalPairActionabilityDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPairTradingReadinessDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPairGapDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalPipelineDiagnosticCountersRow;
pub use program_instruction_discriminator_summary::ProgramInstructionDiscriminatorSummaryDto;
pub use analysis_signal::AnalysisSignalDto;
pub use chain_instruction::ChainInstructionDto;
pub use chain_slot::ChainSlotDto;
@@ -70,17 +72,17 @@ pub use launch_surface::LaunchSurfaceDto;
pub use launch_surface_key::LaunchSurfaceKeyDto;
pub use liquidity_event::LiquidityEventDto;
pub use local_pipeline_diagnostics::LocalDecodedEventDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalEventClassificationDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalDexDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalDuplicateDecodedEventTradeDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalEventClassificationDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalMissingTradeEventDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalMissingTradeEventReasonSummaryDto;
pub use local_pipeline_diagnostics::LocalMultiTradeSignaturePairDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalNonActionablePairDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPairActionabilityDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPairDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPairGapDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalPairActionabilityDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPairTradingReadinessDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPairGapDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticCountersDto;
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticSummaryDto;
pub use observed_token::ObservedTokenDto;
@@ -91,10 +93,10 @@ pub use pair_candle::PairCandleDto;
pub use pair_metric::PairMetricDto;
pub use pool::PoolDto;
pub use pool_listing::PoolListingDto;
pub use pool_lifecycle_event::PoolLifecycleEventDto;
pub use pool_origin::PoolOriginDto;
pub use pool_token::PoolTokenDto;
pub use program_instruction_diagnostic::ProgramInstructionDiagnosticDto;
pub use program_instruction_discriminator_summary::ProgramInstructionDiscriminatorSummaryDto;
pub use protocol_candidate::ProtocolCandidateDto;
pub use protocol_candidate_summary::ProtocolCandidateSummaryDto;
pub use swap::SwapDto;

View File

@@ -7,6 +7,10 @@
pub struct LiquidityEventDto {
/// Optional numeric primary key.
pub id: std::option::Option<i64>,
/// Optional related transaction id.
pub transaction_id: std::option::Option<i64>,
/// Optional related decoded DEX event id.
pub decoded_event_id: std::option::Option<i64>,
/// Related DEX id.
pub dex_id: i64,
/// Related pool id.
@@ -19,8 +23,12 @@ pub struct LiquidityEventDto {
pub instruction_index: i64,
/// Optional slot number.
pub slot: std::option::Option<u64>,
/// Optional program id that emitted the decoded event.
pub program_id: std::option::Option<std::string::String>,
/// Liquidity event kind.
pub event_kind: crate::LiquidityEventKind,
/// Optional original decoded event kind.
pub event_kind_text: std::option::Option<std::string::String>,
/// Optional actor wallet.
pub actor_wallet: std::option::Option<std::string::String>,
/// Base token id.
@@ -35,8 +43,14 @@ pub struct LiquidityEventDto {
pub quote_amount: std::string::String,
/// Optional LP amount as decimal text.
pub lp_amount: std::option::Option<std::string::String>,
/// Whether the persisted amount fields are complete.
pub amounts_are_complete: bool,
/// Optional source decoded payload JSON.
pub payload_json: std::option::Option<std::string::String>,
/// Execution timestamp.
pub executed_at: chrono::DateTime<chrono::Utc>,
/// Creation timestamp.
pub created_at: chrono::DateTime<chrono::Utc>,
}
impl LiquidityEventDto {
@@ -57,15 +71,20 @@ impl LiquidityEventDto {
quote_amount: std::string::String,
lp_amount: std::option::Option<std::string::String>,
) -> Self {
let now = chrono::Utc::now();
return Self {
id: None,
transaction_id: None,
decoded_event_id: None,
dex_id,
pool_id,
pair_id,
signature,
instruction_index,
slot,
program_id: None,
event_kind,
event_kind_text: None,
actor_wallet,
base_token_id,
quote_token_id,
@@ -73,9 +92,32 @@ impl LiquidityEventDto {
base_amount,
quote_amount,
lp_amount,
executed_at: chrono::Utc::now(),
amounts_are_complete: true,
payload_json: None,
executed_at: now,
created_at: now,
};
}
/// Adds decoded-event linkage and audit payload metadata to the DTO.
#[allow(clippy::too_many_arguments)]
pub fn with_decoded_event_metadata(
mut self,
transaction_id: std::option::Option<i64>,
decoded_event_id: std::option::Option<i64>,
program_id: std::option::Option<std::string::String>,
event_kind_text: std::option::Option<std::string::String>,
payload_json: std::option::Option<std::string::String>,
amounts_are_complete: bool,
) -> Self {
self.transaction_id = transaction_id;
self.decoded_event_id = decoded_event_id;
self.program_id = program_id;
self.event_kind_text = event_kind_text;
self.payload_json = payload_json;
self.amounts_are_complete = amounts_are_complete;
return self;
}
}
impl TryFrom<crate::LiquidityEventEntity> for LiquidityEventDto {
@@ -114,13 +156,17 @@ impl TryFrom<crate::LiquidityEventEntity> for LiquidityEventDto {
};
return Ok(Self {
id: Some(entity.id),
transaction_id: entity.transaction_id,
decoded_event_id: entity.decoded_event_id,
dex_id: entity.dex_id,
pool_id: entity.pool_id,
pair_id: entity.pair_id,
signature: entity.signature,
instruction_index: entity.instruction_index,
slot,
program_id: entity.program_id,
event_kind,
event_kind_text: entity.event_kind_text,
actor_wallet: entity.actor_wallet,
base_token_id: entity.base_token_id,
quote_token_id: entity.quote_token_id,
@@ -128,7 +174,27 @@ impl TryFrom<crate::LiquidityEventEntity> for LiquidityEventDto {
base_amount: entity.base_amount,
quote_amount: entity.quote_amount,
lp_amount: entity.lp_amount,
amounts_are_complete: match entity.amounts_are_complete {
Some(amounts_are_complete) => amounts_are_complete != 0,
None => true,
},
payload_json: entity.payload_json,
executed_at,
created_at: match entity.created_at {
Some(created_at) => {
let created_at_result = chrono::DateTime::parse_from_rfc3339(&created_at);
match created_at_result {
Ok(created_at) => created_at.with_timezone(&chrono::Utc),
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot parse liquidity event created_at '{}': {}",
created_at, error
)));
},
}
},
None => executed_at,
},
});
}
}

View File

@@ -23,6 +23,10 @@ pub struct LocalPipelineDiagnosticSummaryDto {
pub decoded_non_actionable_trade_event_count: i64,
/// Total decoded events with unknown classification.
pub decoded_unknown_event_count: i64,
/// Total persisted liquidity events.
pub liquidity_event_count: i64,
/// Total persisted pool lifecycle events.
pub pool_lifecycle_event_count: i64,
/// Whether the local persisted pipeline has no blocking diagnostic issue.
pub diagnostics_clean: bool,
/// Number of blocking diagnostic issues.
@@ -363,6 +367,10 @@ pub struct LocalPipelineDiagnosticCountersDto {
pub decoded_non_actionable_trade_event_count: i64,
/// Total decoded events with unknown classification.
pub decoded_unknown_event_count: i64,
/// Total persisted liquidity events.
pub liquidity_event_count: i64,
/// Total persisted pool lifecycle events.
pub pool_lifecycle_event_count: i64,
/// Total decoded trade candidates without trade event, including ignored failed transactions.
pub missing_trade_event_count: i64,
/// Explicit alias for decoded trade candidates without linked trade event.
@@ -433,6 +441,8 @@ pub(crate) struct LocalPipelineDiagnosticCountersRow {
pub(crate) decoded_non_trade_useful_event_count: i64,
pub(crate) decoded_non_actionable_trade_event_count: i64,
pub(crate) decoded_unknown_event_count: i64,
pub(crate) liquidity_event_count: i64,
pub(crate) pool_lifecycle_event_count: i64,
pub(crate) missing_trade_event_count: i64,
pub(crate) decoded_trade_candidate_without_trade_event_count: i64,
pub(crate) decoded_trade_candidate_without_trade_event_on_ok_transaction_count: i64,

View File

@@ -0,0 +1,145 @@
// file: kb_lib/src/db/dtos/pool_lifecycle_event.rs
//! Pool lifecycle event DTO.
/// Application-facing normalized pool lifecycle event DTO.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PoolLifecycleEventDto {
/// Optional numeric primary key.
pub id: std::option::Option<i64>,
/// Related transaction id.
pub transaction_id: i64,
/// Related decoded DEX event id, when available.
pub decoded_event_id: std::option::Option<i64>,
/// Related DEX id, when the DEX row is known.
pub dex_id: std::option::Option<i64>,
/// Related pool id, when the pool row is known.
pub pool_id: std::option::Option<i64>,
/// Related pair id, when the pair row is known.
pub pair_id: std::option::Option<i64>,
/// Transaction signature.
pub signature: std::string::String,
/// Optional slot number.
pub slot: std::option::Option<u64>,
/// Protocol name that emitted the decoded event.
pub protocol_name: std::string::String,
/// Program id that emitted the decoded event.
pub program_id: std::string::String,
/// Stable decoded event kind.
pub event_kind: std::string::String,
/// Pool account address, when decoded.
pub pool_account: std::option::Option<std::string::String>,
/// First token mint, when decoded.
pub token_a_mint: std::option::Option<std::string::String>,
/// Second token mint, when decoded.
pub token_b_mint: std::option::Option<std::string::String>,
/// Source decoded payload JSON.
pub payload_json: std::string::String,
/// Execution timestamp.
pub executed_at: chrono::DateTime<chrono::Utc>,
/// Creation timestamp.
pub created_at: chrono::DateTime<chrono::Utc>,
}
impl PoolLifecycleEventDto {
/// Creates a new pool lifecycle event DTO.
#[allow(clippy::too_many_arguments)]
pub fn new(
transaction_id: i64,
decoded_event_id: std::option::Option<i64>,
dex_id: std::option::Option<i64>,
pool_id: std::option::Option<i64>,
pair_id: std::option::Option<i64>,
signature: std::string::String,
slot: std::option::Option<u64>,
protocol_name: std::string::String,
program_id: std::string::String,
event_kind: std::string::String,
pool_account: std::option::Option<std::string::String>,
token_a_mint: std::option::Option<std::string::String>,
token_b_mint: std::option::Option<std::string::String>,
payload_json: std::string::String,
) -> Self {
let now = chrono::Utc::now();
return Self {
id: None,
transaction_id,
decoded_event_id,
dex_id,
pool_id,
pair_id,
signature,
slot,
protocol_name,
program_id,
event_kind,
pool_account,
token_a_mint,
token_b_mint,
payload_json,
executed_at: now,
created_at: now,
};
}
}
impl TryFrom<crate::PoolLifecycleEventEntity> for PoolLifecycleEventDto {
type Error = crate::Error;
fn try_from(entity: crate::PoolLifecycleEventEntity) -> Result<Self, Self::Error> {
let executed_at_result = chrono::DateTime::parse_from_rfc3339(&entity.executed_at);
let executed_at = match executed_at_result {
Ok(executed_at) => executed_at.with_timezone(&chrono::Utc),
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot parse pool lifecycle event executed_at '{}': {}",
entity.executed_at, error
)));
},
};
let created_at_result = chrono::DateTime::parse_from_rfc3339(&entity.created_at);
let created_at = match created_at_result {
Ok(created_at) => created_at.with_timezone(&chrono::Utc),
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot parse pool lifecycle event created_at '{}': {}",
entity.created_at, error
)));
},
};
let slot = match entity.slot {
Some(slot) => {
let slot_result = u64::try_from(slot);
match slot_result {
Ok(slot) => Some(slot),
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot convert pool lifecycle event slot '{}' to u64: {}",
slot, error
)));
},
}
},
None => None,
};
return Ok(Self {
id: Some(entity.id),
transaction_id: entity.transaction_id,
decoded_event_id: entity.decoded_event_id,
dex_id: entity.dex_id,
pool_id: entity.pool_id,
pair_id: entity.pair_id,
signature: entity.signature,
slot,
protocol_name: entity.protocol_name,
program_id: entity.program_id,
event_kind: entity.event_kind,
pool_account: entity.pool_account,
token_a_mint: entity.token_a_mint,
token_b_mint: entity.token_b_mint,
payload_json: entity.payload_json,
executed_at,
created_at,
});
}
}

View File

@@ -26,6 +26,7 @@ mod pair_candle;
mod pair_metric;
mod pool;
mod pool_listing;
mod pool_lifecycle_event;
mod pool_origin;
mod pool_token;
mod program_instruction_diagnostic;
@@ -64,6 +65,7 @@ pub use pair_candle::PairCandleEntity;
pub use pair_metric::PairMetricEntity;
pub use pool::PoolEntity;
pub use pool_listing::PoolListingEntity;
pub use pool_lifecycle_event::PoolLifecycleEventEntity;
pub use pool_origin::PoolOriginEntity;
pub use pool_token::PoolTokenEntity;
pub use program_instruction_diagnostic::ProgramInstructionDiagnosticEntity;

View File

@@ -7,6 +7,10 @@
pub struct LiquidityEventEntity {
/// Numeric primary key.
pub id: i64,
/// Optional related transaction id.
pub transaction_id: std::option::Option<i64>,
/// Optional related decoded DEX event id.
pub decoded_event_id: std::option::Option<i64>,
/// Related DEX id.
pub dex_id: i64,
/// Related pool id.
@@ -19,8 +23,12 @@ pub struct LiquidityEventEntity {
pub instruction_index: i64,
/// Optional slot number.
pub slot: std::option::Option<i64>,
/// Optional program id that emitted the decoded event.
pub program_id: std::option::Option<std::string::String>,
/// Event kind stored as stable integer.
pub event_kind: i16,
/// Optional original decoded event kind.
pub event_kind_text: std::option::Option<std::string::String>,
/// Optional actor wallet.
pub actor_wallet: std::option::Option<std::string::String>,
/// Base token id.
@@ -35,6 +43,12 @@ pub struct LiquidityEventEntity {
pub quote_amount: std::string::String,
/// Optional LP amount as decimal text.
pub lp_amount: std::option::Option<std::string::String>,
/// Whether the persisted amount fields are complete.
pub amounts_are_complete: std::option::Option<i64>,
/// Optional source decoded payload JSON.
pub payload_json: std::option::Option<std::string::String>,
/// Execution timestamp encoded as RFC3339 UTC text.
pub executed_at: std::string::String,
/// Optional creation timestamp encoded as RFC3339 UTC text.
pub created_at: std::option::Option<std::string::String>,
}

View File

@@ -0,0 +1,42 @@
// file: kb_lib/src/db/entities/pool_lifecycle_event.rs
//! Pool lifecycle event entity.
/// Persisted normalized pool lifecycle event row.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
pub struct PoolLifecycleEventEntity {
/// Numeric primary key.
pub id: i64,
/// Related transaction id.
pub transaction_id: i64,
/// Related decoded DEX event id, when available.
pub decoded_event_id: std::option::Option<i64>,
/// Related DEX id, when the DEX row is known.
pub dex_id: std::option::Option<i64>,
/// Related pool id, when the pool row is known.
pub pool_id: std::option::Option<i64>,
/// Related pair id, when the pair row is known.
pub pair_id: std::option::Option<i64>,
/// Transaction signature.
pub signature: std::string::String,
/// Optional slot number.
pub slot: std::option::Option<i64>,
/// Protocol name that emitted the decoded event.
pub protocol_name: std::string::String,
/// Program id that emitted the decoded event.
pub program_id: std::string::String,
/// Stable decoded event kind.
pub event_kind: std::string::String,
/// Pool account address, when decoded.
pub pool_account: std::option::Option<std::string::String>,
/// First token mint, when decoded.
pub token_a_mint: std::option::Option<std::string::String>,
/// Second token mint, when decoded.
pub token_b_mint: std::option::Option<std::string::String>,
/// Source decoded payload JSON.
pub payload_json: std::string::String,
/// Execution timestamp encoded as RFC3339 UTC text.
pub executed_at: std::string::String,
/// Creation timestamp encoded as RFC3339 UTC text.
pub created_at: std::string::String,
}

View File

@@ -24,6 +24,7 @@ mod pair_analytic_signal;
mod pair_candle;
mod pair_metric;
mod pool;
mod pool_lifecycle_event;
mod pool_listing;
mod pool_origin;
mod pool_token;
@@ -116,6 +117,9 @@ pub use pair_metric::query_pair_metrics_upsert;
pub use pool::query_pools_get_by_address;
pub use pool::query_pools_list;
pub use pool::query_pools_upsert;
pub use pool_lifecycle_event::query_pool_lifecycle_events_get_by_decoded_event_id;
pub use pool_lifecycle_event::query_pool_lifecycle_events_list_recent;
pub use pool_lifecycle_event::query_pool_lifecycle_events_upsert;
pub use pool_listing::query_pool_listings_get_by_pool_id;
pub use pool_listing::query_pool_listings_list;
pub use pool_listing::query_pool_listings_upsert;

View File

@@ -27,13 +27,17 @@ pub async fn query_liquidity_events_upsert(
let query_result = sqlx::query(
r#"
INSERT INTO k_sol_liquidity_events (
transaction_id,
decoded_event_id,
dex_id,
pool_id,
pair_id,
signature,
instruction_index,
slot,
program_id,
event_kind,
event_kind_text,
actor_wallet,
base_token_id,
quote_token_id,
@@ -41,15 +45,22 @@ INSERT INTO k_sol_liquidity_events (
base_amount,
quote_amount,
lp_amount,
executed_at
amounts_are_complete,
payload_json,
executed_at,
created_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(signature, instruction_index) DO UPDATE SET
transaction_id = excluded.transaction_id,
decoded_event_id = excluded.decoded_event_id,
dex_id = excluded.dex_id,
pool_id = excluded.pool_id,
pair_id = excluded.pair_id,
slot = excluded.slot,
program_id = excluded.program_id,
event_kind = excluded.event_kind,
event_kind_text = excluded.event_kind_text,
actor_wallet = excluded.actor_wallet,
base_token_id = excluded.base_token_id,
quote_token_id = excluded.quote_token_id,
@@ -57,16 +68,22 @@ ON CONFLICT(signature, instruction_index) DO UPDATE SET
base_amount = excluded.base_amount,
quote_amount = excluded.quote_amount,
lp_amount = excluded.lp_amount,
amounts_are_complete = excluded.amounts_are_complete,
payload_json = excluded.payload_json,
executed_at = excluded.executed_at
"#,
)
.bind(dto.transaction_id)
.bind(dto.decoded_event_id)
.bind(dto.dex_id)
.bind(dto.pool_id)
.bind(dto.pair_id)
.bind(dto.signature.clone())
.bind(dto.instruction_index)
.bind(slot_i64)
.bind(dto.program_id.clone())
.bind(dto.event_kind.to_i16())
.bind(dto.event_kind_text.clone())
.bind(dto.actor_wallet.clone())
.bind(dto.base_token_id)
.bind(dto.quote_token_id)
@@ -74,7 +91,10 @@ ON CONFLICT(signature, instruction_index) DO UPDATE SET
.bind(dto.base_amount.clone())
.bind(dto.quote_amount.clone())
.bind(dto.lp_amount.clone())
.bind(if dto.amounts_are_complete { 1_i64 } else { 0_i64 })
.bind(dto.payload_json.clone())
.bind(dto.executed_at.to_rfc3339())
.bind(dto.created_at.to_rfc3339())
.execute(pool)
.await;
if let Err(error) = query_result {
@@ -122,13 +142,17 @@ pub async fn query_liquidity_events_list_recent(
r#"
SELECT
id,
transaction_id,
decoded_event_id,
dex_id,
pool_id,
pair_id,
signature,
instruction_index,
slot,
program_id,
event_kind,
event_kind_text,
actor_wallet,
base_token_id,
quote_token_id,
@@ -136,7 +160,10 @@ SELECT
base_amount,
quote_amount,
lp_amount,
executed_at
amounts_are_complete,
payload_json,
executed_at,
created_at
FROM k_sol_liquidity_events
ORDER BY id DESC
LIMIT ?

View File

@@ -48,6 +48,8 @@ SELECT
FROM k_sol_dex_decoded_events
WHERE COALESCE(json_extract(payload_json, '$.eventCategory'), 'unknown') = 'unknown'
) AS decoded_unknown_event_count,
(SELECT COUNT(*) FROM k_sol_liquidity_events) AS liquidity_event_count,
(SELECT COUNT(*) FROM k_sol_pool_lifecycle_events) AS pool_lifecycle_event_count,
(
SELECT COUNT(*)
FROM k_sol_dex_decoded_events dde
@@ -357,6 +359,8 @@ SELECT
decoded_non_actionable_trade_event_count: row
.decoded_non_actionable_trade_event_count,
decoded_unknown_event_count: row.decoded_unknown_event_count,
liquidity_event_count: row.liquidity_event_count,
pool_lifecycle_event_count: row.pool_lifecycle_event_count,
missing_trade_event_count: row.missing_trade_event_count,
decoded_trade_candidate_without_trade_event_count: row
.decoded_trade_candidate_without_trade_event_count,

View File

@@ -0,0 +1,294 @@
// file: kb_lib/src/db/queries/pool_lifecycle_event.rs
//! Queries for `k_sol_pool_lifecycle_events`.
/// Returns one pool lifecycle event by decoded event id.
pub async fn query_pool_lifecycle_events_get_by_decoded_event_id(
database: &crate::Database,
decoded_event_id: i64,
) -> Result<std::option::Option<crate::PoolLifecycleEventDto>, crate::Error> {
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::PoolLifecycleEventEntity>(
r#"
SELECT
id,
transaction_id,
decoded_event_id,
dex_id,
pool_id,
pair_id,
signature,
slot,
protocol_name,
program_id,
event_kind,
pool_account,
token_a_mint,
token_b_mint,
payload_json,
executed_at,
created_at
FROM k_sol_pool_lifecycle_events
WHERE decoded_event_id = ?
LIMIT 1
"#,
)
.bind(decoded_event_id)
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot fetch k_sol_pool_lifecycle_events by decoded_event_id '{}' on sqlite: {}",
decoded_event_id, error
)));
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::PoolLifecycleEventDto::try_from(entity);
match dto_result {
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
/// Inserts or updates one normalized pool lifecycle event row.
pub async fn query_pool_lifecycle_events_upsert(
database: &crate::Database,
dto: &crate::PoolLifecycleEventDto,
) -> Result<i64, crate::Error> {
let slot_i64 = match dto.slot {
Some(slot) => {
let slot_result = i64::try_from(slot);
match slot_result {
Ok(slot) => Some(slot),
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot convert pool lifecycle event slot '{}' to i64: {}",
slot, error
)));
},
}
},
None => None,
};
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let existing_id = match dto.decoded_event_id {
Some(decoded_event_id) => {
let existing_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
r#"
SELECT id
FROM k_sol_pool_lifecycle_events
WHERE decoded_event_id = ?
LIMIT 1
"#,
)
.bind(decoded_event_id)
.fetch_optional(pool)
.await;
match existing_result {
Ok(existing_id) => existing_id,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot fetch k_sol_pool_lifecycle_events id for decoded_event_id '{}' on sqlite: {}",
decoded_event_id, error
)));
},
}
},
None => None,
};
if let Some(id) = existing_id {
let update_result = sqlx::query(
r#"
UPDATE k_sol_pool_lifecycle_events
SET
transaction_id = ?,
dex_id = ?,
pool_id = ?,
pair_id = ?,
signature = ?,
slot = ?,
protocol_name = ?,
program_id = ?,
event_kind = ?,
pool_account = ?,
token_a_mint = ?,
token_b_mint = ?,
payload_json = ?,
executed_at = ?
WHERE id = ?
"#,
)
.bind(dto.transaction_id)
.bind(dto.dex_id)
.bind(dto.pool_id)
.bind(dto.pair_id)
.bind(dto.signature.clone())
.bind(slot_i64)
.bind(dto.protocol_name.clone())
.bind(dto.program_id.clone())
.bind(dto.event_kind.clone())
.bind(dto.pool_account.clone())
.bind(dto.token_a_mint.clone())
.bind(dto.token_b_mint.clone())
.bind(dto.payload_json.clone())
.bind(dto.executed_at.to_rfc3339())
.bind(id)
.execute(pool)
.await;
if let Err(error) = update_result {
return Err(crate::Error::Db(format!(
"cannot update k_sol_pool_lifecycle_events id '{}' on sqlite: {}",
id, error
)));
}
return Ok(id);
}
let insert_result = sqlx::query(
r#"
INSERT INTO k_sol_pool_lifecycle_events (
transaction_id,
decoded_event_id,
dex_id,
pool_id,
pair_id,
signature,
slot,
protocol_name,
program_id,
event_kind,
pool_account,
token_a_mint,
token_b_mint,
payload_json,
executed_at,
created_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"#,
)
.bind(dto.transaction_id)
.bind(dto.decoded_event_id)
.bind(dto.dex_id)
.bind(dto.pool_id)
.bind(dto.pair_id)
.bind(dto.signature.clone())
.bind(slot_i64)
.bind(dto.protocol_name.clone())
.bind(dto.program_id.clone())
.bind(dto.event_kind.clone())
.bind(dto.pool_account.clone())
.bind(dto.token_a_mint.clone())
.bind(dto.token_b_mint.clone())
.bind(dto.payload_json.clone())
.bind(dto.executed_at.to_rfc3339())
.bind(dto.created_at.to_rfc3339())
.execute(pool)
.await;
if let Err(error) = insert_result {
return Err(crate::Error::Db(format!(
"cannot insert k_sol_pool_lifecycle_events on sqlite: {}",
error
)));
}
let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
r#"
SELECT id
FROM k_sol_pool_lifecycle_events
WHERE transaction_id = ?
AND protocol_name = ?
AND event_kind = ?
AND signature = ?
ORDER BY id DESC
LIMIT 1
"#,
)
.bind(dto.transaction_id)
.bind(dto.protocol_name.clone())
.bind(dto.event_kind.clone())
.bind(dto.signature.clone())
.fetch_one(pool)
.await;
match id_result {
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot fetch inserted k_sol_pool_lifecycle_events id for signature '{}' on sqlite: {}",
dto.signature, error
)));
},
}
},
}
}
/// Lists recent pool lifecycle events ordered from newest to oldest.
pub async fn query_pool_lifecycle_events_list_recent(
database: &crate::Database,
limit: u32,
) -> Result<std::vec::Vec<crate::PoolLifecycleEventDto>, crate::Error> {
if limit == 0 {
return Ok(std::vec::Vec::new());
}
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::PoolLifecycleEventEntity>(
r#"
SELECT
id,
transaction_id,
decoded_event_id,
dex_id,
pool_id,
pair_id,
signature,
slot,
protocol_name,
program_id,
event_kind,
pool_account,
token_a_mint,
token_b_mint,
payload_json,
executed_at,
created_at
FROM k_sol_pool_lifecycle_events
ORDER BY id DESC
LIMIT ?
"#,
)
.bind(i64::from(limit))
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list k_sol_pool_lifecycle_events on sqlite: {}",
error
)));
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
let dto_result = crate::PoolLifecycleEventDto::try_from(entity);
let dto = match dto_result {
Ok(dto) => dto,
Err(error) => return Err(error),
};
dtos.push(dto);
}
return Ok(dtos);
},
}
}

View File

@@ -1072,13 +1072,17 @@ async fn create_tbl_liquidity_events(pool: &sqlx::SqlitePool) -> Result<(), crat
r#"
CREATE TABLE IF NOT EXISTS k_sol_liquidity_events (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
transaction_id INTEGER NULL,
decoded_event_id INTEGER NULL,
dex_id INTEGER NOT NULL,
pool_id INTEGER NOT NULL,
pair_id INTEGER NULL,
signature TEXT NOT NULL,
instruction_index INTEGER NOT NULL,
slot INTEGER NULL,
program_id TEXT NULL,
event_kind INTEGER NOT NULL,
event_kind_text TEXT NULL,
actor_wallet TEXT NULL,
base_token_id INTEGER NOT NULL,
quote_token_id INTEGER NOT NULL,
@@ -1086,7 +1090,12 @@ CREATE TABLE IF NOT EXISTS k_sol_liquidity_events (
base_amount TEXT NOT NULL,
quote_amount TEXT NOT NULL,
lp_amount TEXT NULL,
amounts_are_complete INTEGER NOT NULL DEFAULT 1,
payload_json TEXT NULL,
executed_at TEXT NOT NULL,
created_at TEXT NULL,
FOREIGN KEY(transaction_id) REFERENCES k_sol_chain_transactions(id),
FOREIGN KEY(decoded_event_id) REFERENCES k_sol_dex_decoded_events(id),
FOREIGN KEY(dex_id) REFERENCES k_sol_dexes(id),
FOREIGN KEY(pool_id) REFERENCES k_sol_pools(id),
FOREIGN KEY(pair_id) REFERENCES k_sol_pairs(id),

View File

@@ -9,6 +9,10 @@ pub enum LiquidityEventKind {
Add,
/// Liquidity removal.
Remove,
/// Concentrated-liquidity position opening without a guaranteed amount delta.
PositionOpen,
/// Concentrated-liquidity position closing without a guaranteed amount delta.
PositionClose,
}
impl LiquidityEventKind {
@@ -17,6 +21,8 @@ impl LiquidityEventKind {
match self {
Self::Add => return 0,
Self::Remove => return 1,
Self::PositionOpen => return 2,
Self::PositionClose => return 3,
}
}
@@ -25,6 +31,8 @@ impl LiquidityEventKind {
match value {
0 => return Ok(Self::Add),
1 => return Ok(Self::Remove),
2 => return Ok(Self::PositionOpen),
3 => return Ok(Self::PositionClose),
_ => {
return Err(crate::Error::Db(format!(
"invalid LiquidityEventKind value: {}",