0.7.49
This commit is contained in:
@@ -61,6 +61,7 @@ pub use dtos::LocalRaydiumSurfaceDiagnosticSummaryDto;
|
||||
pub use dtos::LocalTokenMetadataGapDiagnosticSampleDto;
|
||||
pub use dtos::ObservedTokenDto;
|
||||
pub use dtos::OnchainObservationDto;
|
||||
pub use dtos::OrderbookEventDto;
|
||||
pub use dtos::PairAnalyticSignalDto;
|
||||
pub use dtos::PairCandleDto;
|
||||
pub use dtos::PairDto;
|
||||
@@ -106,6 +107,7 @@ pub use entities::LaunchSurfaceKeyEntity;
|
||||
pub use entities::LiquidityEventEntity;
|
||||
pub use entities::ObservedTokenEntity;
|
||||
pub use entities::OnchainObservationEntity;
|
||||
pub use entities::OrderbookEventEntity;
|
||||
pub use entities::PairAnalyticSignalEntity;
|
||||
pub use entities::PairCandleEntity;
|
||||
pub use entities::PairEntity;
|
||||
@@ -152,8 +154,10 @@ pub use queries::query_dex_decode_replay_ledger_get_by_signature;
|
||||
pub use queries::query_dex_decode_replay_ledger_get_by_transaction;
|
||||
pub use queries::query_dex_decode_replay_ledger_upsert;
|
||||
pub use queries::query_dex_decoded_events_delete_by_key;
|
||||
pub use queries::query_dex_decoded_events_delete_locally_covered_upstream_instruction_matches;
|
||||
pub use queries::query_dex_decoded_events_delete_meteora_dlmm_anchor_swap_instruction_audits;
|
||||
pub use queries::query_dex_decoded_events_delete_related_instruction_audit;
|
||||
pub use queries::query_dex_decoded_events_delete_replaced_raydium_clmm_instruction_audits;
|
||||
pub use queries::query_dex_decoded_events_get_by_key;
|
||||
pub use queries::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint;
|
||||
pub use queries::query_dex_decoded_events_list_by_transaction_id;
|
||||
@@ -216,6 +220,7 @@ pub use queries::query_observed_tokens_list;
|
||||
pub use queries::query_observed_tokens_upsert;
|
||||
pub use queries::query_onchain_observations_insert;
|
||||
pub use queries::query_onchain_observations_list_recent;
|
||||
pub use queries::query_orderbook_events_upsert;
|
||||
pub use queries::query_pair_analytic_signals_get_by_key;
|
||||
pub use queries::query_pair_analytic_signals_list_by_pair_id;
|
||||
pub use queries::query_pair_analytic_signals_upsert;
|
||||
|
||||
@@ -24,6 +24,7 @@ mod local_dex_corpus_search;
|
||||
mod local_pipeline_diagnostics;
|
||||
mod observed_token;
|
||||
mod onchain_observation;
|
||||
mod orderbook_event;
|
||||
mod pair;
|
||||
mod pair_analytic_signal;
|
||||
mod pair_candle;
|
||||
@@ -117,6 +118,7 @@ pub use local_pipeline_diagnostics::LocalRaydiumSurfaceDiagnosticSummaryDto;
|
||||
pub use local_pipeline_diagnostics::LocalTokenMetadataGapDiagnosticSampleDto;
|
||||
pub use observed_token::ObservedTokenDto;
|
||||
pub use onchain_observation::OnchainObservationDto;
|
||||
pub use orderbook_event::OrderbookEventDto;
|
||||
pub use pair::PairDto;
|
||||
pub use pair_analytic_signal::PairAnalyticSignalDto;
|
||||
pub use pair_candle::PairCandleDto;
|
||||
|
||||
190
kb_lib/src/db/dtos/orderbook_event.rs
Normal file
190
kb_lib/src/db/dtos/orderbook_event.rs
Normal file
@@ -0,0 +1,190 @@
|
||||
// file: kb_lib/src/db/dtos/orderbook_event.rs
|
||||
|
||||
//! Orderbook event DTO.
|
||||
|
||||
/// Application-facing normalized orderbook or limit-order event DTO.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct OrderbookEventDto {
|
||||
/// 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,
|
||||
/// Normalized orderbook action.
|
||||
pub order_action: std::string::String,
|
||||
/// Pool account address, when decoded.
|
||||
pub pool_account: std::option::Option<std::string::String>,
|
||||
/// Market/orderbook account, when decoded.
|
||||
pub market_account: std::option::Option<std::string::String>,
|
||||
/// Wallet or authority associated with the event, when decoded.
|
||||
pub actor_wallet: std::option::Option<std::string::String>,
|
||||
/// Limit/order account, when decoded.
|
||||
pub order_account: std::option::Option<std::string::String>,
|
||||
/// Base or token A mint, when decoded.
|
||||
pub base_token_mint: std::option::Option<std::string::String>,
|
||||
/// Quote or token B mint, when decoded.
|
||||
pub quote_token_mint: std::option::Option<std::string::String>,
|
||||
/// Raw order amount as decimal text, when decoded.
|
||||
pub amount_raw: std::option::Option<std::string::String>,
|
||||
/// Raw minimum amount as decimal text, when decoded.
|
||||
pub amount_min_raw: std::option::Option<std::string::String>,
|
||||
/// Optional tick index for CLMM limit orders.
|
||||
pub tick_index: std::option::Option<i64>,
|
||||
/// Optional zero-for-one side flag.
|
||||
pub zero_for_one: std::option::Option<bool>,
|
||||
/// 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 OrderbookEventDto {
|
||||
/// Creates a new orderbook 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,
|
||||
order_action: std::string::String,
|
||||
pool_account: std::option::Option<std::string::String>,
|
||||
market_account: std::option::Option<std::string::String>,
|
||||
actor_wallet: std::option::Option<std::string::String>,
|
||||
order_account: std::option::Option<std::string::String>,
|
||||
base_token_mint: std::option::Option<std::string::String>,
|
||||
quote_token_mint: std::option::Option<std::string::String>,
|
||||
amount_raw: std::option::Option<std::string::String>,
|
||||
amount_min_raw: std::option::Option<std::string::String>,
|
||||
tick_index: std::option::Option<i64>,
|
||||
zero_for_one: std::option::Option<bool>,
|
||||
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,
|
||||
order_action,
|
||||
pool_account,
|
||||
market_account,
|
||||
actor_wallet,
|
||||
order_account,
|
||||
base_token_mint,
|
||||
quote_token_mint,
|
||||
amount_raw,
|
||||
amount_min_raw,
|
||||
tick_index,
|
||||
zero_for_one,
|
||||
payload_json,
|
||||
executed_at: now,
|
||||
created_at: now,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<crate::OrderbookEventEntity> for OrderbookEventDto {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(entity: crate::OrderbookEventEntity) -> 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 orderbook 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 orderbook 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 orderbook event slot '{}' to u64: {}",
|
||||
slot, error
|
||||
)));
|
||||
},
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
let zero_for_one = match entity.zero_for_one {
|
||||
Some(0) => Some(false),
|
||||
Some(_) => Some(true),
|
||||
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,
|
||||
order_action: entity.order_action,
|
||||
pool_account: entity.pool_account,
|
||||
market_account: entity.market_account,
|
||||
actor_wallet: entity.actor_wallet,
|
||||
order_account: entity.order_account,
|
||||
base_token_mint: entity.base_token_mint,
|
||||
quote_token_mint: entity.quote_token_mint,
|
||||
amount_raw: entity.amount_raw,
|
||||
amount_min_raw: entity.amount_min_raw,
|
||||
tick_index: entity.tick_index,
|
||||
zero_for_one,
|
||||
payload_json: entity.payload_json,
|
||||
executed_at,
|
||||
created_at,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ mod launch_surface_key;
|
||||
mod liquidity_event;
|
||||
mod observed_token;
|
||||
mod onchain_observation;
|
||||
mod orderbook_event;
|
||||
mod pair;
|
||||
mod pair_analytic_signal;
|
||||
mod pair_candle;
|
||||
@@ -70,6 +71,7 @@ pub use launch_surface_key::LaunchSurfaceKeyEntity;
|
||||
pub use liquidity_event::LiquidityEventEntity;
|
||||
pub use observed_token::ObservedTokenEntity;
|
||||
pub use onchain_observation::OnchainObservationEntity;
|
||||
pub use orderbook_event::OrderbookEventEntity;
|
||||
pub use pair::PairEntity;
|
||||
pub use pair_analytic_signal::PairAnalyticSignalEntity;
|
||||
pub use pair_candle::PairCandleEntity;
|
||||
|
||||
58
kb_lib/src/db/entities/orderbook_event.rs
Normal file
58
kb_lib/src/db/entities/orderbook_event.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
// file: kb_lib/src/db/entities/orderbook_event.rs
|
||||
|
||||
//! Orderbook event entity.
|
||||
|
||||
/// Persisted normalized orderbook or limit-order event row.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
|
||||
pub struct OrderbookEventEntity {
|
||||
/// 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,
|
||||
/// Normalized orderbook action.
|
||||
pub order_action: std::string::String,
|
||||
/// Pool account address, when decoded.
|
||||
pub pool_account: std::option::Option<std::string::String>,
|
||||
/// Market/orderbook account, when decoded.
|
||||
pub market_account: std::option::Option<std::string::String>,
|
||||
/// Wallet or authority associated with the event, when decoded.
|
||||
pub actor_wallet: std::option::Option<std::string::String>,
|
||||
/// Limit/order account, when decoded.
|
||||
pub order_account: std::option::Option<std::string::String>,
|
||||
/// Base or token A mint, when decoded.
|
||||
pub base_token_mint: std::option::Option<std::string::String>,
|
||||
/// Quote or token B mint, when decoded.
|
||||
pub quote_token_mint: std::option::Option<std::string::String>,
|
||||
/// Raw order amount as decimal text, when decoded.
|
||||
pub amount_raw: std::option::Option<std::string::String>,
|
||||
/// Raw minimum amount as decimal text, when decoded.
|
||||
pub amount_min_raw: std::option::Option<std::string::String>,
|
||||
/// Optional tick index for CLMM limit orders.
|
||||
pub tick_index: std::option::Option<i64>,
|
||||
/// Optional zero-for-one side flag, stored as 0/1.
|
||||
pub zero_for_one: std::option::Option<i64>,
|
||||
/// 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,
|
||||
}
|
||||
@@ -13,9 +13,9 @@ mod dex_decode_replay_ledger;
|
||||
mod dex_decoded_event;
|
||||
mod dex_event_coverage_entry;
|
||||
mod fee_event;
|
||||
mod instruction_observation;
|
||||
mod known_http_endpoint;
|
||||
mod known_ws_endpoint;
|
||||
mod instruction_observation;
|
||||
mod launch_attribution;
|
||||
mod launch_surface;
|
||||
mod launch_surface_key;
|
||||
@@ -24,6 +24,7 @@ mod local_dex_corpus_search;
|
||||
mod local_pipeline_diagnostics;
|
||||
mod observed_token;
|
||||
mod onchain_observation;
|
||||
mod orderbook_event;
|
||||
mod pair;
|
||||
mod pair_analytic_signal;
|
||||
mod pair_candle;
|
||||
@@ -72,8 +73,10 @@ pub use dex_decode_replay_ledger::query_dex_decode_replay_ledger_get_by_signatur
|
||||
pub use dex_decode_replay_ledger::query_dex_decode_replay_ledger_get_by_transaction;
|
||||
pub use dex_decode_replay_ledger::query_dex_decode_replay_ledger_upsert;
|
||||
pub use dex_decoded_event::query_dex_decoded_events_delete_by_key;
|
||||
pub use dex_decoded_event::query_dex_decoded_events_delete_locally_covered_upstream_instruction_matches;
|
||||
pub use dex_decoded_event::query_dex_decoded_events_delete_meteora_dlmm_anchor_swap_instruction_audits;
|
||||
pub use dex_decoded_event::query_dex_decoded_events_delete_related_instruction_audit;
|
||||
pub use dex_decoded_event::query_dex_decoded_events_delete_replaced_raydium_clmm_instruction_audits;
|
||||
pub use dex_decoded_event::query_dex_decoded_events_get_by_key;
|
||||
pub use dex_decoded_event::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint;
|
||||
pub use dex_decoded_event::query_dex_decoded_events_list_by_transaction_id;
|
||||
@@ -87,14 +90,14 @@ pub use dex_event_coverage_entry::query_dex_event_coverage_entries_upsert;
|
||||
pub use fee_event::query_fee_events_get_by_decoded_event_id;
|
||||
pub use fee_event::query_fee_events_list_recent;
|
||||
pub use fee_event::query_fee_events_upsert;
|
||||
pub use instruction_observation::query_instruction_observations_list_by_filter;
|
||||
pub use instruction_observation::query_instruction_observations_upsert;
|
||||
pub use known_http_endpoint::query_known_http_endpoints_get;
|
||||
pub use known_http_endpoint::query_known_http_endpoints_list;
|
||||
pub use known_http_endpoint::query_known_http_endpoints_upsert;
|
||||
pub use known_ws_endpoint::query_known_ws_endpoints_get;
|
||||
pub use known_ws_endpoint::query_known_ws_endpoints_list;
|
||||
pub use known_ws_endpoint::query_known_ws_endpoints_upsert;
|
||||
pub use instruction_observation::query_instruction_observations_list_by_filter;
|
||||
pub use instruction_observation::query_instruction_observations_upsert;
|
||||
pub use launch_attribution::query_launch_attributions_get_by_decoded_event_id;
|
||||
pub use launch_attribution::query_launch_attributions_list_by_pool_id;
|
||||
pub use launch_attribution::query_launch_attributions_upsert;
|
||||
@@ -133,6 +136,7 @@ pub use observed_token::query_observed_tokens_list;
|
||||
pub use observed_token::query_observed_tokens_upsert;
|
||||
pub use onchain_observation::query_onchain_observations_insert;
|
||||
pub use onchain_observation::query_onchain_observations_list_recent;
|
||||
pub use orderbook_event::query_orderbook_events_upsert;
|
||||
pub use pair::query_pairs_get_by_pool_id;
|
||||
pub use pair::query_pairs_list;
|
||||
pub use pair::query_pairs_update_symbol;
|
||||
|
||||
@@ -191,6 +191,185 @@ WHERE transaction_id = ?
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes Raydium CLMM instruction-audit rows for locally mapped CLMM instructions.
|
||||
///
|
||||
/// The CLMM specialized decoder now emits named `raydium_clmm.*` rows for all
|
||||
/// locally mapped instruction discriminants. Keeping the former
|
||||
/// `raydium_clmm.instruction_audit` rows for the same discriminants creates
|
||||
/// duplicate coverage and makes residual audit diagnostics noisy. Unknown
|
||||
/// discriminants remain untouched because they are intentionally absent from
|
||||
/// this allow-list.
|
||||
pub async fn query_dex_decoded_events_delete_replaced_raydium_clmm_instruction_audits(
|
||||
database: &crate::Database,
|
||||
transaction_id: std::option::Option<i64>,
|
||||
) -> Result<u64, crate::Error> {
|
||||
match database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
let query_result = sqlx::query(
|
||||
r#"
|
||||
DELETE FROM k_sol_dex_decoded_events
|
||||
WHERE protocol_name = 'raydium_clmm'
|
||||
AND event_kind = 'raydium_clmm.instruction_audit'
|
||||
AND (? IS NULL OR transaction_id = ?)
|
||||
AND COALESCE(
|
||||
json_extract(payload_json, '$.discriminatorHex'),
|
||||
json_extract(payload_json, '$.discriminator_hex'),
|
||||
json_extract(payload_json, '$.instructionDiscriminatorHex'),
|
||||
json_extract(payload_json, '$.instruction_discriminator_hex')
|
||||
) IN (
|
||||
'4c7c800fd55725fa',
|
||||
'9d20dab7471d1293',
|
||||
'b19059ecfaba7d63',
|
||||
'759d3c674231a300',
|
||||
'7b86510031446262',
|
||||
'c975989055556cb2',
|
||||
'a78a4e95dfc2067e',
|
||||
'8888fcddc2427e59',
|
||||
'12eda6c52210d590',
|
||||
'8934edd4d7756c68',
|
||||
'2b44d4a7592fa401',
|
||||
'bd0eb5785576e33e',
|
||||
'3f5794216d230868',
|
||||
'e992d18ecf6840bc',
|
||||
'11fb415c88f20ea9',
|
||||
'a026d06f685b2c01',
|
||||
'3a7fbc3e4f52c460',
|
||||
'2e9cf3760dcdfbb2',
|
||||
'851d59df45eeb00a',
|
||||
'5f87c0c4f281e644',
|
||||
'87802f4d0f98f031',
|
||||
'4db84ad67056f1c7',
|
||||
'4dffae527d1dc92e',
|
||||
'7034a74b20c9d389',
|
||||
'cd4e74215c691a60',
|
||||
'f8c69e91e17587c8',
|
||||
'457d73daf5baf2c4',
|
||||
'2b04ed0b1ac91e62',
|
||||
'07160c53f22b3079',
|
||||
'313cae889a1c74c8',
|
||||
'7f467728bce33d07',
|
||||
'82576c062ee0757b',
|
||||
'a3ace0340b9a6adf'
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.bind(transaction_id)
|
||||
.bind(transaction_id)
|
||||
.execute(pool)
|
||||
.await;
|
||||
match query_result {
|
||||
Ok(result) => return Ok(result.rows_affected()),
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot delete mapped Raydium CLMM instruction audit events on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes upstream registry instruction-match rows already covered by specialized local decoders.
|
||||
///
|
||||
/// The upstream registry fallback is useful only while an instruction is not yet
|
||||
/// handled by a protocol-specific decoder. Once `k_sol_dex_event_coverage_entries`
|
||||
/// has a non-empty `local_event_kind` for the same decoder/entry/discriminator,
|
||||
/// the fallback row is redundant and must be removed so coverage queries do not
|
||||
/// report both `upstream_git.instruction_match` and the local `protocol.*` event.
|
||||
pub async fn query_dex_decoded_events_delete_locally_covered_upstream_instruction_matches(
|
||||
database: &crate::Database,
|
||||
upstream_decoder_code: std::option::Option<&str>,
|
||||
) -> Result<u64, crate::Error> {
|
||||
match database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
let unlink_result = sqlx::query(
|
||||
r#"
|
||||
UPDATE k_sol_instruction_observations
|
||||
SET decoded_event_id = NULL
|
||||
WHERE decoded_event_id IN (
|
||||
SELECT de.id
|
||||
FROM k_sol_dex_decoded_events de
|
||||
WHERE de.protocol_name = 'upstream_git'
|
||||
AND de.event_kind = 'upstream_git.instruction_match'
|
||||
AND (? IS NULL OR json_extract(de.payload_json, '$.upstreamDecoderCode') = ?)
|
||||
AND (
|
||||
COALESCE(json_extract(de.payload_json, '$.upstreamDecoderCode'), '')
|
||||
|| '|'
|
||||
|| COALESCE(json_extract(de.payload_json, '$.upstreamEntryName'), '')
|
||||
|| '|'
|
||||
|| COALESCE(json_extract(de.payload_json, '$.upstreamDiscriminatorHex'), '')
|
||||
) IN (
|
||||
SELECT
|
||||
ce.decoder_code
|
||||
|| '|'
|
||||
|| ce.entry_name
|
||||
|| '|'
|
||||
|| ce.discriminator_hex
|
||||
FROM k_sol_dex_event_coverage_entries ce
|
||||
WHERE ce.local_event_kind IS NOT NULL
|
||||
AND ce.local_event_kind <> ''
|
||||
AND ce.discriminator_hex IS NOT NULL
|
||||
AND ce.discriminator_hex <> ''
|
||||
)
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.bind(upstream_decoder_code)
|
||||
.bind(upstream_decoder_code)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = unlink_result {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot unlink locally covered upstream instruction matches from instruction observations on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
|
||||
let query_result = sqlx::query(
|
||||
r#"
|
||||
DELETE FROM k_sol_dex_decoded_events
|
||||
WHERE protocol_name = 'upstream_git'
|
||||
AND event_kind = 'upstream_git.instruction_match'
|
||||
AND (? IS NULL OR json_extract(payload_json, '$.upstreamDecoderCode') = ?)
|
||||
AND (
|
||||
COALESCE(json_extract(payload_json, '$.upstreamDecoderCode'), '')
|
||||
|| '|'
|
||||
|| COALESCE(json_extract(payload_json, '$.upstreamEntryName'), '')
|
||||
|| '|'
|
||||
|| COALESCE(json_extract(payload_json, '$.upstreamDiscriminatorHex'), '')
|
||||
) IN (
|
||||
SELECT
|
||||
ce.decoder_code
|
||||
|| '|'
|
||||
|| ce.entry_name
|
||||
|| '|'
|
||||
|| ce.discriminator_hex
|
||||
FROM k_sol_dex_event_coverage_entries ce
|
||||
WHERE ce.local_event_kind IS NOT NULL
|
||||
AND ce.local_event_kind <> ''
|
||||
AND ce.discriminator_hex IS NOT NULL
|
||||
AND ce.discriminator_hex <> ''
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.bind(upstream_decoder_code)
|
||||
.bind(upstream_decoder_code)
|
||||
.execute(pool)
|
||||
.await;
|
||||
match query_result {
|
||||
Ok(result) => return Ok(result.rows_affected()),
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot delete locally covered upstream instruction matches on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes Meteora DLMM Anchor self-CPI swap audit rows already covered by decoded swaps.
|
||||
///
|
||||
/// This targets only local-corpus-observed Anchor event discriminators that are
|
||||
|
||||
@@ -569,6 +569,41 @@ SET
|
||||
)
|
||||
)
|
||||
)
|
||||
WHEN expected_db_target = 'k_sol_orderbook_events' THEN (
|
||||
SELECT COUNT(oe.id)
|
||||
FROM k_sol_dex_decoded_events de
|
||||
JOIN k_sol_orderbook_events oe ON oe.decoded_event_id = de.id
|
||||
WHERE (
|
||||
(k_sol_dex_event_coverage_entries.program_id IS NULL OR de.program_id = k_sol_dex_event_coverage_entries.program_id)
|
||||
AND (
|
||||
(
|
||||
k_sol_dex_event_coverage_entries.local_event_kind IS NOT NULL
|
||||
AND k_sol_dex_event_coverage_entries.local_event_kind <> ''
|
||||
AND de.event_kind = k_sol_dex_event_coverage_entries.local_event_kind
|
||||
)
|
||||
OR (
|
||||
k_sol_dex_event_coverage_entries.entry_name IS NOT NULL
|
||||
AND (
|
||||
json_extract(de.payload_json, '$.upstreamEntryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||
OR json_extract(de.payload_json, '$.upstreamInstructionName') = k_sol_dex_event_coverage_entries.entry_name
|
||||
OR json_extract(de.payload_json, '$.upstreamEventName') = k_sol_dex_event_coverage_entries.entry_name
|
||||
OR json_extract(de.payload_json, '$.entryName') = k_sol_dex_event_coverage_entries.entry_name
|
||||
)
|
||||
)
|
||||
OR (
|
||||
k_sol_dex_event_coverage_entries.discriminator_hex IS NOT NULL
|
||||
AND k_sol_dex_event_coverage_entries.discriminator_hex <> ''
|
||||
AND (
|
||||
json_extract(de.payload_json, '$.upstreamDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||
OR json_extract(de.payload_json, '$.instructionDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||
OR json_extract(de.payload_json, '$.anchorEventDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||
OR json_extract(de.payload_json, '$.anchorEventDiscriminator') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||
OR json_extract(de.payload_json, '$.discriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
ELSE materialized_count
|
||||
END,
|
||||
first_signature = (
|
||||
|
||||
212
kb_lib/src/db/queries/orderbook_event.rs
Normal file
212
kb_lib/src/db/queries/orderbook_event.rs
Normal file
@@ -0,0 +1,212 @@
|
||||
// file: kb_lib/src/db/queries/orderbook_event.rs
|
||||
|
||||
//! Queries for `k_sol_orderbook_events`.
|
||||
|
||||
/// Inserts or updates one normalized orderbook event row.
|
||||
pub async fn query_orderbook_events_upsert(
|
||||
database: &crate::Database,
|
||||
dto: &crate::OrderbookEventDto,
|
||||
) -> 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 orderbook event slot '{}' to i64: {}",
|
||||
slot, error
|
||||
)));
|
||||
},
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
let zero_for_one_i64 = match dto.zero_for_one {
|
||||
Some(true) => Some(1_i64),
|
||||
Some(false) => Some(0_i64),
|
||||
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_orderbook_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_orderbook_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_orderbook_events
|
||||
SET
|
||||
transaction_id = ?,
|
||||
dex_id = ?,
|
||||
pool_id = ?,
|
||||
pair_id = ?,
|
||||
signature = ?,
|
||||
slot = ?,
|
||||
protocol_name = ?,
|
||||
program_id = ?,
|
||||
event_kind = ?,
|
||||
order_action = ?,
|
||||
pool_account = ?,
|
||||
market_account = ?,
|
||||
actor_wallet = ?,
|
||||
order_account = ?,
|
||||
base_token_mint = ?,
|
||||
quote_token_mint = ?,
|
||||
amount_raw = ?,
|
||||
amount_min_raw = ?,
|
||||
tick_index = ?,
|
||||
zero_for_one = ?,
|
||||
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.order_action.clone())
|
||||
.bind(dto.pool_account.clone())
|
||||
.bind(dto.market_account.clone())
|
||||
.bind(dto.actor_wallet.clone())
|
||||
.bind(dto.order_account.clone())
|
||||
.bind(dto.base_token_mint.clone())
|
||||
.bind(dto.quote_token_mint.clone())
|
||||
.bind(dto.amount_raw.clone())
|
||||
.bind(dto.amount_min_raw.clone())
|
||||
.bind(dto.tick_index)
|
||||
.bind(zero_for_one_i64)
|
||||
.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_orderbook_events id '{}' on sqlite: {}",
|
||||
id, error
|
||||
)));
|
||||
}
|
||||
return Ok(id);
|
||||
}
|
||||
let insert_result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO k_sol_orderbook_events (
|
||||
transaction_id,
|
||||
decoded_event_id,
|
||||
dex_id,
|
||||
pool_id,
|
||||
pair_id,
|
||||
signature,
|
||||
slot,
|
||||
protocol_name,
|
||||
program_id,
|
||||
event_kind,
|
||||
order_action,
|
||||
pool_account,
|
||||
market_account,
|
||||
actor_wallet,
|
||||
order_account,
|
||||
base_token_mint,
|
||||
quote_token_mint,
|
||||
amount_raw,
|
||||
amount_min_raw,
|
||||
tick_index,
|
||||
zero_for_one,
|
||||
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.order_action.clone())
|
||||
.bind(dto.pool_account.clone())
|
||||
.bind(dto.market_account.clone())
|
||||
.bind(dto.actor_wallet.clone())
|
||||
.bind(dto.order_account.clone())
|
||||
.bind(dto.base_token_mint.clone())
|
||||
.bind(dto.quote_token_mint.clone())
|
||||
.bind(dto.amount_raw.clone())
|
||||
.bind(dto.amount_min_raw.clone())
|
||||
.bind(dto.tick_index)
|
||||
.bind(zero_for_one_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) = insert_result {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot insert k_sol_orderbook_events on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
|
||||
r#"
|
||||
SELECT id
|
||||
FROM k_sol_orderbook_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_orderbook_events id for signature '{}' on sqlite: {}",
|
||||
dto.signature, error
|
||||
)));
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,67 @@ pub async fn query_trade_market_materialization_delete_all(
|
||||
("k_sol_pair_analytic_signals", "DELETE FROM k_sol_pair_analytic_signals"),
|
||||
("k_sol_pair_candles", "DELETE FROM k_sol_pair_candles"),
|
||||
("k_sol_pair_metrics", "DELETE FROM k_sol_pair_metrics"),
|
||||
("k_sol_orderbook_events", "DELETE FROM k_sol_orderbook_events"),
|
||||
("k_sol_trade_events", "DELETE FROM k_sol_trade_events"),
|
||||
(
|
||||
"locally_covered_upstream_instruction_observation_links",
|
||||
r#"
|
||||
UPDATE k_sol_instruction_observations
|
||||
SET decoded_event_id = NULL
|
||||
WHERE decoded_event_id IN (
|
||||
SELECT de.id
|
||||
FROM k_sol_dex_decoded_events de
|
||||
WHERE de.protocol_name = 'upstream_git'
|
||||
AND de.event_kind = 'upstream_git.instruction_match'
|
||||
AND (
|
||||
COALESCE(json_extract(de.payload_json, '$.upstreamDecoderCode'), '')
|
||||
|| '|'
|
||||
|| COALESCE(json_extract(de.payload_json, '$.upstreamEntryName'), '')
|
||||
|| '|'
|
||||
|| COALESCE(json_extract(de.payload_json, '$.upstreamDiscriminatorHex'), '')
|
||||
) IN (
|
||||
SELECT
|
||||
ce.decoder_code
|
||||
|| '|'
|
||||
|| ce.entry_name
|
||||
|| '|'
|
||||
|| ce.discriminator_hex
|
||||
FROM k_sol_dex_event_coverage_entries ce
|
||||
WHERE ce.local_event_kind IS NOT NULL
|
||||
AND ce.local_event_kind <> ''
|
||||
AND ce.discriminator_hex IS NOT NULL
|
||||
AND ce.discriminator_hex <> ''
|
||||
)
|
||||
)
|
||||
"#,
|
||||
),
|
||||
(
|
||||
"locally_covered_upstream_instruction_matches",
|
||||
r#"
|
||||
DELETE FROM k_sol_dex_decoded_events
|
||||
WHERE protocol_name = 'upstream_git'
|
||||
AND event_kind = 'upstream_git.instruction_match'
|
||||
AND (
|
||||
COALESCE(json_extract(payload_json, '$.upstreamDecoderCode'), '')
|
||||
|| '|'
|
||||
|| COALESCE(json_extract(payload_json, '$.upstreamEntryName'), '')
|
||||
|| '|'
|
||||
|| COALESCE(json_extract(payload_json, '$.upstreamDiscriminatorHex'), '')
|
||||
) IN (
|
||||
SELECT
|
||||
ce.decoder_code
|
||||
|| '|'
|
||||
|| ce.entry_name
|
||||
|| '|'
|
||||
|| ce.discriminator_hex
|
||||
FROM k_sol_dex_event_coverage_entries ce
|
||||
WHERE ce.local_event_kind IS NOT NULL
|
||||
AND ce.local_event_kind <> ''
|
||||
AND ce.discriminator_hex IS NOT NULL
|
||||
AND ce.discriminator_hex <> ''
|
||||
)
|
||||
"#,
|
||||
),
|
||||
];
|
||||
let mut deleted_count = 0_u64;
|
||||
for (table_name, statement) in statements {
|
||||
|
||||
@@ -374,6 +374,22 @@ pub(crate) async fn ensure_schema(database: &crate::Database) -> Result<(), crat
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
}
|
||||
let result = create_tbl_orderbook_events(pool).await;
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
}
|
||||
let result = create_idx_orderbook_events_transaction_id(pool).await;
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
}
|
||||
let result = create_idx_orderbook_events_pool_id(pool).await;
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
}
|
||||
let result = create_uix_orderbook_events_decoded_event_id(pool).await;
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
}
|
||||
let result = create_tbl_launch_surfaces(pool).await;
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
@@ -2710,3 +2726,85 @@ WHERE decoded_event_id IS NOT NULL
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Creates `k_sol_orderbook_events`.
|
||||
async fn create_tbl_orderbook_events(pool: &sqlx::SqlitePool) -> Result<(), crate::Error> {
|
||||
return execute_sqlite_schema_statement(
|
||||
pool,
|
||||
"create_tbl_orderbook_events",
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS k_sol_orderbook_events (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
transaction_id INTEGER NOT NULL,
|
||||
decoded_event_id INTEGER NULL,
|
||||
dex_id INTEGER NULL,
|
||||
pool_id INTEGER NULL,
|
||||
pair_id INTEGER NULL,
|
||||
signature TEXT NOT NULL,
|
||||
slot INTEGER NULL,
|
||||
protocol_name TEXT NOT NULL,
|
||||
program_id TEXT NOT NULL,
|
||||
event_kind TEXT NOT NULL,
|
||||
order_action TEXT NOT NULL,
|
||||
pool_account TEXT NULL,
|
||||
market_account TEXT NULL,
|
||||
actor_wallet TEXT NULL,
|
||||
order_account TEXT NULL,
|
||||
base_token_mint TEXT NULL,
|
||||
quote_token_mint TEXT NULL,
|
||||
amount_raw TEXT NULL,
|
||||
amount_min_raw TEXT NULL,
|
||||
tick_index INTEGER NULL,
|
||||
zero_for_one INTEGER NULL,
|
||||
payload_json TEXT NOT NULL,
|
||||
executed_at TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Creates index on `k_sol_orderbook_events(transaction_id)`.
|
||||
async fn create_idx_orderbook_events_transaction_id(
|
||||
pool: &sqlx::SqlitePool,
|
||||
) -> Result<(), crate::Error> {
|
||||
return execute_sqlite_schema_statement(
|
||||
pool,
|
||||
"create_idx_orderbook_events_transaction_id",
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS idx_orderbook_events_transaction_id
|
||||
ON k_sol_orderbook_events (transaction_id)
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Creates index on `k_sol_orderbook_events(pool_id)`.
|
||||
async fn create_idx_orderbook_events_pool_id(pool: &sqlx::SqlitePool) -> Result<(), crate::Error> {
|
||||
return execute_sqlite_schema_statement(
|
||||
pool,
|
||||
"create_idx_orderbook_events_pool_id",
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS idx_orderbook_events_pool_id
|
||||
ON k_sol_orderbook_events (pool_id)
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Creates partial unique index on `k_sol_orderbook_events(decoded_event_id)`.
|
||||
async fn create_uix_orderbook_events_decoded_event_id(
|
||||
pool: &sqlx::SqlitePool,
|
||||
) -> Result<(), crate::Error> {
|
||||
return execute_sqlite_schema_statement(
|
||||
pool,
|
||||
"create_uix_orderbook_events_decoded_event_id",
|
||||
r#"
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uix_orderbook_events_decoded_event_id
|
||||
ON k_sol_orderbook_events (decoded_event_id)
|
||||
WHERE decoded_event_id IS NOT NULL
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -70,12 +70,16 @@ pub use raydium_amm_v4::RaydiumAmmV4DecodedEvent;
|
||||
pub use raydium_amm_v4::RaydiumAmmV4Decoder;
|
||||
pub use raydium_amm_v4::RaydiumAmmV4Initialize2PoolDecoded;
|
||||
pub use raydium_amm_v4::RaydiumAmmV4SwapDecoded;
|
||||
pub use raydium_clmm::RaydiumClmmCollectProtocolFeeDecoded;
|
||||
pub use raydium_clmm::RaydiumClmmCreatePoolDecoded;
|
||||
pub use raydium_clmm::RaydiumClmmDecodedEvent;
|
||||
pub use raydium_clmm::RaydiumClmmDecodedInstructionEvent;
|
||||
pub use raydium_clmm::RaydiumClmmProgramDataEventDecoded;
|
||||
pub use raydium_clmm::RaydiumClmmDecoder;
|
||||
pub use raydium_clmm::RaydiumClmmSwapLegacyDecoded;
|
||||
pub use raydium_clmm::RaydiumClmmSwapV2Decoded;
|
||||
pub use raydium_clmm::decode_raydium_clmm_instruction;
|
||||
pub use raydium_clmm::decode_raydium_clmm_program_data_event;
|
||||
pub use raydium_cpmm::RaydiumCpmmDecodedEvent;
|
||||
pub use raydium_cpmm::RaydiumCpmmLpChangeEventDecoded;
|
||||
pub use raydium_cpmm::RaydiumCpmmSwapDecoded;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -148,7 +148,7 @@ fn prepare_payload_for_transaction_status(
|
||||
transaction: &crate::ChainTransactionDto,
|
||||
payload_json: serde_json::Value,
|
||||
) -> serde_json::Value {
|
||||
if transaction.err_json.is_none() {
|
||||
if !transaction_has_effective_error(transaction) {
|
||||
return payload_json;
|
||||
}
|
||||
let mut object = match payload_json {
|
||||
@@ -177,3 +177,17 @@ fn prepare_payload_for_transaction_status(
|
||||
);
|
||||
return serde_json::Value::Object(object);
|
||||
}
|
||||
|
||||
fn transaction_has_effective_error(transaction: &crate::ChainTransactionDto) -> bool {
|
||||
let err_json = match transaction.err_json.as_ref() {
|
||||
Some(err_json) => err_json.trim(),
|
||||
None => return false,
|
||||
};
|
||||
if err_json.is_empty() {
|
||||
return false;
|
||||
}
|
||||
if err_json == "null" {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -238,9 +238,15 @@ pub fn classify_dex_event_actionability(
|
||||
if trade_candidate {
|
||||
return DexEventActionability::TradeCandidate;
|
||||
}
|
||||
if is_dex_informational_event_kind(event_kind) {
|
||||
return DexEventActionability::Informational;
|
||||
}
|
||||
if is_dex_trade_event_kind(event_kind) {
|
||||
return DexEventActionability::NonActionableTrade;
|
||||
}
|
||||
if is_dex_orderbook_event_kind(event_kind) {
|
||||
return DexEventActionability::NonTradeUseful;
|
||||
}
|
||||
let category = classify_dex_event_category(event_kind);
|
||||
match category {
|
||||
DexEventCategory::Liquidity => return DexEventActionability::NonTradeUseful,
|
||||
@@ -323,6 +329,12 @@ pub fn is_dex_liquidity_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.contains(".lp_change_event") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".liquidity_change_event") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".liquidity_calculate_event") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".withdraw") {
|
||||
return true;
|
||||
}
|
||||
@@ -341,6 +353,9 @@ pub fn is_dex_liquidity_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.contains(".initialize_position") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".create_personal_position_event") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".open_position") {
|
||||
return true;
|
||||
}
|
||||
@@ -440,6 +455,38 @@ pub fn is_dex_reward_event_kind(event_kind: &str) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Returns true for orderbook or limit-order events that must not become candles.
|
||||
pub fn is_dex_orderbook_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.contains(".order_place") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".order_cancel") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".order_fill") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".settle_funds") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".open_limit_order") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".increase_limit_order") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".decrease_limit_order") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".close_limit_order") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".settle_limit_order") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Returns true for pool, pair, launch, mint, burn or migration lifecycle events.
|
||||
pub fn is_dex_pool_lifecycle_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.contains(".create_lock_escrow") {
|
||||
@@ -539,6 +586,9 @@ pub fn is_dex_pool_creation_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.contains(".create_pool") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".pool_created_event") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".create_amm") {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -214,6 +214,9 @@ fn infer_expected_db_target_for_entry(
|
||||
if decoder_code == "raydium_cpmm" && entry_name == "swap_event" {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string());
|
||||
}
|
||||
if decoder_code == "raydium_clmm" && entry_name == "initialize_reward" {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_REWARD_EVENTS.to_string());
|
||||
}
|
||||
return infer_expected_db_target(event_family, entry_kind);
|
||||
}
|
||||
|
||||
@@ -238,8 +241,8 @@ fn infer_expected_db_target(
|
||||
"liquidity" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS,
|
||||
"liquidity_add" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS,
|
||||
"liquidity_remove" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS,
|
||||
"position_open" => crate::DexEventCoverageEntryDto::DB_TARGET_POOL_LIFECYCLE_EVENTS,
|
||||
"position_close" => crate::DexEventCoverageEntryDto::DB_TARGET_POOL_LIFECYCLE_EVENTS,
|
||||
"position_open" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS,
|
||||
"position_close" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS,
|
||||
"fee" => crate::DexEventCoverageEntryDto::DB_TARGET_FEE_EVENTS,
|
||||
"reward" => crate::DexEventCoverageEntryDto::DB_TARGET_REWARD_EVENTS,
|
||||
"admin_config" => crate::DexEventCoverageEntryDto::DB_TARGET_POOL_ADMIN_EVENTS,
|
||||
@@ -283,6 +286,7 @@ fn infer_event_family(
|
||||
return Some("swap".to_string());
|
||||
}
|
||||
if contains_any(normalized.as_str(), &["create_pool", "initialize_pool", "initialize2"])
|
||||
|| normalized == "create_customizable_pool"
|
||||
|| normalized == "initialize"
|
||||
|| normalized.starts_with("initialize_")
|
||||
{
|
||||
@@ -306,11 +310,13 @@ fn infer_event_family(
|
||||
}
|
||||
if contains_any(normalized.as_str(), &["close_position", "position_close"])
|
||||
|| normalized.contains("close_position_if_empty")
|
||||
|| normalized == "close_protocol_position"
|
||||
{
|
||||
return Some("position_close".to_string());
|
||||
}
|
||||
if contains_any(normalized.as_str(), &["fee", "collect", "claim_fee"])
|
||||
&& !normalized.contains("reward")
|
||||
&& !normalized.contains("config")
|
||||
{
|
||||
return Some("fee".to_string());
|
||||
}
|
||||
@@ -323,6 +329,9 @@ fn infer_event_family(
|
||||
) {
|
||||
return Some("admin_config".to_string());
|
||||
}
|
||||
if normalized == "create_support_mint_associated" {
|
||||
return Some("account_create".to_string());
|
||||
}
|
||||
if normalized.contains("mint") {
|
||||
return Some("mint".to_string());
|
||||
}
|
||||
@@ -335,21 +344,24 @@ fn infer_event_family(
|
||||
if contains_any(normalized.as_str(), &["create_ata", "init_account", "open_orders_create"]) {
|
||||
return Some("account_create".to_string());
|
||||
}
|
||||
if contains_any(normalized.as_str(), &["close_account", "close_open_orders"])
|
||||
|| normalized.starts_with("close_")
|
||||
{
|
||||
return Some("account_close".to_string());
|
||||
}
|
||||
if normalized.contains("wrap_sol") {
|
||||
return Some("wrap_sol".to_string());
|
||||
}
|
||||
if normalized.contains("unwrap_sol") {
|
||||
return Some("unwrap_sol".to_string());
|
||||
}
|
||||
if normalized.contains("place_order") || normalized.contains("post_order") {
|
||||
if normalized.contains("place_order")
|
||||
|| normalized.contains("post_order")
|
||||
|| normalized == "open_limit_order"
|
||||
|| normalized == "increase_limit_order"
|
||||
{
|
||||
return Some("order_place".to_string());
|
||||
}
|
||||
if normalized.contains("cancel_order") || normalized.contains("cancel_all") {
|
||||
if normalized.contains("cancel_order")
|
||||
|| normalized.contains("cancel_all")
|
||||
|| normalized == "close_limit_order"
|
||||
|| normalized == "decrease_limit_order"
|
||||
{
|
||||
return Some("order_cancel".to_string());
|
||||
}
|
||||
if normalized.contains("fill") {
|
||||
@@ -358,9 +370,14 @@ fn infer_event_family(
|
||||
if normalized.contains("consume_events") {
|
||||
return Some("consume_events".to_string());
|
||||
}
|
||||
if normalized.contains("settle_funds") {
|
||||
if normalized.contains("settle_funds") || normalized == "settle_limit_order" {
|
||||
return Some("settle_funds".to_string());
|
||||
}
|
||||
if contains_any(normalized.as_str(), &["close_account", "close_open_orders"])
|
||||
|| normalized.starts_with("close_")
|
||||
{
|
||||
return Some("account_close".to_string());
|
||||
}
|
||||
if normalized.contains("vault") && normalized.contains("deposit") {
|
||||
return Some("vault_deposit".to_string());
|
||||
}
|
||||
@@ -399,7 +416,7 @@ fn contains_any(value: &str, needles: &[&str]) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn known_local_event_kind(
|
||||
pub(crate) fn known_local_event_kind(
|
||||
decoder_code: &str,
|
||||
entry_name: &str,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
@@ -444,19 +461,98 @@ fn known_local_event_kind(
|
||||
return Some("raydium_cpmm.update_pool_status".to_string());
|
||||
},
|
||||
("raydium_cpmm", "withdraw") => return Some("raydium_cpmm.withdraw".to_string()),
|
||||
("raydium_clmm", "swap") => return Some("raydium_clmm.swap".to_string()),
|
||||
("raydium_clmm", "swap_v2") => return Some("raydium_clmm.swap_v2".to_string()),
|
||||
("raydium_clmm", "increase_liquidity_v2") => {
|
||||
return Some("raydium_clmm.increase_liquidity_v2".to_string());
|
||||
("raydium_clmm", "close_limit_order") => {
|
||||
return Some("raydium_clmm.close_limit_order".to_string());
|
||||
},
|
||||
("raydium_clmm", "open_limit_order") => {
|
||||
return Some("raydium_clmm.open_limit_order".to_string());
|
||||
},
|
||||
("raydium_clmm", "increase_limit_order") => {
|
||||
return Some("raydium_clmm.increase_limit_order".to_string());
|
||||
},
|
||||
("raydium_clmm", "decrease_limit_order") => {
|
||||
return Some("raydium_clmm.decrease_limit_order".to_string());
|
||||
},
|
||||
("raydium_clmm", "close_position") => {
|
||||
return Some("raydium_clmm.close_position".to_string());
|
||||
},
|
||||
("raydium_clmm", "close_protocol_position") => {
|
||||
return Some("raydium_clmm.close_protocol_position".to_string());
|
||||
},
|
||||
("raydium_clmm", "collect_fund_fee") => {
|
||||
return Some("raydium_clmm.collect_fund_fee".to_string());
|
||||
},
|
||||
("raydium_clmm", "collect_protocol_fee") => {
|
||||
return Some("raydium_clmm.collect_protocol_fee".to_string());
|
||||
},
|
||||
("raydium_clmm", "collect_remaining_rewards") => {
|
||||
return Some("raydium_clmm.collect_remaining_rewards".to_string());
|
||||
},
|
||||
("raydium_clmm", "create_amm_config") => {
|
||||
return Some("raydium_clmm.create_amm_config".to_string());
|
||||
},
|
||||
("raydium_clmm", "create_customizable_pool") => {
|
||||
return Some("raydium_clmm.create_customizable_pool".to_string());
|
||||
},
|
||||
("raydium_clmm", "create_dynamic_fee_config") => {
|
||||
return Some("raydium_clmm.create_dynamic_fee_config".to_string());
|
||||
},
|
||||
("raydium_clmm", "create_operation_account") => {
|
||||
return Some("raydium_clmm.create_operation_account".to_string());
|
||||
},
|
||||
("raydium_clmm", "create_pool") => return Some("raydium_clmm.create_pool".to_string()),
|
||||
("raydium_clmm", "create_support_mint_associated") => {
|
||||
return Some("raydium_clmm.create_support_mint_associated".to_string());
|
||||
},
|
||||
("raydium_clmm", "decrease_liquidity") => {
|
||||
return Some("raydium_clmm.decrease_liquidity".to_string());
|
||||
},
|
||||
("raydium_clmm", "decrease_liquidity_v2") => {
|
||||
return Some("raydium_clmm.decrease_liquidity_v2".to_string());
|
||||
},
|
||||
("raydium_clmm", "increase_liquidity") => {
|
||||
return Some("raydium_clmm.increase_liquidity".to_string());
|
||||
},
|
||||
("raydium_clmm", "increase_liquidity_v2") => {
|
||||
return Some("raydium_clmm.increase_liquidity_v2".to_string());
|
||||
},
|
||||
("raydium_clmm", "initialize_reward") => {
|
||||
return Some("raydium_clmm.initialize_reward".to_string());
|
||||
},
|
||||
("raydium_clmm", "open_position") => {
|
||||
return Some("raydium_clmm.open_position".to_string());
|
||||
},
|
||||
("raydium_clmm", "open_position_v2") => {
|
||||
return Some("raydium_clmm.open_position_v2".to_string());
|
||||
},
|
||||
("raydium_clmm", "open_position_with_token22_nft") => {
|
||||
return Some("raydium_clmm.open_position_with_token22_nft".to_string());
|
||||
},
|
||||
("raydium_clmm", "close_position") => {
|
||||
return Some("raydium_clmm.close_position".to_string());
|
||||
("raydium_clmm", "set_reward_params") => {
|
||||
return Some("raydium_clmm.set_reward_params".to_string());
|
||||
},
|
||||
("raydium_clmm", "settle_limit_order") => {
|
||||
return Some("raydium_clmm.settle_limit_order".to_string());
|
||||
},
|
||||
("raydium_clmm", "swap") => return Some("raydium_clmm.swap".to_string()),
|
||||
("raydium_clmm", "swap_router_base_in") => {
|
||||
return Some("raydium_clmm.swap_router_base_in".to_string());
|
||||
},
|
||||
("raydium_clmm", "swap_v2") => return Some("raydium_clmm.swap_v2".to_string()),
|
||||
("raydium_clmm", "transfer_reward_owner") => {
|
||||
return Some("raydium_clmm.transfer_reward_owner".to_string());
|
||||
},
|
||||
("raydium_clmm", "update_amm_config") => {
|
||||
return Some("raydium_clmm.update_amm_config".to_string());
|
||||
},
|
||||
("raydium_clmm", "update_operation_account") => {
|
||||
return Some("raydium_clmm.update_operation_account".to_string());
|
||||
},
|
||||
("raydium_clmm", "update_pool_status") => {
|
||||
return Some("raydium_clmm.update_pool_status".to_string());
|
||||
},
|
||||
("raydium_clmm", "update_reward_infos") => {
|
||||
return Some("raydium_clmm.update_reward_infos".to_string());
|
||||
},
|
||||
_ => return None,
|
||||
}
|
||||
@@ -511,6 +607,77 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_family_inference_covers_raydium_clmm_idl_entries() {
|
||||
assert_eq!(
|
||||
super::infer_event_family("create_customizable_pool", crate::ENTRY_KIND_INSTRUCTION),
|
||||
Some("pool_create".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_event_family("create_dynamic_fee_config", crate::ENTRY_KIND_INSTRUCTION),
|
||||
Some("admin_config".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_event_family(
|
||||
"create_support_mint_associated",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
),
|
||||
Some("account_create".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_event_family("close_limit_order", crate::ENTRY_KIND_INSTRUCTION),
|
||||
Some("order_cancel".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_event_family("open_limit_order", crate::ENTRY_KIND_INSTRUCTION),
|
||||
Some("order_place".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_event_family("increase_limit_order", crate::ENTRY_KIND_INSTRUCTION),
|
||||
Some("order_place".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_event_family("decrease_limit_order", crate::ENTRY_KIND_INSTRUCTION),
|
||||
Some("order_cancel".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_event_family("close_protocol_position", crate::ENTRY_KIND_INSTRUCTION),
|
||||
Some("position_close".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_event_family("settle_limit_order", crate::ENTRY_KIND_INSTRUCTION),
|
||||
Some("settle_funds".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_expected_db_target(Some("position_open"), crate::ENTRY_KIND_INSTRUCTION),
|
||||
Some(crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS.to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_expected_db_target(Some("position_close"), crate::ENTRY_KIND_INSTRUCTION),
|
||||
Some(crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS.to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::known_local_event_kind("raydium_clmm", "create_pool"),
|
||||
Some("raydium_clmm.create_pool".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::known_local_event_kind("raydium_clmm", "collect_protocol_fee"),
|
||||
Some("raydium_clmm.collect_protocol_fee".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::known_local_event_kind("raydium_clmm", "open_limit_order"),
|
||||
Some("raydium_clmm.open_limit_order".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::known_local_event_kind("raydium_clmm", "increase_limit_order"),
|
||||
Some("raydium_clmm.increase_limit_order".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::known_local_event_kind("raydium_clmm", "decrease_limit_order"),
|
||||
Some("raydium_clmm.decrease_limit_order".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sync_upstream_registry_persists_raydium_cpmm_coverage_rows() {
|
||||
let database = make_database().await;
|
||||
|
||||
@@ -60,6 +60,19 @@ impl InstructionObservationIndexService {
|
||||
return self.upsert_source_rows(rows).await;
|
||||
}
|
||||
|
||||
/// Refreshes observations for the same transaction window used by local replay.
|
||||
pub async fn refresh_replay_window(
|
||||
&self,
|
||||
limit: std::option::Option<i64>,
|
||||
) -> Result<crate::InstructionObservationIndexRefreshResult, crate::Error> {
|
||||
let rows_result = self.list_replay_window_source_rows(limit).await;
|
||||
let rows = match rows_result {
|
||||
Ok(rows) => rows,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
return self.upsert_source_rows(rows).await;
|
||||
}
|
||||
|
||||
/// Refreshes observations for recently persisted instructions.
|
||||
pub async fn refresh_recent(
|
||||
&self,
|
||||
@@ -145,6 +158,73 @@ ORDER BY ins.instruction_index ASC, ins.inner_instruction_index ASC, ins.id ASC
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_replay_window_source_rows(
|
||||
&self,
|
||||
limit: std::option::Option<i64>,
|
||||
) -> Result<std::vec::Vec<InstructionObservationSourceRow>, crate::Error> {
|
||||
let effective_limit = match limit {
|
||||
Some(limit) => {
|
||||
if limit <= 0 {
|
||||
10_000
|
||||
} else {
|
||||
limit
|
||||
}
|
||||
},
|
||||
None => 10_000,
|
||||
};
|
||||
match self.database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
let query_result = sqlx::query_as::<sqlx::Sqlite, InstructionObservationSourceRow>(
|
||||
r#"
|
||||
WITH replay_transactions AS (
|
||||
SELECT id
|
||||
FROM k_sol_chain_transactions
|
||||
ORDER BY id ASC
|
||||
LIMIT ?
|
||||
)
|
||||
SELECT
|
||||
tx.id AS transaction_id,
|
||||
tx.signature AS signature,
|
||||
tx.slot AS slot,
|
||||
tx.block_time_unix AS block_time,
|
||||
tx.err_json AS err_json,
|
||||
ins.id AS instruction_id,
|
||||
ins.parent_instruction_id AS parent_instruction_id,
|
||||
ins.instruction_index AS instruction_index,
|
||||
ins.inner_instruction_index AS inner_instruction_index,
|
||||
ins.program_id AS program_id,
|
||||
ins.accounts_json AS accounts_json,
|
||||
ins.data_json AS data_json,
|
||||
de.pool_account AS pool_account,
|
||||
de.event_kind AS decoded_event_kind,
|
||||
de.id AS decoded_event_id
|
||||
FROM k_sol_chain_instructions ins
|
||||
JOIN replay_transactions replay_tx
|
||||
ON replay_tx.id = ins.transaction_id
|
||||
JOIN k_sol_chain_transactions tx
|
||||
ON tx.id = ins.transaction_id
|
||||
LEFT JOIN k_sol_dex_decoded_events de
|
||||
ON de.transaction_id = tx.id
|
||||
AND de.instruction_id = ins.id
|
||||
ORDER BY tx.id ASC, ins.instruction_index ASC, ins.inner_instruction_index ASC, ins.id ASC
|
||||
"#,
|
||||
)
|
||||
.bind(effective_limit)
|
||||
.fetch_all(pool)
|
||||
.await;
|
||||
match query_result {
|
||||
Ok(rows) => return Ok(rows),
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot list instruction observation source rows for replay window: {}",
|
||||
error
|
||||
)));
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_recent_source_rows(
|
||||
&self,
|
||||
limit: u32,
|
||||
@@ -280,6 +360,45 @@ fn resolve_instruction_name(
|
||||
};
|
||||
return Some(name.to_string());
|
||||
}
|
||||
if program_id == crate::RAYDIUM_CLMM_PROGRAM_ID || decoder_code == Some("raydium_clmm") {
|
||||
let name = match discriminator_hex {
|
||||
"4c7c800fd55725fa" => "raydium_clmm.close_limit_order",
|
||||
"9d20dab7471d1293" => "raydium_clmm.open_limit_order",
|
||||
"b19059ecfaba7d63" => "raydium_clmm.increase_limit_order",
|
||||
"759d3c674231a300" => "raydium_clmm.decrease_limit_order",
|
||||
"7b86510031446262" => "raydium_clmm.close_position",
|
||||
"c975989055556cb2" => "raydium_clmm.close_protocol_position",
|
||||
"a78a4e95dfc2067e" => "raydium_clmm.collect_fund_fee",
|
||||
"8888fcddc2427e59" => "raydium_clmm.collect_protocol_fee",
|
||||
"12eda6c52210d590" => "raydium_clmm.collect_remaining_rewards",
|
||||
"8934edd4d7756c68" => "raydium_clmm.create_amm_config",
|
||||
"2b44d4a7592fa401" => "raydium_clmm.create_customizable_pool",
|
||||
"bd0eb5785576e33e" => "raydium_clmm.create_dynamic_fee_config",
|
||||
"3f5794216d230868" => "raydium_clmm.create_operation_account",
|
||||
"e992d18ecf6840bc" => "raydium_clmm.create_pool",
|
||||
"11fb415c88f20ea9" => "raydium_clmm.create_support_mint_associated",
|
||||
"a026d06f685b2c01" => "raydium_clmm.decrease_liquidity",
|
||||
"3a7fbc3e4f52c460" => "raydium_clmm.decrease_liquidity_v2",
|
||||
"2e9cf3760dcdfbb2" => "raydium_clmm.increase_liquidity",
|
||||
"851d59df45eeb00a" => "raydium_clmm.increase_liquidity_v2",
|
||||
"5f87c0c4f281e644" => "raydium_clmm.initialize_reward",
|
||||
"87802f4d0f98f031" => "raydium_clmm.open_position",
|
||||
"4db84ad67056f1c7" => "raydium_clmm.open_position_v2",
|
||||
"4dffae527d1dc92e" => "raydium_clmm.open_position_with_token22_nft",
|
||||
"7034a74b20c9d389" => "raydium_clmm.set_reward_params",
|
||||
"cd4e74215c691a60" => "raydium_clmm.settle_limit_order",
|
||||
"f8c69e91e17587c8" => "raydium_clmm.swap",
|
||||
"457d73daf5baf2c4" => "raydium_clmm.swap_router_base_in",
|
||||
"2b04ed0b1ac91e62" => "raydium_clmm.swap_v2",
|
||||
"07160c53f22b3079" => "raydium_clmm.transfer_reward_owner",
|
||||
"313cae889a1c74c8" => "raydium_clmm.update_amm_config",
|
||||
"7f467728bce33d07" => "raydium_clmm.update_operation_account",
|
||||
"82576c062ee0757b" => "raydium_clmm.update_pool_status",
|
||||
"a3ace0340b9a6adf" => "raydium_clmm.update_reward_infos",
|
||||
_ => return None,
|
||||
};
|
||||
return Some(name.to_string());
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -585,6 +585,9 @@ pub use db::ObservedTokenStatus;
|
||||
pub use db::OnchainObservationDto;
|
||||
/// Persisted on-chain observation row.
|
||||
pub use db::OnchainObservationEntity;
|
||||
/// Application-facing normalized orderbook or limit-order event DTO.
|
||||
pub use db::OrderbookEventDto;
|
||||
pub use db::OrderbookEventEntity;
|
||||
/// Application-facing pair-analytic-signal DTO.
|
||||
pub use db::PairAnalyticSignalDto;
|
||||
/// Persisted pair-analytic-signal row.
|
||||
@@ -737,10 +740,13 @@ pub use db::query_dex_decode_replay_ledger_get_by_transaction;
|
||||
pub use db::query_dex_decode_replay_ledger_upsert;
|
||||
/// Deletes one decoded DEX event row by its natural key.
|
||||
pub use db::query_dex_decoded_events_delete_by_key;
|
||||
/// Deletes upstream registry instruction-match rows already covered by specialized local decoders.
|
||||
pub use db::query_dex_decoded_events_delete_locally_covered_upstream_instruction_matches;
|
||||
/// Deletes Meteora DLMM Anchor self-CPI swap audit rows already covered by decoded swaps.
|
||||
pub use db::query_dex_decoded_events_delete_meteora_dlmm_anchor_swap_instruction_audits;
|
||||
/// Deletes decoded DEX instruction audit rows related to one decoded instruction.
|
||||
pub use db::query_dex_decoded_events_delete_related_instruction_audit;
|
||||
pub use db::query_dex_decoded_events_delete_replaced_raydium_clmm_instruction_audits;
|
||||
/// Reads one decoded DEX event by its natural key.
|
||||
pub use db::query_dex_decoded_events_get_by_key;
|
||||
/// Returns the latest Pump.fun create payload associated with a token mint.
|
||||
@@ -864,6 +870,8 @@ pub use db::query_observed_tokens_upsert;
|
||||
pub use db::query_onchain_observations_insert;
|
||||
/// Lists recent on-chain observations ordered from newest to oldest.
|
||||
pub use db::query_onchain_observations_list_recent;
|
||||
/// Inserts or updates one normalized orderbook event row.
|
||||
pub use db::query_orderbook_events_upsert;
|
||||
/// Returns one pair-analytic-signal row identified by its key, if it exists.
|
||||
pub use db::query_pair_analytic_signals_get_by_key;
|
||||
/// Lists all pair-analytic signals for one pair ordered by key.
|
||||
@@ -1138,12 +1146,18 @@ pub use dex::RaydiumAmmV4Decoder;
|
||||
pub use dex::RaydiumAmmV4Initialize2PoolDecoded;
|
||||
/// Decoded Raydium AMM v4 swap event.
|
||||
pub use dex::RaydiumAmmV4SwapDecoded;
|
||||
/// Decoded Raydium CLMM collect_protocol_fee instruction.
|
||||
pub use dex::RaydiumClmmCollectProtocolFeeDecoded;
|
||||
/// Decoded Raydium CLMM create_pool instruction.
|
||||
pub use dex::RaydiumClmmCreatePoolDecoded;
|
||||
/// Decoded Raydium CLMM event.
|
||||
pub use dex::RaydiumClmmDecodedEvent;
|
||||
/// Decoded Raydium CLMM instruction event with projected instruction id.
|
||||
pub use dex::RaydiumClmmDecodedInstructionEvent;
|
||||
/// Raydium CLMM transaction decoder.
|
||||
pub use dex::RaydiumClmmDecoder;
|
||||
/// Decoded Raydium CLMM Anchor Program data event payload.
|
||||
pub use dex::RaydiumClmmProgramDataEventDecoded;
|
||||
/// Decoded Raydium CLMM legacy swap event.
|
||||
pub use dex::RaydiumClmmSwapLegacyDecoded;
|
||||
/// Decoded Raydium CLMM swap_v2 instruction.
|
||||
@@ -1162,6 +1176,8 @@ pub use dex::RaydiumCpmmSwapMode;
|
||||
pub use dex::classify_raydium_cpmm_instruction_data;
|
||||
/// Decodes a Raydium CLMM instruction.
|
||||
pub use dex::decode_raydium_clmm_instruction;
|
||||
/// Decodes one Raydium CLMM Anchor Program data event.
|
||||
pub use dex::decode_raydium_clmm_program_data_event;
|
||||
/// Decodes one Raydium CPMM instruction from projected instruction fields.
|
||||
pub use dex::decode_raydium_cpmm_instruction;
|
||||
/// Decodes Raydium CPMM Anchor events emitted in `Program data:` logs.
|
||||
@@ -1217,6 +1233,8 @@ pub use dex_event_classification::is_dex_liquidity_event_kind;
|
||||
pub use dex_event_classification::is_dex_liquidity_remove_event_kind;
|
||||
/// Returns true for migration DEX events.
|
||||
pub use dex_event_classification::is_dex_migration_event_kind;
|
||||
/// Returns true for orderbook or limit-order events that must not become candles.
|
||||
pub use dex_event_classification::is_dex_orderbook_event_kind;
|
||||
/// Returns true for pair creation DEX events.
|
||||
pub use dex_event_classification::is_dex_pair_creation_event_kind;
|
||||
/// Returns true for pool creation DEX events.
|
||||
@@ -1408,6 +1426,10 @@ pub use solana_pubsub_ws::parse_solana_ws_typed_notification_from_event;
|
||||
pub use token_backfill::PoolBackfillResult;
|
||||
/// One signature-backfill result summary.
|
||||
pub use token_backfill::SignatureBackfillResult;
|
||||
/// One item produced by a batch signature backfill.
|
||||
pub use token_backfill::SignatureBatchBackfillItemResult;
|
||||
/// Batch signature-backfill result summary.
|
||||
pub use token_backfill::SignatureBatchBackfillResult;
|
||||
/// One token-backfill result summary.
|
||||
pub use token_backfill::TokenBackfillResult;
|
||||
/// Historical token backfill service.
|
||||
|
||||
@@ -13,6 +13,10 @@ fn default_skip_certified_dex_decode() -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
fn default_defer_instruction_observation_index_refresh() -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Configuration for a local pipeline replay pass.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -31,6 +35,9 @@ pub struct LocalPipelineReplayConfig {
|
||||
/// Whether DEX decoding must run even when the replay ledger certifies a safe prior pass.
|
||||
#[serde(default)]
|
||||
pub force_decode_replay: bool,
|
||||
/// Whether instruction observation indexing is deferred and refreshed once after replay.
|
||||
#[serde(default = "default_defer_instruction_observation_index_refresh")]
|
||||
pub defer_instruction_observation_index_refresh: bool,
|
||||
}
|
||||
|
||||
impl Default for LocalPipelineReplayConfig {
|
||||
@@ -42,6 +49,7 @@ impl Default for LocalPipelineReplayConfig {
|
||||
reset_market_materialization_before_replay: true,
|
||||
skip_certified_dex_decode: true,
|
||||
force_decode_replay: false,
|
||||
defer_instruction_observation_index_refresh: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -90,6 +98,8 @@ pub struct LocalPipelineReplayResult {
|
||||
pub reward_event_count: usize,
|
||||
/// Total pool administration event materialization results returned by replayed non-trade calls.
|
||||
pub pool_admin_event_count: usize,
|
||||
/// Total orderbook event materialization results returned by replayed non-trade calls.
|
||||
pub orderbook_event_count: usize,
|
||||
/// Total candle upsert results returned by replayed candle calls.
|
||||
///
|
||||
/// This is a replay write/result counter, not the number of distinct rows
|
||||
@@ -111,6 +121,10 @@ pub struct LocalPipelineReplayResult {
|
||||
pub pair_symbol_updated_count: usize,
|
||||
/// Number of derived market materialization rows deleted before replay.
|
||||
pub reset_market_materialization_deleted_count: u64,
|
||||
/// Total instruction source rows scanned by the observation index refresh.
|
||||
pub instruction_observation_scanned_count: usize,
|
||||
/// Total instruction-observation rows upserted by the observation index refresh.
|
||||
pub instruction_observation_upserted_count: usize,
|
||||
/// Number of errors outside per-signature replay.
|
||||
pub global_error_count: usize,
|
||||
}
|
||||
@@ -352,6 +366,7 @@ impl LocalPipelineReplayService {
|
||||
result.fee_event_count += non_trade_result.fee_event_count;
|
||||
result.reward_event_count += non_trade_result.reward_event_count;
|
||||
result.pool_admin_event_count += non_trade_result.pool_admin_event_count;
|
||||
result.orderbook_event_count += non_trade_result.orderbook_event_count;
|
||||
},
|
||||
Err(error) => {
|
||||
result.non_trade_materialization_error_count += 1;
|
||||
@@ -426,25 +441,55 @@ impl LocalPipelineReplayService {
|
||||
);
|
||||
},
|
||||
}
|
||||
if !config.defer_instruction_observation_index_refresh {
|
||||
let instruction_index_result =
|
||||
instruction_observation_index.refresh_signature(signature.as_str()).await;
|
||||
match instruction_index_result {
|
||||
Ok(index_result) => {
|
||||
result.instruction_observation_scanned_count +=
|
||||
index_result.scanned_instruction_count;
|
||||
result.instruction_observation_upserted_count +=
|
||||
index_result.upserted_observation_count;
|
||||
tracing::debug!(
|
||||
signature = %signature,
|
||||
upserted_observation_count = index_result.upserted_observation_count,
|
||||
"instruction observation index refreshed during local replay"
|
||||
);
|
||||
},
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
signature = %signature,
|
||||
error = %error,
|
||||
"instruction observation index refresh failed during local replay"
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
result.replayed_transaction_count += 1;
|
||||
}
|
||||
if config.defer_instruction_observation_index_refresh {
|
||||
let instruction_index_result =
|
||||
instruction_observation_index.refresh_signature(signature.as_str()).await;
|
||||
instruction_observation_index.refresh_replay_window(config.limit).await;
|
||||
match instruction_index_result {
|
||||
Ok(index_result) => {
|
||||
result.instruction_observation_scanned_count +=
|
||||
index_result.scanned_instruction_count;
|
||||
result.instruction_observation_upserted_count +=
|
||||
index_result.upserted_observation_count;
|
||||
tracing::debug!(
|
||||
signature = %signature,
|
||||
scanned_instruction_count = index_result.scanned_instruction_count,
|
||||
upserted_observation_count = index_result.upserted_observation_count,
|
||||
"instruction observation index refreshed during local replay"
|
||||
"instruction observation index refreshed after local replay"
|
||||
);
|
||||
},
|
||||
Err(error) => {
|
||||
result.global_error_count += 1;
|
||||
tracing::warn!(
|
||||
signature = %signature,
|
||||
error = %error,
|
||||
"instruction observation index refresh failed during local replay"
|
||||
"instruction observation index refresh failed after local replay"
|
||||
);
|
||||
},
|
||||
}
|
||||
result.replayed_transaction_count += 1;
|
||||
}
|
||||
if config.refresh_missing_token_metadata {
|
||||
let metadata_service = match &self.http_pool {
|
||||
@@ -476,6 +521,52 @@ impl LocalPipelineReplayService {
|
||||
}
|
||||
|
||||
async fn refresh_event_coverage_best_effort(&self) {
|
||||
let cleanup_result =
|
||||
crate::query_dex_decoded_events_delete_replaced_raydium_clmm_instruction_audits(
|
||||
self.database.as_ref(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
match cleanup_result {
|
||||
Ok(deleted_count) => {
|
||||
if deleted_count > 0 {
|
||||
tracing::info!(
|
||||
deleted_count = deleted_count,
|
||||
"replaced Raydium CLMM instruction audits cleaned before dex event coverage refresh"
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
error = %error,
|
||||
"Raydium CLMM replaced instruction-audit cleanup failed before dex event coverage refresh"
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
let upstream_cleanup_result =
|
||||
crate::query_dex_decoded_events_delete_locally_covered_upstream_instruction_matches(
|
||||
self.database.as_ref(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
match upstream_cleanup_result {
|
||||
Ok(deleted_count) => {
|
||||
if deleted_count > 0 {
|
||||
tracing::info!(
|
||||
deleted_count = deleted_count,
|
||||
"locally covered upstream instruction matches cleaned before dex event coverage refresh"
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
error = %error,
|
||||
"locally covered upstream instruction-match cleanup failed before dex event coverage refresh"
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
let coverage_service = crate::DexEventCoverageService::new(self.database.clone());
|
||||
let refresh_result = coverage_service.refresh_local_counts(None).await;
|
||||
match refresh_result {
|
||||
@@ -494,6 +585,46 @@ impl LocalPipelineReplayService {
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
let post_refresh_upstream_cleanup_result =
|
||||
crate::query_dex_decoded_events_delete_locally_covered_upstream_instruction_matches(
|
||||
self.database.as_ref(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
match post_refresh_upstream_cleanup_result {
|
||||
Ok(deleted_count) => {
|
||||
if deleted_count > 0 {
|
||||
tracing::info!(
|
||||
deleted_count = deleted_count,
|
||||
"locally covered upstream instruction matches cleaned after dex event coverage refresh"
|
||||
);
|
||||
let second_refresh_result = coverage_service.refresh_local_counts(None).await;
|
||||
match second_refresh_result {
|
||||
Ok(second_refresh_result) => {
|
||||
tracing::debug!(
|
||||
upserted_entry_count = second_refresh_result.upserted_entry_count,
|
||||
refreshed_entry_count = second_refresh_result.refreshed_entry_count,
|
||||
summary_count = second_refresh_result.summaries.len(),
|
||||
"dex event coverage refreshed after upstream instruction-match cleanup"
|
||||
);
|
||||
},
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
error = %error,
|
||||
"dex event coverage refresh failed after upstream instruction-match cleanup"
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
error = %error,
|
||||
"locally covered upstream instruction-match cleanup failed after dex event coverage refresh"
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_certified_dex_decode_skip_ledger(
|
||||
|
||||
@@ -19,6 +19,8 @@ pub struct NonTradeEventMaterializationResult {
|
||||
pub reward_event_count: usize,
|
||||
/// Number of pool administration events inserted or refreshed.
|
||||
pub pool_admin_event_count: usize,
|
||||
/// Number of orderbook or limit-order events inserted or refreshed.
|
||||
pub orderbook_event_count: usize,
|
||||
}
|
||||
|
||||
/// Materializes useful non-trade decoded DEX events.
|
||||
@@ -61,7 +63,7 @@ impl NonTradeEventMaterializationService {
|
||||
)));
|
||||
},
|
||||
};
|
||||
if transaction.err_json.is_some() {
|
||||
if transaction_has_effective_error(&transaction) {
|
||||
tracing::debug!(
|
||||
signature = %transaction.signature,
|
||||
"skipping non-trade materialization for failed transaction"
|
||||
@@ -189,6 +191,24 @@ impl NonTradeEventMaterializationService {
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
if crate::is_dex_orderbook_event_kind(decoded_event.event_kind.as_str()) {
|
||||
let materialized = self
|
||||
.materialize_orderbook_event(
|
||||
&transaction,
|
||||
transaction_id,
|
||||
decoded_event,
|
||||
&payload,
|
||||
)
|
||||
.await;
|
||||
match materialized {
|
||||
Ok(was_materialized) => {
|
||||
if was_materialized {
|
||||
result.orderbook_event_count += 1;
|
||||
}
|
||||
},
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
for decoded_event in &decoded_events {
|
||||
if !decoded_event.event_kind.ends_with(".lp_change_event") {
|
||||
@@ -673,6 +693,86 @@ WHERE decoded_event_id = ?
|
||||
}
|
||||
}
|
||||
|
||||
async fn materialize_orderbook_event(
|
||||
&self,
|
||||
transaction: &crate::ChainTransactionDto,
|
||||
transaction_id: i64,
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
payload: &serde_json::Value,
|
||||
) -> Result<bool, crate::Error> {
|
||||
let decoded_event_id = match decoded_event.id {
|
||||
Some(decoded_event_id) => decoded_event_id,
|
||||
None => return Ok(false),
|
||||
};
|
||||
let context = self.resolve_decoded_event_context(decoded_event).await;
|
||||
let context = match context {
|
||||
Ok(context) => context,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let order_action = normalize_orderbook_action(decoded_event.event_kind.as_str());
|
||||
let actor_wallet = extract_first_string(
|
||||
payload,
|
||||
&["actorWallet", "actor_wallet", "owner", "authority", "payer", "user"],
|
||||
);
|
||||
let order_account = match extract_first_string(
|
||||
payload,
|
||||
&["orderAccount", "order_account", "limitOrder", "limit_order", "order"],
|
||||
) {
|
||||
Some(order_account) => Some(order_account),
|
||||
None => fallback_order_account(decoded_event.event_kind.as_str(), payload),
|
||||
};
|
||||
let amount_raw = extract_first_amount_string(
|
||||
payload,
|
||||
&[
|
||||
"amountRaw",
|
||||
"amount_raw",
|
||||
"amount",
|
||||
"decreasedAmountRaw",
|
||||
"decreased_amount_raw",
|
||||
"decreasedAmount",
|
||||
"increasedAmountRaw",
|
||||
"increased_amount_raw",
|
||||
"increasedAmount",
|
||||
],
|
||||
);
|
||||
let amount_min_raw = extract_first_amount_string(
|
||||
payload,
|
||||
&["amountMinRaw", "amount_min_raw", "amountMin", "amount_min"],
|
||||
);
|
||||
let tick_index = extract_first_i64(payload, &["tickIndex", "tick_index"]);
|
||||
let zero_for_one = extract_first_bool(payload, &["zeroForOne", "zero_for_one"]);
|
||||
let dto = crate::OrderbookEventDto::new(
|
||||
transaction_id,
|
||||
Some(decoded_event_id),
|
||||
context.dex_id,
|
||||
context.pool_id,
|
||||
context.pair_id,
|
||||
transaction.signature.clone(),
|
||||
transaction.slot,
|
||||
decoded_event.protocol_name.clone(),
|
||||
decoded_event.program_id.clone(),
|
||||
decoded_event.event_kind.clone(),
|
||||
order_action,
|
||||
decoded_event.pool_account.clone(),
|
||||
decoded_event.market_account.clone(),
|
||||
actor_wallet,
|
||||
order_account,
|
||||
decoded_event.token_a_mint.clone(),
|
||||
decoded_event.token_b_mint.clone(),
|
||||
amount_raw,
|
||||
amount_min_raw,
|
||||
tick_index,
|
||||
zero_for_one,
|
||||
decoded_event.payload_json.clone(),
|
||||
);
|
||||
let upsert_result =
|
||||
crate::query_orderbook_events_upsert(self.database.as_ref(), &dto).await;
|
||||
match upsert_result {
|
||||
Ok(_) => return Ok(true),
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
async fn ensure_liquidity_context_from_decoded_event(
|
||||
&self,
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
@@ -789,6 +889,162 @@ WHERE decoded_event_id = ?
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_orderbook_action(event_kind: &str) -> std::string::String {
|
||||
if event_kind.contains(".open_limit_order") {
|
||||
return "open_limit_order".to_string();
|
||||
}
|
||||
if event_kind.contains(".increase_limit_order") {
|
||||
return "increase_limit_order".to_string();
|
||||
}
|
||||
if event_kind.contains(".decrease_limit_order") {
|
||||
return "decrease_limit_order".to_string();
|
||||
}
|
||||
if event_kind.contains(".close_limit_order") {
|
||||
return "close_limit_order".to_string();
|
||||
}
|
||||
if event_kind.contains(".settle_limit_order") {
|
||||
return "settle_limit_order".to_string();
|
||||
}
|
||||
if event_kind.contains("order_place") {
|
||||
return "order_place".to_string();
|
||||
}
|
||||
if event_kind.contains("order_cancel") {
|
||||
return "order_cancel".to_string();
|
||||
}
|
||||
if event_kind.contains("settle_funds") {
|
||||
return "settle_funds".to_string();
|
||||
}
|
||||
return event_kind.to_string();
|
||||
}
|
||||
|
||||
fn fallback_order_account(
|
||||
event_kind: &str,
|
||||
payload: &serde_json::Value,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
if event_kind.contains(".close_limit_order") {
|
||||
return extract_account_at(payload, 2);
|
||||
}
|
||||
if event_kind.contains(".open_limit_order")
|
||||
|| event_kind.contains(".increase_limit_order")
|
||||
|| event_kind.contains(".decrease_limit_order")
|
||||
{
|
||||
return extract_account_at(payload, 3);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn extract_account_at(
|
||||
value: &serde_json::Value,
|
||||
index: usize,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
if let Some(object) = value.as_object() {
|
||||
let accounts = object.get("accounts");
|
||||
if let Some(accounts) = accounts {
|
||||
if let Some(array) = accounts.as_array() {
|
||||
let candidate = array.get(index);
|
||||
if let Some(candidate) = candidate {
|
||||
if let Some(text) = candidate.as_str() {
|
||||
let trimmed = text.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for nested_value in object.values() {
|
||||
let nested = extract_account_at(nested_value, index);
|
||||
if nested.is_some() {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn extract_first_i64(
|
||||
value: &serde_json::Value,
|
||||
candidate_keys: &[&str],
|
||||
) -> std::option::Option<i64> {
|
||||
if let Some(object) = value.as_object() {
|
||||
for candidate_key in candidate_keys {
|
||||
let candidate_value = object.get(*candidate_key);
|
||||
if let Some(candidate_value) = candidate_value {
|
||||
if let Some(number) = candidate_value.as_i64() {
|
||||
return Some(number);
|
||||
}
|
||||
if let Some(number) = candidate_value.as_u64() {
|
||||
let converted = i64::try_from(number);
|
||||
if let Ok(converted) = converted {
|
||||
return Some(converted);
|
||||
}
|
||||
}
|
||||
if let Some(text) = candidate_value.as_str() {
|
||||
let parsed = text.parse::<i64>();
|
||||
if let Ok(parsed) = parsed {
|
||||
return Some(parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for nested_value in object.values() {
|
||||
let nested = extract_first_i64(nested_value, candidate_keys);
|
||||
if nested.is_some() {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn extract_first_bool(
|
||||
value: &serde_json::Value,
|
||||
candidate_keys: &[&str],
|
||||
) -> std::option::Option<bool> {
|
||||
if let Some(object) = value.as_object() {
|
||||
for candidate_key in candidate_keys {
|
||||
let candidate_value = object.get(*candidate_key);
|
||||
if let Some(candidate_value) = candidate_value {
|
||||
if let Some(flag) = candidate_value.as_bool() {
|
||||
return Some(flag);
|
||||
}
|
||||
if let Some(number) = candidate_value.as_i64() {
|
||||
return Some(number != 0);
|
||||
}
|
||||
if let Some(text) = candidate_value.as_str() {
|
||||
if text == "true" || text == "1" {
|
||||
return Some(true);
|
||||
}
|
||||
if text == "false" || text == "0" {
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for nested_value in object.values() {
|
||||
let nested = extract_first_bool(nested_value, candidate_keys);
|
||||
if nested.is_some() {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn transaction_has_effective_error(transaction: &crate::ChainTransactionDto) -> bool {
|
||||
let err_json = match transaction.err_json.as_ref() {
|
||||
Some(err_json) => err_json.trim(),
|
||||
None => return false,
|
||||
};
|
||||
if err_json.is_empty() {
|
||||
return false;
|
||||
}
|
||||
if err_json == "null" {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn extract_first_u64(
|
||||
value: &serde_json::Value,
|
||||
candidate_keys: &[&str],
|
||||
@@ -902,6 +1158,28 @@ fn extract_first_number_as_string(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
#[test]
|
||||
fn blank_or_null_err_json_is_not_effective_failure() {
|
||||
let mut transaction = crate::ChainTransactionDto::new(
|
||||
"sig-non-trade-effective-error".to_string(),
|
||||
Some(1),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
"{}".to_string(),
|
||||
);
|
||||
assert!(!super::transaction_has_effective_error(&transaction));
|
||||
transaction.err_json = Some("".to_string());
|
||||
assert!(!super::transaction_has_effective_error(&transaction));
|
||||
transaction.err_json = Some("null".to_string());
|
||||
assert!(!super::transaction_has_effective_error(&transaction));
|
||||
transaction.err_json = Some("{\"InstructionError\":[0,\"Custom\"]}".to_string());
|
||||
assert!(super::transaction_has_effective_error(&transaction));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_nested_liquidity_amounts() {
|
||||
let payload = serde_json::json!({
|
||||
|
||||
@@ -1279,6 +1279,53 @@ fn decode_raydium_clmm_candidate(
|
||||
),
|
||||
});
|
||||
},
|
||||
crate::RaydiumClmmDecodedEvent::CreatePool(event) => {
|
||||
return Some(crate::OnchainDexPairCandidateDto {
|
||||
signature: signature.to_string(),
|
||||
slot,
|
||||
block_time,
|
||||
failed,
|
||||
program_id: program_id.to_string(),
|
||||
dex_code,
|
||||
candidate_kind: "create_pool".to_string(),
|
||||
confidence: "high".to_string(),
|
||||
instruction_index: instruction.instruction_index,
|
||||
inner_instruction_index: instruction.inner_instruction_index,
|
||||
instruction_name: Some("raydium_clmm.create_pool".to_string()),
|
||||
instruction_data_prefix: instruction_data_prefix(instruction.data.as_deref()),
|
||||
instruction_discriminator_hex: instruction_discriminator_hex(
|
||||
instruction.data.as_deref(),
|
||||
),
|
||||
pool_address: Some(event.pool_state.clone()),
|
||||
token_a_mint: Some(event.token_mint_0),
|
||||
token_b_mint: Some(event.token_mint_1),
|
||||
verified_pool_address: Some(event.pool_state.clone()),
|
||||
observed_token_mints: std::vec::Vec::new(),
|
||||
token_balance_deltas: std::vec::Vec::new(),
|
||||
candidate_pool_accounts: std::vec::Vec::new(),
|
||||
candidate_token_vault_accounts: std::vec::Vec::new(),
|
||||
candidate_program_accounts: std::vec::Vec::new(),
|
||||
account_samples: sample_strings(instruction.accounts.as_slice(), 12),
|
||||
log_samples: sample_logs(logs, 8),
|
||||
backfill_hint: build_backfill_hint(
|
||||
"pool",
|
||||
Some(event.pool_state.as_str()),
|
||||
signature,
|
||||
),
|
||||
});
|
||||
},
|
||||
crate::RaydiumClmmDecodedEvent::CollectProtocolFee(_)
|
||||
| crate::RaydiumClmmDecodedEvent::CollectPersonalFeeEvent(_)
|
||||
| crate::RaydiumClmmDecodedEvent::CollectProtocolFeeEvent(_)
|
||||
| crate::RaydiumClmmDecodedEvent::ConfigChangeEvent(_)
|
||||
| crate::RaydiumClmmDecodedEvent::CreatePersonalPositionEvent(_)
|
||||
| crate::RaydiumClmmDecodedEvent::DecreaseLiquidityEvent(_)
|
||||
| crate::RaydiumClmmDecodedEvent::IncreaseLiquidityEvent(_)
|
||||
| crate::RaydiumClmmDecodedEvent::LiquidityCalculateEvent(_)
|
||||
| crate::RaydiumClmmDecodedEvent::LiquidityChangeEvent(_)
|
||||
| crate::RaydiumClmmDecodedEvent::PoolCreatedEvent(_)
|
||||
| crate::RaydiumClmmDecodedEvent::SwapEvent(_)
|
||||
| crate::RaydiumClmmDecodedEvent::UpdateRewardInfosEvent(_) => return None,
|
||||
}
|
||||
}
|
||||
return None;
|
||||
|
||||
@@ -131,6 +131,68 @@ pub struct SignatureBackfillResult {
|
||||
pub pair_candle_count: usize,
|
||||
}
|
||||
|
||||
/// One item produced by a batch signature backfill.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct SignatureBatchBackfillItemResult {
|
||||
/// Input transaction signature.
|
||||
pub signature: std::string::String,
|
||||
/// Whether the signature was replayed successfully.
|
||||
pub success: bool,
|
||||
/// Error text when this signature failed before a replay result could be produced.
|
||||
pub error: std::option::Option<std::string::String>,
|
||||
/// Per-signature replay result when available.
|
||||
pub result: std::option::Option<crate::SignatureBackfillResult>,
|
||||
}
|
||||
|
||||
/// Batch signature-backfill result summary.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct SignatureBatchBackfillResult {
|
||||
/// Number of raw signatures submitted by the UI.
|
||||
pub input_signature_count: usize,
|
||||
/// Number of unique non-empty signatures processed.
|
||||
pub unique_signature_count: usize,
|
||||
/// Number of successfully replayed signatures.
|
||||
pub success_count: usize,
|
||||
/// Number of signatures that failed before a replay result could be produced.
|
||||
pub failure_count: usize,
|
||||
/// Whether processing stopped after the first hard failure.
|
||||
pub aborted: bool,
|
||||
/// 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,
|
||||
/// Number of signatures whose `getTransaction` lookup failed after retries.
|
||||
pub transaction_fetch_error_count: usize,
|
||||
/// Last transaction fetch error observed during this run, if any.
|
||||
pub last_transaction_fetch_error: std::option::Option<std::string::String>,
|
||||
/// 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,
|
||||
/// Detailed per-signature results.
|
||||
pub items: std::vec::Vec<crate::SignatureBatchBackfillItemResult>,
|
||||
}
|
||||
|
||||
/// Historical token backfill service.
|
||||
///
|
||||
/// This service reuses the existing transaction projection and downstream
|
||||
@@ -878,6 +940,178 @@ impl TokenBackfillService {
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// Replays a batch of known transaction signatures through the existing pipeline.
|
||||
///
|
||||
/// Unlike [`Self::backfill_signature`], this method refreshes token metadata and
|
||||
/// event coverage only once after the whole batch has been processed. This keeps
|
||||
/// manual discovery backfills responsive when many signatures were collected from
|
||||
/// an external explorer.
|
||||
pub async fn backfill_signatures(
|
||||
&self,
|
||||
signatures: &[std::string::String],
|
||||
continue_on_error: bool,
|
||||
) -> Result<crate::SignatureBatchBackfillResult, crate::Error> {
|
||||
let mut result = crate::SignatureBatchBackfillResult {
|
||||
input_signature_count: signatures.len(),
|
||||
unique_signature_count: 0,
|
||||
success_count: 0,
|
||||
failure_count: 0,
|
||||
aborted: false,
|
||||
resolved_transaction_count: 0,
|
||||
missing_transaction_count: 0,
|
||||
transaction_fetch_error_count: 0,
|
||||
last_transaction_fetch_error: None,
|
||||
decoded_event_count: 0,
|
||||
detection_count: 0,
|
||||
launch_attribution_count: 0,
|
||||
pool_origin_count: 0,
|
||||
wallet_participation_count: 0,
|
||||
trade_event_count: 0,
|
||||
liquidity_event_count: 0,
|
||||
pool_lifecycle_event_count: 0,
|
||||
fee_event_count: 0,
|
||||
reward_event_count: 0,
|
||||
pool_admin_event_count: 0,
|
||||
pair_candle_count: 0,
|
||||
items: std::vec::Vec::new(),
|
||||
};
|
||||
let mut seen = std::collections::BTreeSet::<std::string::String>::new();
|
||||
for signature in signatures {
|
||||
let trimmed_signature = signature.trim().to_string();
|
||||
if trimmed_signature.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if seen.contains(trimmed_signature.as_str()) {
|
||||
continue;
|
||||
}
|
||||
seen.insert(trimmed_signature.clone());
|
||||
result.unique_signature_count += 1;
|
||||
let replay_result = self.replay_signature(trimmed_signature.clone()).await;
|
||||
let replay = match replay_result {
|
||||
Ok(replay) => replay,
|
||||
Err(error) => {
|
||||
result.failure_count += 1;
|
||||
result.items.push(crate::SignatureBatchBackfillItemResult {
|
||||
signature: trimmed_signature.clone(),
|
||||
success: false,
|
||||
error: Some(error.to_string()),
|
||||
result: None,
|
||||
});
|
||||
if !continue_on_error {
|
||||
result.aborted = true;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
},
|
||||
};
|
||||
let signature_result = crate::SignatureBackfillResult {
|
||||
signature: trimmed_signature.clone(),
|
||||
resolved_transaction_count: replay.resolved_transaction_count,
|
||||
missing_transaction_count: replay.missing_transaction_count,
|
||||
transaction_fetch_error_count: replay.transaction_fetch_error_count,
|
||||
last_transaction_fetch_error: replay.last_transaction_fetch_error.clone(),
|
||||
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,
|
||||
};
|
||||
result.success_count += 1;
|
||||
result.resolved_transaction_count += signature_result.resolved_transaction_count;
|
||||
result.missing_transaction_count += signature_result.missing_transaction_count;
|
||||
result.transaction_fetch_error_count += signature_result.transaction_fetch_error_count;
|
||||
if signature_result.last_transaction_fetch_error.is_some() {
|
||||
result.last_transaction_fetch_error =
|
||||
signature_result.last_transaction_fetch_error.clone();
|
||||
}
|
||||
result.decoded_event_count += signature_result.decoded_event_count;
|
||||
result.detection_count += signature_result.detection_count;
|
||||
result.launch_attribution_count += signature_result.launch_attribution_count;
|
||||
result.pool_origin_count += signature_result.pool_origin_count;
|
||||
result.wallet_participation_count += signature_result.wallet_participation_count;
|
||||
result.trade_event_count += signature_result.trade_event_count;
|
||||
result.liquidity_event_count += signature_result.liquidity_event_count;
|
||||
result.pool_lifecycle_event_count += signature_result.pool_lifecycle_event_count;
|
||||
result.fee_event_count += signature_result.fee_event_count;
|
||||
result.reward_event_count += signature_result.reward_event_count;
|
||||
result.pool_admin_event_count += signature_result.pool_admin_event_count;
|
||||
result.pair_candle_count += signature_result.pair_candle_count;
|
||||
result.items.push(crate::SignatureBatchBackfillItemResult {
|
||||
signature: trimmed_signature,
|
||||
success: true,
|
||||
error: None,
|
||||
result: Some(signature_result),
|
||||
});
|
||||
}
|
||||
if result.unique_signature_count == 0 {
|
||||
return Err(crate::Error::Config(
|
||||
"signature batch must contain at least one non-empty signature".to_string(),
|
||||
));
|
||||
}
|
||||
self.backfill_missing_token_metadata_best_effort(100).await;
|
||||
self.refresh_event_coverage_best_effort().await;
|
||||
let summary_payload = serde_json::json!({
|
||||
"inputSignatureCount": result.input_signature_count,
|
||||
"uniqueSignatureCount": result.unique_signature_count,
|
||||
"successCount": result.success_count,
|
||||
"failureCount": result.failure_count,
|
||||
"aborted": result.aborted,
|
||||
"resolvedTransactionCount": result.resolved_transaction_count,
|
||||
"missingTransactionCount": result.missing_transaction_count,
|
||||
"transactionFetchErrorCount": result.transaction_fetch_error_count,
|
||||
"lastTransactionFetchError": result.last_transaction_fetch_error,
|
||||
"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_batch.backfill.completed".to_string(),
|
||||
crate::ObservationSourceKind::HttpRpc,
|
||||
Some(format!("backfill:{}", self.http_role)),
|
||||
format!("{} signatures", result.unique_signature_count),
|
||||
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_batch.backfill.completed".to_string(),
|
||||
crate::AnalysisSignalSeverity::Low,
|
||||
format!("{} signatures", result.unique_signature_count),
|
||||
Some(observation_id),
|
||||
None,
|
||||
summary_payload,
|
||||
))
|
||||
.await;
|
||||
if let Err(error) = signal_result {
|
||||
return Err(error);
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
async fn fetch_transaction_value_with_retry(
|
||||
&self,
|
||||
signature: &str,
|
||||
@@ -943,6 +1177,52 @@ impl TokenBackfillService {
|
||||
}
|
||||
|
||||
async fn refresh_event_coverage_best_effort(&self) {
|
||||
let cleanup_result =
|
||||
crate::query_dex_decoded_events_delete_replaced_raydium_clmm_instruction_audits(
|
||||
self.database.as_ref(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
match cleanup_result {
|
||||
Ok(deleted_count) => {
|
||||
if deleted_count > 0 {
|
||||
tracing::info!(
|
||||
deleted_count = deleted_count,
|
||||
"replaced Raydium CLMM instruction audits cleaned before dex event coverage refresh"
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
error = %error,
|
||||
"Raydium CLMM replaced instruction-audit cleanup failed before dex event coverage refresh"
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
let upstream_cleanup_result =
|
||||
crate::query_dex_decoded_events_delete_locally_covered_upstream_instruction_matches(
|
||||
self.database.as_ref(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
match upstream_cleanup_result {
|
||||
Ok(deleted_count) => {
|
||||
if deleted_count > 0 {
|
||||
tracing::info!(
|
||||
deleted_count = deleted_count,
|
||||
"locally covered upstream instruction matches cleaned before dex event coverage refresh"
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
error = %error,
|
||||
"locally covered upstream instruction-match cleanup failed before dex event coverage refresh"
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
let coverage_service = crate::DexEventCoverageService::new(self.database.clone());
|
||||
let refresh_result = coverage_service.refresh_local_counts(None).await;
|
||||
match refresh_result {
|
||||
@@ -960,6 +1240,45 @@ impl TokenBackfillService {
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
let post_refresh_upstream_cleanup_result =
|
||||
crate::query_dex_decoded_events_delete_locally_covered_upstream_instruction_matches(
|
||||
self.database.as_ref(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
match post_refresh_upstream_cleanup_result {
|
||||
Ok(deleted_count) => {
|
||||
if deleted_count > 0 {
|
||||
tracing::info!(
|
||||
deleted_count = deleted_count,
|
||||
"locally covered upstream instruction matches cleaned after dex event coverage refresh"
|
||||
);
|
||||
let second_refresh_result = coverage_service.refresh_local_counts(None).await;
|
||||
match second_refresh_result {
|
||||
Ok(second_refresh_result) => {
|
||||
tracing::debug!(
|
||||
upserted_entry_count = second_refresh_result.upserted_entry_count,
|
||||
summary_count = second_refresh_result.summaries.len(),
|
||||
"dex event coverage refreshed after upstream instruction-match cleanup"
|
||||
);
|
||||
},
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
error = %error,
|
||||
"dex event coverage refresh failed after upstream instruction-match cleanup"
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
error = %error,
|
||||
"locally covered upstream instruction-match cleanup failed after dex event coverage refresh"
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,37 @@ const UPSTREAM_GIT_PROGRAM_NOTES: &str = "program id extracted from upstream Git
|
||||
const UPSTREAM_GIT_DISCRIMINATOR_NOTES: &str = "entry name and discriminator extracted from upstream Git decoder source or from the discriminator convention used by that upstream decoder; not corpus-verified; no trade/candle/materialization proof";
|
||||
const UPSTREAM_GIT_ALIAS_PROGRAM_NOTES: &str = "upstream Git decoder name kept as a discovery alias; program id and discriminator rows are represented by the canonical decoder entry to avoid duplicate registry keys";
|
||||
|
||||
const RAYDIUM_IDL_SOURCE_REPO: &str = "raydium-io/raydium-idl";
|
||||
|
||||
const RAYDIUM_IDL_DISCRIMINATOR_NOTES: &str = "entry name and discriminator extracted from Raydium official IDL snapshot; not corpus-verified; no trade/candle/materialization proof";
|
||||
|
||||
const fn raydium_idl_discriminator_entry(
|
||||
decoder_code: &'static str,
|
||||
program_id: std::option::Option<&'static str>,
|
||||
program_family: &'static str,
|
||||
surface_kind: &'static str,
|
||||
entry_kind: &'static str,
|
||||
entry_name: &'static str,
|
||||
discriminator_hex: &'static str,
|
||||
discriminator_len: u16,
|
||||
source_path: &'static str,
|
||||
) -> crate::UpstreamRegistryEntry {
|
||||
return crate::UpstreamRegistryEntry {
|
||||
source_repo: Some(RAYDIUM_IDL_SOURCE_REPO),
|
||||
source_path: Some(source_path),
|
||||
decoder_code,
|
||||
program_id,
|
||||
program_family,
|
||||
surface_kind,
|
||||
entry_kind,
|
||||
entry_name,
|
||||
discriminator_hex: Some(discriminator_hex),
|
||||
discriminator_len: Some(discriminator_len),
|
||||
proof_status: crate::PROOF_STATUS_UPSTREAM_GIT_UNVERIFIED,
|
||||
notes: RAYDIUM_IDL_DISCRIMINATOR_NOTES,
|
||||
};
|
||||
}
|
||||
|
||||
const fn upstream_git_program_entry(
|
||||
decoder_code: &'static str,
|
||||
program_id: std::option::Option<&'static str>,
|
||||
@@ -11775,6 +11806,61 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[
|
||||
8,
|
||||
"decoders/raydium-clmm-decoder/src/instructions/close_position.rs",
|
||||
),
|
||||
raydium_idl_discriminator_entry(
|
||||
"raydium_clmm",
|
||||
Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
|
||||
"raydium",
|
||||
"clmm",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"close_limit_order",
|
||||
"4c7c800fd55725fa",
|
||||
8,
|
||||
"raydium_clmm/raydium_clmm.json",
|
||||
),
|
||||
raydium_idl_discriminator_entry(
|
||||
"raydium_clmm",
|
||||
Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
|
||||
"raydium",
|
||||
"clmm",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"open_limit_order",
|
||||
"9d20dab7471d1293",
|
||||
8,
|
||||
"raydium_clmm/raydium_clmm.json",
|
||||
),
|
||||
raydium_idl_discriminator_entry(
|
||||
"raydium_clmm",
|
||||
Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
|
||||
"raydium",
|
||||
"clmm",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"increase_limit_order",
|
||||
"b19059ecfaba7d63",
|
||||
8,
|
||||
"raydium_clmm/raydium_clmm.json",
|
||||
),
|
||||
raydium_idl_discriminator_entry(
|
||||
"raydium_clmm",
|
||||
Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
|
||||
"raydium",
|
||||
"clmm",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"decrease_limit_order",
|
||||
"759d3c674231a300",
|
||||
8,
|
||||
"raydium_clmm/raydium_clmm.json",
|
||||
),
|
||||
raydium_idl_discriminator_entry(
|
||||
"raydium_clmm",
|
||||
Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
|
||||
"raydium",
|
||||
"clmm",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"close_protocol_position",
|
||||
"c975989055556cb2",
|
||||
8,
|
||||
"raydium_clmm/raydium_clmm.json",
|
||||
),
|
||||
upstream_git_discriminator_entry(
|
||||
"raydium_clmm",
|
||||
Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
|
||||
@@ -11852,6 +11938,28 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[
|
||||
8,
|
||||
"decoders/raydium-clmm-decoder/src/instructions/create_amm_config.rs",
|
||||
),
|
||||
raydium_idl_discriminator_entry(
|
||||
"raydium_clmm",
|
||||
Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
|
||||
"raydium",
|
||||
"clmm",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"create_customizable_pool",
|
||||
"2b44d4a7592fa401",
|
||||
8,
|
||||
"raydium_clmm/raydium_clmm.json",
|
||||
),
|
||||
raydium_idl_discriminator_entry(
|
||||
"raydium_clmm",
|
||||
Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
|
||||
"raydium",
|
||||
"clmm",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"create_dynamic_fee_config",
|
||||
"bd0eb5785576e33e",
|
||||
8,
|
||||
"raydium_clmm/raydium_clmm.json",
|
||||
),
|
||||
upstream_git_discriminator_entry(
|
||||
"raydium_clmm",
|
||||
Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
|
||||
@@ -11885,6 +11993,17 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[
|
||||
8,
|
||||
"decoders/raydium-clmm-decoder/src/instructions/create_pool.rs",
|
||||
),
|
||||
raydium_idl_discriminator_entry(
|
||||
"raydium_clmm",
|
||||
Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
|
||||
"raydium",
|
||||
"clmm",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"create_support_mint_associated",
|
||||
"11fb415c88f20ea9",
|
||||
8,
|
||||
"raydium_clmm/raydium_clmm.json",
|
||||
),
|
||||
upstream_git_discriminator_entry(
|
||||
"raydium_clmm",
|
||||
Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
|
||||
@@ -12039,6 +12158,17 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[
|
||||
8,
|
||||
"decoders/raydium-clmm-decoder/src/instructions/set_reward_params.rs",
|
||||
),
|
||||
raydium_idl_discriminator_entry(
|
||||
"raydium_clmm",
|
||||
Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
|
||||
"raydium",
|
||||
"clmm",
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"settle_limit_order",
|
||||
"cd4e74215c691a60",
|
||||
8,
|
||||
"raydium_clmm/raydium_clmm.json",
|
||||
),
|
||||
upstream_git_discriminator_entry(
|
||||
"raydium_clmm",
|
||||
Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
|
||||
|
||||
Reference in New Issue
Block a user