0.7.56
This commit is contained in:
@@ -26,11 +26,14 @@ pub use dtos::DexDecodedEventDto;
|
||||
pub use dtos::DexDto;
|
||||
pub use dtos::DexEventCoverageEntryDto;
|
||||
pub use dtos::DexEventCoverageSummaryDto;
|
||||
pub use dtos::FeeEventAmountDto;
|
||||
pub use dtos::FeeEventDto;
|
||||
pub use dtos::InstructionObservationDto;
|
||||
pub use dtos::InstructionObservationSourceRow;
|
||||
pub use dtos::KnownHttpEndpointDto;
|
||||
pub use dtos::KnownWsEndpointDto;
|
||||
pub use dtos::LaunchAttributionDto;
|
||||
pub use dtos::LaunchEventUpsertInput;
|
||||
pub use dtos::LaunchSurfaceDto;
|
||||
pub use dtos::LaunchSurfaceKeyDto;
|
||||
pub use dtos::LiquidityEventDto;
|
||||
@@ -98,6 +101,7 @@ pub use entities::DexDecodedEventEntity;
|
||||
pub use entities::DexEntity;
|
||||
pub use entities::DexEventCoverageEntryEntity;
|
||||
pub use entities::DexEventCoverageSummaryEntity;
|
||||
pub use entities::FeeEventAmountEntity;
|
||||
pub use entities::FeeEventEntity;
|
||||
pub use entities::InstructionObservationEntity;
|
||||
pub use entities::KnownHttpEndpointEntity;
|
||||
@@ -160,17 +164,17 @@ pub use queries::query_dex_decoded_events_delete_by_key;
|
||||
pub use queries::query_dex_decoded_events_delete_instruction_audit_by_discriminator;
|
||||
pub use queries::query_dex_decoded_events_delete_local_replay_scope_by_transaction_id;
|
||||
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_raydium_clmm_instruction_audit_by_discriminator;
|
||||
pub use queries::query_dex_decoded_events_delete_raydium_launchpad_anchor_self_cpi_audit;
|
||||
pub use queries::query_dex_decoded_events_delete_replaced_raydium_cpmm_instruction_audits;
|
||||
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_delete_replaced_raydium_cpmm_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;
|
||||
pub use queries::query_dex_decoded_events_upsert;
|
||||
pub use queries::query_dex_decoded_events_update_payload_json_by_id;
|
||||
pub use queries::query_dex_decoded_events_upsert;
|
||||
pub use queries::query_dex_event_coverage_entries_delete_by_decoder;
|
||||
pub use queries::query_dex_event_coverage_entries_list_by_decoder;
|
||||
pub use queries::query_dex_event_coverage_entries_list_summary_by_decoder;
|
||||
@@ -180,6 +184,11 @@ pub use queries::query_dex_event_coverage_entries_upsert;
|
||||
pub use queries::query_dexs_get_by_code;
|
||||
pub use queries::query_dexs_list;
|
||||
pub use queries::query_dexs_upsert;
|
||||
pub use queries::query_fee_event_amounts_backfill_single_leg_from_fee_events;
|
||||
pub use queries::query_fee_event_amounts_delete_by_fee_event_id;
|
||||
pub use queries::query_fee_event_amounts_list_by_fee_event_id;
|
||||
pub use queries::query_fee_event_amounts_replace_for_fee_event;
|
||||
pub use queries::query_fee_events_upsert_with_amount_legs;
|
||||
pub use queries::query_fee_events_get_by_decoded_event_id;
|
||||
pub use queries::query_fee_events_list_recent;
|
||||
pub use queries::query_fee_events_upsert;
|
||||
@@ -187,7 +196,6 @@ pub use queries::query_instruction_observation_source_rows_list_by_signature;
|
||||
pub use queries::query_instruction_observation_source_rows_list_recent;
|
||||
pub use queries::query_instruction_observation_source_rows_list_replay_window;
|
||||
pub use queries::query_instruction_observations_delete_by_transaction_ids;
|
||||
pub use dtos::InstructionObservationSourceRow;
|
||||
pub use queries::query_instruction_observations_list_by_filter;
|
||||
pub use queries::query_instruction_observations_upsert;
|
||||
pub use queries::query_known_http_endpoints_get;
|
||||
@@ -200,7 +208,6 @@ pub use queries::query_launch_attributions_get_by_decoded_event_id;
|
||||
pub use queries::query_launch_attributions_list_by_pool_id;
|
||||
pub use queries::query_launch_attributions_upsert;
|
||||
pub use queries::query_launch_events_upsert;
|
||||
pub use dtos::LaunchEventUpsertInput;
|
||||
pub use queries::query_launch_surface_keys_get_by_match;
|
||||
pub use queries::query_launch_surface_keys_list_by_surface_id;
|
||||
pub use queries::query_launch_surface_keys_upsert;
|
||||
|
||||
@@ -13,6 +13,7 @@ mod dex_decode_replay_ledger;
|
||||
mod dex_decoded_event;
|
||||
mod dex_event_coverage_entry;
|
||||
mod fee_event;
|
||||
mod fee_event_amount;
|
||||
mod instruction_observation;
|
||||
mod known_http_endpoint;
|
||||
mod known_ws_endpoint;
|
||||
@@ -88,6 +89,7 @@ pub use dex_decoded_event::DexDecodedEventDto;
|
||||
pub use dex_event_coverage_entry::DexEventCoverageEntryDto;
|
||||
pub use dex_event_coverage_entry::DexEventCoverageSummaryDto;
|
||||
pub use fee_event::FeeEventDto;
|
||||
pub use fee_event_amount::FeeEventAmountDto;
|
||||
pub use instruction_observation::InstructionObservationDto;
|
||||
pub use instruction_observation::InstructionObservationSourceRow;
|
||||
pub use known_http_endpoint::KnownHttpEndpointDto;
|
||||
|
||||
110
kb_lib/src/db/dtos/fee_event_amount.rs
Normal file
110
kb_lib/src/db/dtos/fee_event_amount.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
// file: kb_lib/src/db/dtos/fee_event_amount.rs
|
||||
|
||||
//! Fee event amount DTO.
|
||||
|
||||
/// Application-facing normalized fee event amount leg DTO.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FeeEventAmountDto {
|
||||
/// Optional numeric primary key.
|
||||
pub id: std::option::Option<i64>,
|
||||
/// Related parent fee event id.
|
||||
pub fee_event_id: i64,
|
||||
/// Related transaction id.
|
||||
pub transaction_id: i64,
|
||||
/// Related decoded DEX event id, when available.
|
||||
pub decoded_event_id: std::option::Option<i64>,
|
||||
/// Stable leg index within one parent fee event.
|
||||
pub leg_index: u32,
|
||||
/// Fee component kind or semantic role.
|
||||
pub fee_component_kind: std::string::String,
|
||||
/// Token mint used by this fee amount leg.
|
||||
pub token_mint: std::string::String,
|
||||
/// Raw amount for this leg as decimal text.
|
||||
pub amount_raw: std::string::String,
|
||||
/// Source token account or lamport account, when decoded.
|
||||
pub source_account: std::option::Option<std::string::String>,
|
||||
/// Destination token account or lamport account, when decoded.
|
||||
pub destination_account: std::option::Option<std::string::String>,
|
||||
/// Extraction source used to prove this amount.
|
||||
pub amount_source: std::string::String,
|
||||
/// Source/proof payload JSON.
|
||||
pub payload_json: std::string::String,
|
||||
/// Creation timestamp.
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl FeeEventAmountDto {
|
||||
/// Creates a new fee event amount DTO.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
fee_event_id: i64,
|
||||
transaction_id: i64,
|
||||
decoded_event_id: std::option::Option<i64>,
|
||||
leg_index: u32,
|
||||
fee_component_kind: std::string::String,
|
||||
token_mint: std::string::String,
|
||||
amount_raw: std::string::String,
|
||||
source_account: std::option::Option<std::string::String>,
|
||||
destination_account: std::option::Option<std::string::String>,
|
||||
amount_source: std::string::String,
|
||||
payload_json: std::string::String,
|
||||
) -> Self {
|
||||
return Self {
|
||||
id: None,
|
||||
fee_event_id,
|
||||
transaction_id,
|
||||
decoded_event_id,
|
||||
leg_index,
|
||||
fee_component_kind,
|
||||
token_mint,
|
||||
amount_raw,
|
||||
source_account,
|
||||
destination_account,
|
||||
amount_source,
|
||||
payload_json,
|
||||
created_at: chrono::Utc::now(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<crate::FeeEventAmountEntity> for FeeEventAmountDto {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(entity: crate::FeeEventAmountEntity) -> Result<Self, Self::Error> {
|
||||
let leg_index_result = u32::try_from(entity.leg_index);
|
||||
let leg_index = match leg_index_result {
|
||||
Ok(leg_index) => leg_index,
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot convert fee event amount leg_index '{}' to u32: {}",
|
||||
entity.leg_index, 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 fee event amount created_at '{}': {}",
|
||||
entity.created_at, error
|
||||
)));
|
||||
},
|
||||
};
|
||||
return Ok(Self {
|
||||
id: Some(entity.id),
|
||||
fee_event_id: entity.fee_event_id,
|
||||
transaction_id: entity.transaction_id,
|
||||
decoded_event_id: entity.decoded_event_id,
|
||||
leg_index,
|
||||
fee_component_kind: entity.fee_component_kind,
|
||||
token_mint: entity.token_mint,
|
||||
amount_raw: entity.amount_raw,
|
||||
source_account: entity.source_account,
|
||||
destination_account: entity.destination_account,
|
||||
amount_source: entity.amount_source,
|
||||
payload_json: entity.payload_json,
|
||||
created_at,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ mod dex_decode_replay_ledger;
|
||||
mod dex_decoded_event;
|
||||
mod dex_event_coverage_entry;
|
||||
mod fee_event;
|
||||
mod fee_event_amount;
|
||||
mod known_http_endpoint;
|
||||
mod known_ws_endpoint;
|
||||
mod instruction_observation;
|
||||
@@ -63,6 +64,7 @@ pub use dex_decoded_event::DexDecodedEventEntity;
|
||||
pub use dex_event_coverage_entry::DexEventCoverageEntryEntity;
|
||||
pub use dex_event_coverage_entry::DexEventCoverageSummaryEntity;
|
||||
pub use fee_event::FeeEventEntity;
|
||||
pub use fee_event_amount::FeeEventAmountEntity;
|
||||
pub use known_http_endpoint::KnownHttpEndpointEntity;
|
||||
pub use known_ws_endpoint::KnownWsEndpointEntity;
|
||||
pub use instruction_observation::InstructionObservationEntity;
|
||||
|
||||
34
kb_lib/src/db/entities/fee_event_amount.rs
Normal file
34
kb_lib/src/db/entities/fee_event_amount.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
// file: kb_lib/src/db/entities/fee_event_amount.rs
|
||||
|
||||
//! Fee event amount entity.
|
||||
|
||||
/// Persisted normalized fee event amount leg row.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
|
||||
pub struct FeeEventAmountEntity {
|
||||
/// Numeric primary key.
|
||||
pub id: i64,
|
||||
/// Related parent fee event id.
|
||||
pub fee_event_id: i64,
|
||||
/// Related transaction id.
|
||||
pub transaction_id: i64,
|
||||
/// Related decoded DEX event id, when available.
|
||||
pub decoded_event_id: std::option::Option<i64>,
|
||||
/// Stable leg index within one parent fee event.
|
||||
pub leg_index: i64,
|
||||
/// Fee component kind or semantic role.
|
||||
pub fee_component_kind: std::string::String,
|
||||
/// Token mint used by this fee amount leg.
|
||||
pub token_mint: std::string::String,
|
||||
/// Raw amount for this leg as decimal text.
|
||||
pub amount_raw: std::string::String,
|
||||
/// Source token account or lamport account, when decoded.
|
||||
pub source_account: std::option::Option<std::string::String>,
|
||||
/// Destination token account or lamport account, when decoded.
|
||||
pub destination_account: std::option::Option<std::string::String>,
|
||||
/// Extraction source used to prove this amount.
|
||||
pub amount_source: std::string::String,
|
||||
/// Source/proof payload JSON.
|
||||
pub payload_json: std::string::String,
|
||||
/// Creation timestamp encoded as RFC3339 UTC text.
|
||||
pub created_at: std::string::String,
|
||||
}
|
||||
@@ -13,6 +13,7 @@ mod dex_decode_replay_ledger;
|
||||
mod dex_decoded_event;
|
||||
mod dex_event_coverage_entry;
|
||||
mod fee_event;
|
||||
mod fee_event_amount;
|
||||
mod instruction_observation;
|
||||
mod known_http_endpoint;
|
||||
mod known_ws_endpoint;
|
||||
@@ -99,6 +100,11 @@ 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 fee_event_amount::query_fee_event_amounts_backfill_single_leg_from_fee_events;
|
||||
pub use fee_event_amount::query_fee_event_amounts_delete_by_fee_event_id;
|
||||
pub use fee_event_amount::query_fee_event_amounts_list_by_fee_event_id;
|
||||
pub use fee_event_amount::query_fee_event_amounts_replace_for_fee_event;
|
||||
pub use fee_event_amount::query_fee_events_upsert_with_amount_legs;
|
||||
pub use instruction_observation::query_instruction_observation_source_rows_list_by_signature;
|
||||
pub use instruction_observation::query_instruction_observation_source_rows_list_recent;
|
||||
pub use instruction_observation::query_instruction_observation_source_rows_list_replay_window;
|
||||
|
||||
@@ -136,6 +136,10 @@ WHERE decoded_event_id IN (
|
||||
"k_sol_pool_lifecycle_events",
|
||||
"DELETE FROM k_sol_pool_lifecycle_events WHERE transaction_id = ?",
|
||||
),
|
||||
(
|
||||
"k_sol_fee_event_amounts",
|
||||
"DELETE FROM k_sol_fee_event_amounts WHERE transaction_id = ?",
|
||||
),
|
||||
("k_sol_fee_events", "DELETE FROM k_sol_fee_events WHERE transaction_id = ?"),
|
||||
(
|
||||
"k_sol_reward_events",
|
||||
|
||||
@@ -156,6 +156,10 @@ WHERE id = ?
|
||||
id, error
|
||||
)));
|
||||
}
|
||||
let leg_result = query_fee_events_replace_scalar_amount_leg(database, id, dto).await;
|
||||
if let Err(error) = leg_result {
|
||||
return Err(error);
|
||||
}
|
||||
return Ok(id);
|
||||
}
|
||||
let insert_result = sqlx::query(
|
||||
@@ -227,7 +231,13 @@ LIMIT 1
|
||||
.fetch_one(pool)
|
||||
.await;
|
||||
match id_result {
|
||||
Ok(id) => return Ok(id),
|
||||
Ok(id) => {
|
||||
let leg_result = query_fee_events_replace_scalar_amount_leg(database, id, dto).await;
|
||||
if let Err(error) = leg_result {
|
||||
return Err(error);
|
||||
}
|
||||
return Ok(id);
|
||||
},
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot fetch inserted k_sol_fee_events id for signature '{}' on sqlite: {}",
|
||||
@@ -239,6 +249,88 @@ LIMIT 1
|
||||
}
|
||||
}
|
||||
|
||||
async fn query_fee_events_replace_scalar_amount_leg(
|
||||
database: &crate::Database,
|
||||
fee_event_id: i64,
|
||||
dto: &crate::FeeEventDto,
|
||||
) -> Result<(), crate::Error> {
|
||||
let token_mint = match dto.fee_token_mint.as_ref() {
|
||||
Some(token_mint) => {
|
||||
if token_mint.trim().is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
token_mint.clone()
|
||||
},
|
||||
None => return Ok(()),
|
||||
};
|
||||
let amount_raw = match dto.fee_amount_raw.as_ref() {
|
||||
Some(amount_raw) => {
|
||||
if amount_raw.trim().is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
amount_raw.clone()
|
||||
},
|
||||
None => return Ok(()),
|
||||
};
|
||||
let payload_json = scalar_fee_event_amount_payload_json(dto, token_mint.as_str(), amount_raw.as_str());
|
||||
let amount_leg = crate::FeeEventAmountDto::new(
|
||||
fee_event_id,
|
||||
dto.transaction_id,
|
||||
dto.decoded_event_id,
|
||||
0,
|
||||
dto.event_kind.clone(),
|
||||
token_mint,
|
||||
amount_raw,
|
||||
None,
|
||||
None,
|
||||
"parent_fee_event_amount".to_string(),
|
||||
payload_json,
|
||||
);
|
||||
let replace_result = crate::query_fee_event_amounts_replace_for_fee_event(
|
||||
database,
|
||||
fee_event_id,
|
||||
&[amount_leg],
|
||||
)
|
||||
.await;
|
||||
match replace_result {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn scalar_fee_event_amount_payload_json(
|
||||
dto: &crate::FeeEventDto,
|
||||
token_mint: &str,
|
||||
amount_raw: &str,
|
||||
) -> std::string::String {
|
||||
let mut object = serde_json::Map::new();
|
||||
object.insert(
|
||||
"decodedEventKind".to_string(),
|
||||
serde_json::Value::String(dto.event_kind.clone()),
|
||||
);
|
||||
object.insert(
|
||||
"protocolName".to_string(),
|
||||
serde_json::Value::String(dto.protocol_name.clone()),
|
||||
);
|
||||
object.insert(
|
||||
"legIndex".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(0_u64)),
|
||||
);
|
||||
object.insert(
|
||||
"tokenMint".to_string(),
|
||||
serde_json::Value::String(token_mint.to_string()),
|
||||
);
|
||||
object.insert(
|
||||
"amountRaw".to_string(),
|
||||
serde_json::Value::String(amount_raw.to_string()),
|
||||
);
|
||||
object.insert(
|
||||
"amountSource".to_string(),
|
||||
serde_json::Value::String("parent_fee_event_amount".to_string()),
|
||||
);
|
||||
return serde_json::Value::Object(object).to_string();
|
||||
}
|
||||
|
||||
/// Lists recent fee events ordered from newest to oldest.
|
||||
pub async fn query_fee_events_list_recent(
|
||||
database: &crate::Database,
|
||||
|
||||
677
kb_lib/src/db/queries/fee_event_amount.rs
Normal file
677
kb_lib/src/db/queries/fee_event_amount.rs
Normal file
@@ -0,0 +1,677 @@
|
||||
// file: kb_lib/src/db/queries/fee_event_amount.rs
|
||||
|
||||
//! Queries for `k_sol_fee_event_amounts`.
|
||||
|
||||
/// Deletes fee amount legs for one parent fee event.
|
||||
pub async fn query_fee_event_amounts_delete_by_fee_event_id(
|
||||
database: &crate::Database,
|
||||
fee_event_id: i64,
|
||||
) -> Result<u64, crate::Error> {
|
||||
match database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
let delete_result = sqlx::query(
|
||||
r#"
|
||||
DELETE FROM k_sol_fee_event_amounts
|
||||
WHERE fee_event_id = ?
|
||||
"#,
|
||||
)
|
||||
.bind(fee_event_id)
|
||||
.execute(pool)
|
||||
.await;
|
||||
match delete_result {
|
||||
Ok(result) => return Ok(result.rows_affected()),
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot delete k_sol_fee_event_amounts for fee_event_id '{}' on sqlite: {}",
|
||||
fee_event_id, error
|
||||
)));
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces all fee amount legs for one parent fee event.
|
||||
pub async fn query_fee_event_amounts_replace_for_fee_event(
|
||||
database: &crate::Database,
|
||||
fee_event_id: i64,
|
||||
dtos: &[crate::FeeEventAmountDto],
|
||||
) -> Result<u64, crate::Error> {
|
||||
let delete_result =
|
||||
query_fee_event_amounts_delete_by_fee_event_id(database, fee_event_id).await;
|
||||
if let Err(error) = delete_result {
|
||||
return Err(error);
|
||||
}
|
||||
let mut inserted: u64 = 0;
|
||||
match database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
for dto in dtos {
|
||||
let leg_index_i64 = i64::from(dto.leg_index);
|
||||
let insert_result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO k_sol_fee_event_amounts (
|
||||
fee_event_id,
|
||||
transaction_id,
|
||||
decoded_event_id,
|
||||
leg_index,
|
||||
fee_component_kind,
|
||||
token_mint,
|
||||
amount_raw,
|
||||
source_account,
|
||||
destination_account,
|
||||
amount_source,
|
||||
payload_json,
|
||||
created_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
)
|
||||
.bind(dto.fee_event_id)
|
||||
.bind(dto.transaction_id)
|
||||
.bind(dto.decoded_event_id)
|
||||
.bind(leg_index_i64)
|
||||
.bind(dto.fee_component_kind.clone())
|
||||
.bind(dto.token_mint.clone())
|
||||
.bind(dto.amount_raw.clone())
|
||||
.bind(dto.source_account.clone())
|
||||
.bind(dto.destination_account.clone())
|
||||
.bind(dto.amount_source.clone())
|
||||
.bind(dto.payload_json.clone())
|
||||
.bind(dto.created_at.to_rfc3339())
|
||||
.execute(pool)
|
||||
.await;
|
||||
match insert_result {
|
||||
Ok(_) => inserted += 1,
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot insert k_sol_fee_event_amounts leg '{}' for fee_event_id '{}' on sqlite: {}",
|
||||
dto.leg_index, fee_event_id, error
|
||||
)));
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
return Ok(inserted);
|
||||
}
|
||||
|
||||
/// Lists fee amount legs for one parent fee event.
|
||||
pub async fn query_fee_event_amounts_list_by_fee_event_id(
|
||||
database: &crate::Database,
|
||||
fee_event_id: i64,
|
||||
) -> Result<std::vec::Vec<crate::FeeEventAmountDto>, crate::Error> {
|
||||
match database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::FeeEventAmountEntity>(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
fee_event_id,
|
||||
transaction_id,
|
||||
decoded_event_id,
|
||||
leg_index,
|
||||
fee_component_kind,
|
||||
token_mint,
|
||||
amount_raw,
|
||||
source_account,
|
||||
destination_account,
|
||||
amount_source,
|
||||
payload_json,
|
||||
created_at
|
||||
FROM k_sol_fee_event_amounts
|
||||
WHERE fee_event_id = ?
|
||||
ORDER BY leg_index ASC
|
||||
"#,
|
||||
)
|
||||
.bind(fee_event_id)
|
||||
.fetch_all(pool)
|
||||
.await;
|
||||
let entities = match query_result {
|
||||
Ok(entities) => entities,
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot list k_sol_fee_event_amounts for fee_event_id '{}' on sqlite: {}",
|
||||
fee_event_id, error
|
||||
)));
|
||||
},
|
||||
};
|
||||
let mut dtos = std::vec::Vec::new();
|
||||
for entity in entities {
|
||||
let dto_result = crate::FeeEventAmountDto::try_from(entity);
|
||||
match dto_result {
|
||||
Ok(dto) => dtos.push(dto),
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
return Ok(dtos);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts or updates a fee parent and atomically replaces its amount legs.
|
||||
pub async fn query_fee_events_upsert_with_amount_legs(
|
||||
database: &crate::Database,
|
||||
fee_event: &crate::FeeEventDto,
|
||||
amount_legs: &[crate::FeeEventAmountDto],
|
||||
) -> Result<i64, crate::Error> {
|
||||
let fee_event_id_result = crate::query_fee_events_upsert(database, fee_event).await;
|
||||
let fee_event_id = match fee_event_id_result {
|
||||
Ok(fee_event_id) => fee_event_id,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let mut normalized_legs = std::vec::Vec::new();
|
||||
for amount_leg in amount_legs {
|
||||
let mut normalized_leg = amount_leg.clone();
|
||||
normalized_leg.fee_event_id = fee_event_id;
|
||||
normalized_leg.transaction_id = fee_event.transaction_id;
|
||||
normalized_leg.decoded_event_id = fee_event.decoded_event_id;
|
||||
normalized_legs.push(normalized_leg);
|
||||
}
|
||||
let replace_result = query_fee_event_amounts_replace_for_fee_event(
|
||||
database,
|
||||
fee_event_id,
|
||||
normalized_legs.as_slice(),
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = replace_result {
|
||||
return Err(error);
|
||||
}
|
||||
return Ok(fee_event_id);
|
||||
}
|
||||
|
||||
/// Backfills one amount leg for existing fee parents that expose one scalar amount.
|
||||
pub async fn query_fee_event_amounts_backfill_single_leg_from_fee_events(
|
||||
database: &crate::Database,
|
||||
) -> Result<u64, crate::Error> {
|
||||
let entities = match database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::FeeEventEntity>(
|
||||
r#"
|
||||
SELECT
|
||||
f.id,
|
||||
f.transaction_id,
|
||||
f.decoded_event_id,
|
||||
f.dex_id,
|
||||
f.pool_id,
|
||||
f.pair_id,
|
||||
f.signature,
|
||||
f.slot,
|
||||
f.protocol_name,
|
||||
f.program_id,
|
||||
f.event_kind,
|
||||
f.pool_account,
|
||||
f.actor_wallet,
|
||||
f.fee_token_mint,
|
||||
f.fee_amount_raw,
|
||||
f.payload_json,
|
||||
f.executed_at,
|
||||
f.created_at
|
||||
FROM k_sol_fee_events f
|
||||
WHERE f.fee_token_mint IS NOT NULL
|
||||
AND TRIM(f.fee_token_mint) <> ''
|
||||
AND f.fee_amount_raw IS NOT NULL
|
||||
AND TRIM(f.fee_amount_raw) <> ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM k_sol_fee_event_amounts a
|
||||
WHERE a.fee_event_id = f.id
|
||||
LIMIT 1
|
||||
)
|
||||
ORDER BY f.id ASC
|
||||
"#,
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await;
|
||||
match query_result {
|
||||
Ok(entities) => entities,
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot list fee events for single-leg amount backfill on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
let mut backfilled: u64 = 0;
|
||||
for entity in entities {
|
||||
let dto_result = crate::FeeEventDto::try_from(entity);
|
||||
let dto = match dto_result {
|
||||
Ok(dto) => dto,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let fee_event_id = match dto.id {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
return Err(crate::Error::InvalidState(
|
||||
"fee event backfill row does not contain an id".to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
let token_mint = match dto.fee_token_mint.as_ref() {
|
||||
Some(token_mint) => token_mint.clone(),
|
||||
None => continue,
|
||||
};
|
||||
let amount_raw = match dto.fee_amount_raw.as_ref() {
|
||||
Some(amount_raw) => amount_raw.clone(),
|
||||
None => continue,
|
||||
};
|
||||
let payload_json = single_fee_event_amount_backfill_payload_json(
|
||||
&dto,
|
||||
token_mint.as_str(),
|
||||
amount_raw.as_str(),
|
||||
);
|
||||
let amount_leg = crate::FeeEventAmountDto::new(
|
||||
fee_event_id,
|
||||
dto.transaction_id,
|
||||
dto.decoded_event_id,
|
||||
0,
|
||||
dto.event_kind.clone(),
|
||||
token_mint,
|
||||
amount_raw,
|
||||
None,
|
||||
None,
|
||||
"parent_fee_event_backfill".to_string(),
|
||||
payload_json,
|
||||
);
|
||||
let replace_result =
|
||||
query_fee_event_amounts_replace_for_fee_event(database, fee_event_id, &[amount_leg])
|
||||
.await;
|
||||
match replace_result {
|
||||
Ok(inserted) => backfilled += inserted,
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
return Ok(backfilled);
|
||||
}
|
||||
|
||||
fn single_fee_event_amount_backfill_payload_json(
|
||||
dto: &crate::FeeEventDto,
|
||||
token_mint: &str,
|
||||
amount_raw: &str,
|
||||
) -> std::string::String {
|
||||
let mut object = serde_json::Map::new();
|
||||
object.insert(
|
||||
"decodedEventKind".to_string(),
|
||||
serde_json::Value::String(dto.event_kind.clone()),
|
||||
);
|
||||
object.insert("protocolName".to_string(), serde_json::Value::String(dto.protocol_name.clone()));
|
||||
object.insert(
|
||||
"legIndex".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(0_u64)),
|
||||
);
|
||||
object.insert("tokenMint".to_string(), serde_json::Value::String(token_mint.to_string()));
|
||||
object.insert("amountRaw".to_string(), serde_json::Value::String(amount_raw.to_string()));
|
||||
object.insert(
|
||||
"amountSource".to_string(),
|
||||
serde_json::Value::String("parent_fee_event_backfill".to_string()),
|
||||
);
|
||||
return serde_json::Value::Object(object).to_string();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
async fn make_database() -> crate::Database {
|
||||
let tempdir_result = tempfile::tempdir();
|
||||
let tempdir = match tempdir_result {
|
||||
Ok(tempdir) => tempdir,
|
||||
Err(error) => panic!("tempdir must succeed: {}", error),
|
||||
};
|
||||
let database_path = tempdir.path().join("fee_event_amount.sqlite3");
|
||||
let config = crate::DatabaseConfig {
|
||||
enabled: true,
|
||||
backend: crate::DatabaseBackend::Sqlite,
|
||||
sqlite: crate::SqliteDatabaseConfig {
|
||||
path: database_path.to_string_lossy().to_string(),
|
||||
create_if_missing: true,
|
||||
busy_timeout_ms: 5000,
|
||||
max_connections: 1,
|
||||
auto_initialize_schema: true,
|
||||
use_wal: true,
|
||||
},
|
||||
};
|
||||
let database_result = crate::Database::connect_and_initialize(&config).await;
|
||||
match database_result {
|
||||
Ok(database) => return database,
|
||||
Err(error) => panic!("database init must succeed: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_leg(
|
||||
fee_event_id: i64,
|
||||
leg_index: u32,
|
||||
token_mint: &str,
|
||||
amount_raw: &str,
|
||||
) -> crate::FeeEventAmountDto {
|
||||
return crate::FeeEventAmountDto::new(
|
||||
fee_event_id,
|
||||
7001,
|
||||
Some(8001),
|
||||
leg_index,
|
||||
"trading_fee".to_string(),
|
||||
token_mint.to_string(),
|
||||
amount_raw.to_string(),
|
||||
Some(format!("Source{}", leg_index)),
|
||||
Some(format!("Destination{}", leg_index)),
|
||||
"inner_spl_transfer".to_string(),
|
||||
serde_json::json!({
|
||||
"test": true,
|
||||
"legIndex": leg_index
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fee_event_amounts_replace_roundtrip_supports_multiple_legs() {
|
||||
let database = make_database().await;
|
||||
let fee_event_id = 501;
|
||||
let legs = vec![
|
||||
make_leg(fee_event_id, 0, crate::WSOL_MINT_ID, "100"),
|
||||
make_leg(fee_event_id, 1, "TokenMint111", "200"),
|
||||
];
|
||||
let replace_result =
|
||||
crate::query_fee_event_amounts_replace_for_fee_event(&database, fee_event_id, &legs)
|
||||
.await;
|
||||
let inserted = match replace_result {
|
||||
Ok(inserted) => inserted,
|
||||
Err(error) => panic!("replace must succeed: {}", error),
|
||||
};
|
||||
assert_eq!(inserted, 2);
|
||||
|
||||
let list_result =
|
||||
crate::query_fee_event_amounts_list_by_fee_event_id(&database, fee_event_id).await;
|
||||
let listed = match list_result {
|
||||
Ok(listed) => listed,
|
||||
Err(error) => panic!("list must succeed: {}", error),
|
||||
};
|
||||
assert_eq!(listed.len(), 2);
|
||||
assert_eq!(listed[0].leg_index, 0);
|
||||
assert_eq!(listed[0].token_mint, crate::WSOL_MINT_ID);
|
||||
assert_eq!(listed[0].amount_raw, "100");
|
||||
assert_eq!(listed[1].leg_index, 1);
|
||||
assert_eq!(listed[1].token_mint, "TokenMint111");
|
||||
assert_eq!(listed[1].amount_raw, "200");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fee_event_amounts_replace_deletes_stale_legs() {
|
||||
let database = make_database().await;
|
||||
let fee_event_id = 502;
|
||||
let initial_legs = vec![
|
||||
make_leg(fee_event_id, 0, crate::WSOL_MINT_ID, "100"),
|
||||
make_leg(fee_event_id, 1, "TokenMint222", "200"),
|
||||
];
|
||||
let initial_result = crate::query_fee_event_amounts_replace_for_fee_event(
|
||||
&database,
|
||||
fee_event_id,
|
||||
&initial_legs,
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = initial_result {
|
||||
panic!("initial replace must succeed: {}", error);
|
||||
}
|
||||
|
||||
let replacement_legs = vec![make_leg(fee_event_id, 0, "TokenMint333", "300")];
|
||||
let replacement_result = crate::query_fee_event_amounts_replace_for_fee_event(
|
||||
&database,
|
||||
fee_event_id,
|
||||
&replacement_legs,
|
||||
)
|
||||
.await;
|
||||
let inserted = match replacement_result {
|
||||
Ok(inserted) => inserted,
|
||||
Err(error) => panic!("replacement must succeed: {}", error),
|
||||
};
|
||||
assert_eq!(inserted, 1);
|
||||
|
||||
let list_result =
|
||||
crate::query_fee_event_amounts_list_by_fee_event_id(&database, fee_event_id).await;
|
||||
let listed = match list_result {
|
||||
Ok(listed) => listed,
|
||||
Err(error) => panic!("list must succeed: {}", error),
|
||||
};
|
||||
assert_eq!(listed.len(), 1);
|
||||
assert_eq!(listed[0].leg_index, 0);
|
||||
assert_eq!(listed[0].token_mint, "TokenMint333");
|
||||
assert_eq!(listed[0].amount_raw, "300");
|
||||
}
|
||||
|
||||
fn make_fee_event(
|
||||
transaction_id: i64,
|
||||
decoded_event_id: i64,
|
||||
signature: &str,
|
||||
fee_token_mint: std::option::Option<&str>,
|
||||
fee_amount_raw: std::option::Option<&str>,
|
||||
) -> crate::FeeEventDto {
|
||||
return crate::FeeEventDto::new(
|
||||
transaction_id,
|
||||
Some(decoded_event_id),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
signature.to_string(),
|
||||
Some(1234),
|
||||
"test_protocol".to_string(),
|
||||
"TestProgram111".to_string(),
|
||||
"test_protocol.claim_fee".to_string(),
|
||||
None,
|
||||
None,
|
||||
fee_token_mint.map(|value| return value.to_string()),
|
||||
fee_amount_raw.map(|value| return value.to_string()),
|
||||
serde_json::json!({"test": true}).to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
async fn insert_legacy_fee_event_parent(
|
||||
database: &crate::Database,
|
||||
fee_event: &crate::FeeEventDto,
|
||||
) -> i64 {
|
||||
let slot_i64 = match fee_event.slot {
|
||||
Some(slot) => {
|
||||
let slot_result = i64::try_from(slot);
|
||||
match slot_result {
|
||||
Ok(slot_i64) => Some(slot_i64),
|
||||
Err(error) => panic!("slot conversion must succeed: {}", error),
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
match database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
let insert_result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO k_sol_fee_events (
|
||||
transaction_id,
|
||||
decoded_event_id,
|
||||
dex_id,
|
||||
pool_id,
|
||||
pair_id,
|
||||
signature,
|
||||
slot,
|
||||
protocol_name,
|
||||
program_id,
|
||||
event_kind,
|
||||
pool_account,
|
||||
actor_wallet,
|
||||
fee_token_mint,
|
||||
fee_amount_raw,
|
||||
payload_json,
|
||||
executed_at,
|
||||
created_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
)
|
||||
.bind(fee_event.transaction_id)
|
||||
.bind(fee_event.decoded_event_id)
|
||||
.bind(fee_event.dex_id)
|
||||
.bind(fee_event.pool_id)
|
||||
.bind(fee_event.pair_id)
|
||||
.bind(fee_event.signature.clone())
|
||||
.bind(slot_i64)
|
||||
.bind(fee_event.protocol_name.clone())
|
||||
.bind(fee_event.program_id.clone())
|
||||
.bind(fee_event.event_kind.clone())
|
||||
.bind(fee_event.pool_account.clone())
|
||||
.bind(fee_event.actor_wallet.clone())
|
||||
.bind(fee_event.fee_token_mint.clone())
|
||||
.bind(fee_event.fee_amount_raw.clone())
|
||||
.bind(fee_event.payload_json.clone())
|
||||
.bind(fee_event.executed_at.to_rfc3339())
|
||||
.bind(fee_event.created_at.to_rfc3339())
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = insert_result {
|
||||
panic!("legacy parent insert must succeed: {}", error);
|
||||
}
|
||||
let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
|
||||
r#"
|
||||
SELECT id
|
||||
FROM k_sol_fee_events
|
||||
WHERE signature = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
"#,
|
||||
)
|
||||
.bind(fee_event.signature.clone())
|
||||
.fetch_one(pool)
|
||||
.await;
|
||||
match id_result {
|
||||
Ok(id) => return id,
|
||||
Err(error) => panic!("legacy parent id fetch must succeed: {}", error),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fee_events_upsert_with_amount_legs_normalizes_parent_id() {
|
||||
let database = make_database().await;
|
||||
let fee_event = make_fee_event(7101, 8101, "signature-helper", None, None);
|
||||
let legs = vec![
|
||||
make_leg(0, 0, crate::WSOL_MINT_ID, "100"),
|
||||
make_leg(0, 1, "TokenMint555", "200"),
|
||||
];
|
||||
let upsert_result =
|
||||
crate::query_fee_events_upsert_with_amount_legs(&database, &fee_event, legs.as_slice())
|
||||
.await;
|
||||
let fee_event_id = match upsert_result {
|
||||
Ok(fee_event_id) => fee_event_id,
|
||||
Err(error) => panic!("upsert with legs must succeed: {}", error),
|
||||
};
|
||||
let list_result =
|
||||
crate::query_fee_event_amounts_list_by_fee_event_id(&database, fee_event_id).await;
|
||||
let listed = match list_result {
|
||||
Ok(listed) => listed,
|
||||
Err(error) => panic!("list must succeed: {}", error),
|
||||
};
|
||||
assert_eq!(listed.len(), 2);
|
||||
assert_eq!(listed[0].fee_event_id, fee_event_id);
|
||||
assert_eq!(listed[0].transaction_id, fee_event.transaction_id);
|
||||
assert_eq!(listed[0].decoded_event_id, fee_event.decoded_event_id);
|
||||
assert_eq!(listed[1].fee_event_id, fee_event_id);
|
||||
assert_eq!(listed[1].transaction_id, fee_event.transaction_id);
|
||||
assert_eq!(listed[1].decoded_event_id, fee_event.decoded_event_id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fee_events_upsert_automatically_creates_scalar_amount_leg() {
|
||||
let database = make_database().await;
|
||||
let fee_event = make_fee_event(
|
||||
7151,
|
||||
8151,
|
||||
"signature-auto-leg",
|
||||
Some(crate::WSOL_MINT_ID),
|
||||
Some("777"),
|
||||
);
|
||||
let upsert_result = crate::query_fee_events_upsert(&database, &fee_event).await;
|
||||
let fee_event_id = match upsert_result {
|
||||
Ok(fee_event_id) => fee_event_id,
|
||||
Err(error) => panic!("fee parent upsert must succeed: {}", error),
|
||||
};
|
||||
let list_result =
|
||||
crate::query_fee_event_amounts_list_by_fee_event_id(&database, fee_event_id).await;
|
||||
let listed = match list_result {
|
||||
Ok(listed) => listed,
|
||||
Err(error) => panic!("list must succeed: {}", error),
|
||||
};
|
||||
assert_eq!(listed.len(), 1);
|
||||
assert_eq!(listed[0].fee_event_id, fee_event_id);
|
||||
assert_eq!(listed[0].transaction_id, fee_event.transaction_id);
|
||||
assert_eq!(listed[0].decoded_event_id, fee_event.decoded_event_id);
|
||||
assert_eq!(listed[0].token_mint, crate::WSOL_MINT_ID);
|
||||
assert_eq!(listed[0].amount_raw, "777");
|
||||
assert_eq!(listed[0].amount_source, "parent_fee_event_amount");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fee_event_amounts_backfill_single_leg_from_existing_parent() {
|
||||
let database = make_database().await;
|
||||
let fee_event = make_fee_event(
|
||||
7201,
|
||||
8201,
|
||||
"signature-backfill",
|
||||
Some(crate::WSOL_MINT_ID),
|
||||
Some("999"),
|
||||
);
|
||||
let fee_event_id = insert_legacy_fee_event_parent(&database, &fee_event).await;
|
||||
let backfill_result =
|
||||
crate::query_fee_event_amounts_backfill_single_leg_from_fee_events(&database).await;
|
||||
let backfilled = match backfill_result {
|
||||
Ok(backfilled) => backfilled,
|
||||
Err(error) => panic!("backfill must succeed: {}", error),
|
||||
};
|
||||
assert_eq!(backfilled, 1);
|
||||
let list_result =
|
||||
crate::query_fee_event_amounts_list_by_fee_event_id(&database, fee_event_id).await;
|
||||
let listed = match list_result {
|
||||
Ok(listed) => listed,
|
||||
Err(error) => panic!("list must succeed: {}", error),
|
||||
};
|
||||
assert_eq!(listed.len(), 1);
|
||||
assert_eq!(listed[0].token_mint, crate::WSOL_MINT_ID);
|
||||
assert_eq!(listed[0].amount_raw, "999");
|
||||
assert_eq!(listed[0].amount_source, "parent_fee_event_backfill");
|
||||
|
||||
let second_backfill_result =
|
||||
crate::query_fee_event_amounts_backfill_single_leg_from_fee_events(&database).await;
|
||||
let second_backfilled = match second_backfill_result {
|
||||
Ok(second_backfilled) => second_backfilled,
|
||||
Err(error) => panic!("second backfill must succeed: {}", error),
|
||||
};
|
||||
assert_eq!(second_backfilled, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fee_event_amounts_delete_by_parent_removes_all_legs() {
|
||||
let database = make_database().await;
|
||||
let fee_event_id = 503;
|
||||
let legs = vec![
|
||||
make_leg(fee_event_id, 0, crate::WSOL_MINT_ID, "100"),
|
||||
make_leg(fee_event_id, 1, "TokenMint444", "200"),
|
||||
];
|
||||
let replace_result =
|
||||
crate::query_fee_event_amounts_replace_for_fee_event(&database, fee_event_id, &legs)
|
||||
.await;
|
||||
if let Err(error) = replace_result {
|
||||
panic!("replace must succeed: {}", error);
|
||||
}
|
||||
|
||||
let delete_result =
|
||||
crate::query_fee_event_amounts_delete_by_fee_event_id(&database, fee_event_id).await;
|
||||
let deleted = match delete_result {
|
||||
Ok(deleted) => deleted,
|
||||
Err(error) => panic!("delete must succeed: {}", error),
|
||||
};
|
||||
assert_eq!(deleted, 2);
|
||||
|
||||
let list_result =
|
||||
crate::query_fee_event_amounts_list_by_fee_event_id(&database, fee_event_id).await;
|
||||
let listed = match list_result {
|
||||
Ok(listed) => listed,
|
||||
Err(error) => panic!("list must succeed: {}", error),
|
||||
};
|
||||
assert!(listed.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -342,6 +342,30 @@ pub(crate) async fn ensure_schema(database: &crate::Database) -> Result<(), crat
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
}
|
||||
let result = create_tbl_fee_event_amounts(pool).await;
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
}
|
||||
let result = create_idx_fee_event_amounts_fee_event_id(pool).await;
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
}
|
||||
let result = create_idx_fee_event_amounts_transaction_id(pool).await;
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
}
|
||||
let result = create_idx_fee_event_amounts_decoded_event_id(pool).await;
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
}
|
||||
let result = create_idx_fee_event_amounts_token_mint(pool).await;
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
}
|
||||
let result = create_uix_fee_event_amounts_fee_event_leg(pool).await;
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
}
|
||||
let result = create_tbl_reward_events(pool).await;
|
||||
if let Err(error) = result {
|
||||
return Err(error);
|
||||
@@ -2702,6 +2726,107 @@ WHERE decoded_event_id IS NOT NULL
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Creates `k_sol_fee_event_amounts`.
|
||||
async fn create_tbl_fee_event_amounts(pool: &sqlx::SqlitePool) -> Result<(), crate::Error> {
|
||||
return execute_sqlite_schema_statement(
|
||||
pool,
|
||||
"create_tbl_fee_event_amounts",
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS k_sol_fee_event_amounts (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
fee_event_id INTEGER NOT NULL,
|
||||
transaction_id INTEGER NOT NULL,
|
||||
decoded_event_id INTEGER NULL,
|
||||
leg_index INTEGER NOT NULL,
|
||||
fee_component_kind TEXT NOT NULL,
|
||||
token_mint TEXT NOT NULL,
|
||||
amount_raw TEXT NOT NULL,
|
||||
source_account TEXT NULL,
|
||||
destination_account TEXT NULL,
|
||||
amount_source TEXT NOT NULL,
|
||||
payload_json TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Creates index on `k_sol_fee_event_amounts(fee_event_id)`.
|
||||
async fn create_idx_fee_event_amounts_fee_event_id(
|
||||
pool: &sqlx::SqlitePool,
|
||||
) -> Result<(), crate::Error> {
|
||||
return execute_sqlite_schema_statement(
|
||||
pool,
|
||||
"create_idx_fee_event_amounts_fee_event_id",
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS idx_fee_event_amounts_fee_event_id
|
||||
ON k_sol_fee_event_amounts (fee_event_id)
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Creates index on `k_sol_fee_event_amounts(transaction_id)`.
|
||||
async fn create_idx_fee_event_amounts_transaction_id(
|
||||
pool: &sqlx::SqlitePool,
|
||||
) -> Result<(), crate::Error> {
|
||||
return execute_sqlite_schema_statement(
|
||||
pool,
|
||||
"create_idx_fee_event_amounts_transaction_id",
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS idx_fee_event_amounts_transaction_id
|
||||
ON k_sol_fee_event_amounts (transaction_id)
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Creates index on `k_sol_fee_event_amounts(decoded_event_id)`.
|
||||
async fn create_idx_fee_event_amounts_decoded_event_id(
|
||||
pool: &sqlx::SqlitePool,
|
||||
) -> Result<(), crate::Error> {
|
||||
return execute_sqlite_schema_statement(
|
||||
pool,
|
||||
"create_idx_fee_event_amounts_decoded_event_id",
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS idx_fee_event_amounts_decoded_event_id
|
||||
ON k_sol_fee_event_amounts (decoded_event_id)
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Creates index on `k_sol_fee_event_amounts(token_mint)`.
|
||||
async fn create_idx_fee_event_amounts_token_mint(
|
||||
pool: &sqlx::SqlitePool,
|
||||
) -> Result<(), crate::Error> {
|
||||
return execute_sqlite_schema_statement(
|
||||
pool,
|
||||
"create_idx_fee_event_amounts_token_mint",
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS idx_fee_event_amounts_token_mint
|
||||
ON k_sol_fee_event_amounts (token_mint)
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Creates unique index on one fee amount leg per parent fee event and leg index.
|
||||
async fn create_uix_fee_event_amounts_fee_event_leg(
|
||||
pool: &sqlx::SqlitePool,
|
||||
) -> Result<(), crate::Error> {
|
||||
return execute_sqlite_schema_statement(
|
||||
pool,
|
||||
"create_uix_fee_event_amounts_fee_event_leg",
|
||||
r#"
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uix_fee_event_amounts_fee_event_leg
|
||||
ON k_sol_fee_event_amounts (fee_event_id, leg_index)
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Creates `k_sol_reward_events`.
|
||||
async fn create_tbl_reward_events(pool: &sqlx::SqlitePool) -> Result<(), crate::Error> {
|
||||
return execute_sqlite_schema_statement(
|
||||
|
||||
@@ -43,6 +43,7 @@ pub use meteora_damm_v2::MeteoraDammV2SwapDecoded;
|
||||
pub use meteora_dbc::MeteoraDbcCreatePoolDecoded;
|
||||
pub use meteora_dbc::MeteoraDbcDecodedEvent;
|
||||
pub use meteora_dbc::MeteoraDbcDecoder;
|
||||
pub use meteora_dbc::MeteoraDbcInstructionDecoded;
|
||||
pub use meteora_dbc::MeteoraDbcSwapDecoded;
|
||||
pub use meteora_dlmm::MeteoraDlmmCreatePoolDecoded;
|
||||
pub use meteora_dlmm::MeteoraDlmmDecodedEvent;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1045,7 +1045,7 @@ impl DexDecodeService {
|
||||
event.instruction_id,
|
||||
"meteora_dbc",
|
||||
event.program_id.clone(),
|
||||
"meteora_dbc.create_pool",
|
||||
event.event_kind.as_str(),
|
||||
event.pool_account.clone(),
|
||||
None,
|
||||
event.token_a_mint.clone(),
|
||||
@@ -1063,7 +1063,7 @@ impl DexDecodeService {
|
||||
event.instruction_id,
|
||||
"meteora_dbc",
|
||||
event.program_id.clone(),
|
||||
"meteora_dbc.swap",
|
||||
event.event_kind.as_str(),
|
||||
event.pool_account.clone(),
|
||||
None,
|
||||
event.token_a_mint.clone(),
|
||||
@@ -1073,6 +1073,24 @@ impl DexDecodeService {
|
||||
)
|
||||
.await;
|
||||
},
|
||||
crate::MeteoraDbcDecodedEvent::Instruction(event) => {
|
||||
return self
|
||||
.materialize_named_dex_event(
|
||||
transaction,
|
||||
event.transaction_id,
|
||||
event.instruction_id,
|
||||
"meteora_dbc",
|
||||
event.program_id.clone(),
|
||||
event.event_kind.as_str(),
|
||||
event.pool_account.clone(),
|
||||
None,
|
||||
event.token_a_mint.clone(),
|
||||
event.token_b_mint.clone(),
|
||||
event.related_account.clone(),
|
||||
event.payload_json.clone(),
|
||||
)
|
||||
.await;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -164,6 +164,9 @@ pub(crate) fn dex_detection_route(
|
||||
("meteora_dbc", "meteora_dbc.swap") => {
|
||||
return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDbcPool);
|
||||
},
|
||||
("meteora_dbc", "meteora_dbc.swap2") => {
|
||||
return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDbcPool);
|
||||
},
|
||||
("meteora_dlmm", "meteora_dlmm.create_pool") => {
|
||||
return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDlmmPool);
|
||||
},
|
||||
@@ -304,6 +307,26 @@ mod tests {
|
||||
assert!(!crate::dex_detection_route::decoded_event_has_full_pool_context(&event));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn meteora_dbc_swap2_routes_to_pool_detection() {
|
||||
let event = make_decoded_event(
|
||||
"meteora_dbc",
|
||||
"meteora_dbc.swap2",
|
||||
Some("Pool111"),
|
||||
Some("TokenA111"),
|
||||
Some("TokenB111"),
|
||||
);
|
||||
let route_option = crate::dex_detection_route::dex_detection_route(&event);
|
||||
let route = match route_option {
|
||||
Some(route) => route,
|
||||
None => panic!("route must be selected"),
|
||||
};
|
||||
assert_eq!(route, crate::dex_detection_route::DexDetectionRoute::MeteoraDbcPool);
|
||||
assert!(crate::dex_detection_route::dex_detection_route_requires_full_pool_context(
|
||||
route
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raydium_launchpad_initialize_route_requires_full_pool_context() {
|
||||
let event = make_decoded_event(
|
||||
|
||||
@@ -350,6 +350,9 @@ pub fn is_dex_trade_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.ends_with(".swap") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.ends_with(".swap2") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".swap_") {
|
||||
return true;
|
||||
}
|
||||
@@ -375,6 +378,22 @@ pub fn is_dex_candle_candidate_event_kind(event_kind: &str) -> bool {
|
||||
|
||||
/// Returns true for liquidity lifecycle changes that must not become candles.
|
||||
pub fn is_dex_liquidity_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.starts_with("meteora_dbc.")
|
||||
&& (event_kind.contains("fee")
|
||||
|| event_kind.contains("surplus")
|
||||
|| event_kind.contains("leftover")
|
||||
|| event_kind.contains("zap_protocol_fee"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if event_kind == "meteora_dbc.migrate_meteora_damm_claim_lp_token"
|
||||
|| event_kind == "meteora_dbc.migrate_meteora_damm_lock_lp_token"
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if event_kind.starts_with("meteora_dbc.") && event_kind.contains("lp_token") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".withdraw_pnl") {
|
||||
return false;
|
||||
}
|
||||
@@ -490,6 +509,14 @@ pub fn is_dex_position_close_event_kind(event_kind: &str) -> bool {
|
||||
|
||||
/// Returns true for fee collection events.
|
||||
pub fn is_dex_fee_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.starts_with("meteora_dbc.")
|
||||
&& (event_kind.contains("fee")
|
||||
|| event_kind.contains("surplus")
|
||||
|| event_kind.contains("leftover")
|
||||
|| event_kind.contains("zap_protocol_fee"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if event_kind.starts_with("pump_fees.")
|
||||
&& (event_kind.contains("donation_fee_pda_cranked")
|
||||
|| event_kind.contains("sweep_buyback")
|
||||
@@ -639,7 +666,7 @@ pub fn is_dex_pool_lifecycle_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind == "raydium_amm_v4.pre_initialize" {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".create_lock_escrow") {
|
||||
if event_kind.contains(".create_lock_escrow") || event_kind.contains(".create_locker") {
|
||||
return true;
|
||||
}
|
||||
if event_kind.contains(".initialize_bin_array") {
|
||||
@@ -731,6 +758,9 @@ pub fn is_dex_migration_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.contains(".migrate_pool_coin_creator") {
|
||||
return false;
|
||||
}
|
||||
if event_kind.starts_with("meteora_dbc.") && event_kind.contains("metadata") {
|
||||
return false;
|
||||
}
|
||||
if event_kind.contains(".migrate") {
|
||||
return true;
|
||||
}
|
||||
@@ -742,6 +772,11 @@ pub fn is_dex_migration_event_kind(event_kind: &str) -> bool {
|
||||
|
||||
/// Returns true for pool creation or initialization events.
|
||||
pub fn is_dex_pool_creation_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind == "meteora_dbc.evt_initialize_pool_event"
|
||||
|| event_kind.contains(".initialize_virtual_pool")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if event_kind == "raydium_amm_v4.pre_initialize" {
|
||||
return true;
|
||||
}
|
||||
@@ -824,6 +859,15 @@ pub fn is_dex_token_account_close_event_kind(event_kind: &str) -> bool {
|
||||
|
||||
/// Returns true for admin, configuration or permission changes.
|
||||
pub fn is_dex_admin_event_kind(event_kind: &str) -> bool {
|
||||
if event_kind.starts_with("meteora_dbc.")
|
||||
&& (event_kind.contains("config")
|
||||
|| event_kind.contains("operator")
|
||||
|| event_kind.contains("metadata")
|
||||
|| event_kind.contains("pool_creator")
|
||||
|| event_kind == "meteora_dbc.transfer_pool_creator")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if event_kind.starts_with("pump_fees.")
|
||||
&& (event_kind.contains("authority")
|
||||
|| event_kind.contains("admin")
|
||||
@@ -1232,13 +1276,16 @@ mod tests {
|
||||
);
|
||||
assert_eq!(super::classify_dex_event_category_code("raydium_clmm.swap"), "trade");
|
||||
assert_eq!(super::classify_dex_event_category_code("raydium_clmm.swap_v2"), "trade");
|
||||
assert_eq!(super::classify_dex_event_category_code("meteora_dbc.swap2"), "trade");
|
||||
assert_eq!(super::classify_dex_event_category_code("raydium_clmm.exact_output"), "trade");
|
||||
assert_eq!(super::classify_dex_event_category_code("pump_fun.buy"), "trade");
|
||||
assert_eq!(super::classify_dex_event_category_code("pump_fun.buy_v2"), "trade");
|
||||
assert_eq!(super::classify_dex_event_category_code("pump_fun.sell_v2"), "trade");
|
||||
assert_eq!(super::classify_dex_event_category_code("pump_fun.trade_event"), "trade");
|
||||
assert!(super::is_dex_trade_event_kind("raydium_cpmm.swap_base_input"));
|
||||
assert!(super::is_dex_trade_event_kind("meteora_dbc.swap2"));
|
||||
assert!(super::is_dex_candle_candidate_event_kind("raydium_cpmm.swap_base_input"));
|
||||
assert!(super::is_dex_candle_candidate_event_kind("meteora_dbc.swap2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -65,6 +65,25 @@ impl DexEventCoverageService {
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
if decoder_code.as_deref().is_none() || decoder_code.as_deref() == Some("meteora_dbc") {
|
||||
let supplemental_entries = local_meteora_dbc_registry_entries();
|
||||
for entry in &supplemental_entries {
|
||||
let coverage_entry = build_coverage_entry_from_upstream(entry);
|
||||
let upsert_result = crate::query_dex_event_coverage_entries_upsert(
|
||||
self.database.as_ref(),
|
||||
&coverage_entry,
|
||||
)
|
||||
.await;
|
||||
match upsert_result {
|
||||
Ok(_) => upserted_entry_count += 1,
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
return Ok((
|
||||
search_result.entries.len() + supplemental_entries.len(),
|
||||
upserted_entry_count,
|
||||
));
|
||||
}
|
||||
return Ok((search_result.entries.len(), upserted_entry_count));
|
||||
}
|
||||
|
||||
@@ -99,6 +118,11 @@ impl DexEventCoverageService {
|
||||
if let Err(error) = duplicate_cleanup_result {
|
||||
return Err(error);
|
||||
}
|
||||
let meteora_dbc_duplicate_cleanup_result =
|
||||
self.cleanup_duplicate_meteora_dbc_logical_coverage_rows(&decoder_code).await;
|
||||
if let Err(error) = meteora_dbc_duplicate_cleanup_result {
|
||||
return Err(error);
|
||||
}
|
||||
let refreshed_entry_count = match &decoder_code {
|
||||
Some(decoder_code) => {
|
||||
let refresh_result =
|
||||
@@ -159,6 +183,11 @@ impl DexEventCoverageService {
|
||||
if let Err(error) = duplicate_cleanup_result {
|
||||
return Err(error);
|
||||
}
|
||||
let meteora_dbc_duplicate_cleanup_result =
|
||||
self.cleanup_duplicate_meteora_dbc_logical_coverage_rows(&decoder_code).await;
|
||||
if let Err(error) = meteora_dbc_duplicate_cleanup_result {
|
||||
return Err(error);
|
||||
}
|
||||
let refreshed_entry_count = match &decoder_code {
|
||||
Some(decoder_code) => {
|
||||
let refresh_result =
|
||||
@@ -278,6 +307,50 @@ WHERE decoder_code = 'pump_swap'
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn cleanup_duplicate_meteora_dbc_logical_coverage_rows(
|
||||
&self,
|
||||
decoder_code: &std::option::Option<std::string::String>,
|
||||
) -> Result<u64, crate::Error> {
|
||||
if let Some(decoder_code) = decoder_code {
|
||||
if decoder_code != "meteora_dbc" {
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
match self.database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
let query_result = sqlx::query(
|
||||
r#"
|
||||
DELETE FROM k_sol_dex_event_coverage_entries
|
||||
WHERE decoder_code = 'meteora_dbc'
|
||||
AND id NOT IN (
|
||||
SELECT MIN(id)
|
||||
FROM k_sol_dex_event_coverage_entries
|
||||
WHERE decoder_code = 'meteora_dbc'
|
||||
GROUP BY
|
||||
decoder_code,
|
||||
COALESCE(program_id, ''),
|
||||
entry_kind,
|
||||
entry_name,
|
||||
COALESCE(discriminator_hex, ''),
|
||||
COALESCE(local_event_kind, '')
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
match query_result {
|
||||
Ok(query_result) => return Ok(query_result.rows_affected()),
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot delete duplicate Meteora DBC logical coverage rows on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_coverage_entry_from_upstream(
|
||||
@@ -309,6 +382,353 @@ fn build_coverage_entry_from_upstream(
|
||||
return coverage_entry;
|
||||
}
|
||||
|
||||
fn local_meteora_dbc_registry_entries() -> std::vec::Vec<crate::UpstreamRegistryEntryDto> {
|
||||
let mut entries = std::vec::Vec::new();
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"anchor_self_cpi_log",
|
||||
Some("e445a52e51cb9a1d"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"instruction_audit",
|
||||
None,
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"claim_creator_trading_fee",
|
||||
Some("52dcfabd03556b2d"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"claim_partner_pool_creation_fee",
|
||||
Some("faee1a048b0a65f8"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"claim_protocol_fee",
|
||||
Some("a5e4853063f9ff21"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"claim_protocol_pool_creation_fee",
|
||||
Some("72cd53bcf0991936"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"claim_trading_fee",
|
||||
Some("08ec5931987db151"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"close_claim_protocol_fee_operator",
|
||||
Some("082957235030791a"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"close_operator_account",
|
||||
Some("ab09d54a7817031d"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"create_config",
|
||||
Some("c9cff3724b6f2fbd"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"create_locker",
|
||||
Some("a75a899a4b2f1154"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"create_operator_account",
|
||||
Some("dd40f695f099e5a3"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"create_partner_metadata",
|
||||
Some("c0a8eabfbce2e3ff"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"create_virtual_pool_metadata",
|
||||
Some("2d61bb67fe6d7c86"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"creator_withdraw_surplus",
|
||||
Some("a50389071c864c50"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"initialize_virtual_pool_with_spl_token",
|
||||
Some("8c55d7b06636684f"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"initialize_virtual_pool_with_token2022",
|
||||
Some("a976334e916edc9b"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"migrate_meteora_damm",
|
||||
Some("1b013016b43f76d9"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"migrate_meteora_damm_claim_lp_token",
|
||||
Some("8b85021e5b917f9a"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"migrate_meteora_damm_lock_lp_token",
|
||||
Some("b137ee9dfb58a52a"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"migration_damm_v2",
|
||||
Some("9ca9e66735e45040"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"migration_damm_v2_create_metadata",
|
||||
Some("6dbd1324c3b7de52"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"migration_meteora_damm_create_metadata",
|
||||
Some("2f5e7e73dde2c285"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"partner_withdraw_surplus",
|
||||
Some("a8ad4864c962265c"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"swap",
|
||||
Some("f8c69e91e17587c8"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"swap2",
|
||||
Some("414b3f4ceb5b5b88"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"transfer_pool_creator",
|
||||
Some("1407a9213a93a621"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"withdraw_leftover",
|
||||
Some("14c6caedebf3b742"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"withdraw_migration_fee",
|
||||
Some("ed8e2d178106dea2"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_INSTRUCTION,
|
||||
"zap_protocol_fee",
|
||||
Some("d59bbb2238b65bf0"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_claim_creator_trading_fee_event_local_idl",
|
||||
Some("9ae4d7ca859bd68a"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_claim_pool_creation_fee_event_local_idl",
|
||||
Some("956f952c8840af3e"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_claim_protocol_fee_event_local_idl",
|
||||
Some("baf44bfbbc0d1921"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_claim_protocol_liquidity_migration_fee_event_local_idl",
|
||||
Some("51a8741fa1561b23"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_claim_trading_fee_event_local_idl",
|
||||
Some("1a5375f05cca70fe"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_close_claim_fee_operator_event_local_idl",
|
||||
Some("6f2725376ed8c217"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_create_claim_fee_operator_event_local_idl",
|
||||
Some("1506997844741cb1"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_create_config_event_local_idl",
|
||||
Some("83cfb4aeb449a536"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_create_config_v2_event_local_idl",
|
||||
Some("a34a42bb77c31a90"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_create_meteora_migration_metadata_event_local_idl",
|
||||
Some("63a7853fd68faf8b"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_creator_withdraw_surplus_event_local_idl",
|
||||
Some("9849150f4257359d"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_curve_complete_event_local_idl",
|
||||
Some("e5e756549c864b18"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_initialize_pool_event_local_idl",
|
||||
Some("e432f655cb428625"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_partner_claim_pool_creation_fee_event_local_idl",
|
||||
Some("aedf2c96916259c3"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_partner_metadata_event_local_idl",
|
||||
Some("c87f06370d200896"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_partner_withdraw_migration_fee_event_local_idl",
|
||||
Some("b5697f4308bb7839"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_partner_withdraw_surplus_event_local_idl",
|
||||
Some("c3389809e8482316"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_swap_event_local_idl",
|
||||
Some("1b3c15d58aaabb93"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_swap2_event_local_idl",
|
||||
Some("bd4233a826507599"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_update_pool_creator_event_local_idl",
|
||||
Some("6be1a5ed5b9ed5dc"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_virtual_pool_metadata_event_local_idl",
|
||||
Some("bc12484cc35b264a"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_withdraw_leftover_event_local_idl",
|
||||
Some("bfbd688f6f9c5ee5"),
|
||||
);
|
||||
add_local_meteora_dbc_entry(
|
||||
&mut entries,
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
"evt_withdraw_migration_fee_event_local_idl",
|
||||
Some("1acb5455a11764d6"),
|
||||
);
|
||||
return entries;
|
||||
}
|
||||
|
||||
fn add_local_meteora_dbc_entry(
|
||||
entries: &mut std::vec::Vec<crate::UpstreamRegistryEntryDto>,
|
||||
entry_kind: &str,
|
||||
entry_name: &str,
|
||||
discriminator_hex: std::option::Option<&str>,
|
||||
) {
|
||||
entries.push(crate::UpstreamRegistryEntryDto {
|
||||
source_repo: Some("local_idl".to_string()),
|
||||
source_path: Some(
|
||||
"idls/meteora_dbc.dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN.json".to_string(),
|
||||
),
|
||||
decoder_code: "meteora_dbc".to_string(),
|
||||
program_id: Some(crate::METEORA_DBC_PROGRAM_ID.to_string()),
|
||||
program_family: "meteora".to_string(),
|
||||
surface_kind: "launch_bonding_curve".to_string(),
|
||||
entry_kind: entry_kind.to_string(),
|
||||
entry_name: entry_name.to_string(),
|
||||
discriminator_hex: discriminator_hex.map(|value| return value.to_string()),
|
||||
discriminator_len: discriminator_hex.map(|_| return 8_u16),
|
||||
proof_status: crate::PROOF_STATUS_UPSTREAM_GIT_MAPPED_UNVERIFIED.to_string(),
|
||||
notes: "supplemental local Meteora DBC IDL coverage row".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
fn infer_expected_db_target_for_entry(
|
||||
decoder_code: &str,
|
||||
entry_name: &str,
|
||||
@@ -324,6 +744,9 @@ fn infer_expected_db_target_for_entry(
|
||||
if decoder_code == "pump_fees" {
|
||||
return infer_pump_fees_expected_db_target(entry_name, entry_kind);
|
||||
}
|
||||
if decoder_code == "meteora_dbc" {
|
||||
return infer_meteora_dbc_expected_db_target(entry_name, entry_kind);
|
||||
}
|
||||
if decoder_code == "raydium_cpmm"
|
||||
&& (entry_name == "swap_event" || entry_name == "anchor_idl_instruction")
|
||||
{
|
||||
@@ -1094,9 +1517,236 @@ fn infer_event_family_for_entry(
|
||||
if decoder_code == "raydium_stable_swap" {
|
||||
return infer_raydium_stable_swap_event_family(entry_name, entry_kind);
|
||||
}
|
||||
if decoder_code == "meteora_dbc" {
|
||||
return infer_meteora_dbc_event_family(entry_name, entry_kind);
|
||||
}
|
||||
return infer_event_family(entry_name, entry_kind);
|
||||
}
|
||||
|
||||
fn infer_meteora_dbc_expected_db_target(
|
||||
entry_name: &str,
|
||||
entry_kind: &str,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
if entry_kind == crate::ENTRY_KIND_PROGRAM || entry_kind == crate::ENTRY_KIND_ACCOUNT {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string());
|
||||
}
|
||||
let family = infer_meteora_dbc_event_family(entry_name, entry_kind);
|
||||
let family = match family {
|
||||
Some(family) => family,
|
||||
None => {
|
||||
return Some(
|
||||
crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
let normalized_entry_name = entry_name.strip_suffix("_local_idl").unwrap_or(entry_name);
|
||||
if family == "swap" && (normalized_entry_name == "swap" || normalized_entry_name == "swap2") {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_TRADE_EVENTS.to_string());
|
||||
}
|
||||
if family == "swap" {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string());
|
||||
}
|
||||
if entry_kind == crate::ENTRY_KIND_EVENT
|
||||
&& meteora_dbc_anchor_event_is_decoded_only_source(normalized_entry_name)
|
||||
{
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string());
|
||||
}
|
||||
if family == "pool_create" || family == "migration" || family == "pool_lifecycle" {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_POOL_LIFECYCLE_EVENTS.to_string());
|
||||
}
|
||||
if family == "liquidity" {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS.to_string());
|
||||
}
|
||||
if family == "fee" {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_FEE_EVENTS.to_string());
|
||||
}
|
||||
if family == "admin_config" {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_POOL_ADMIN_EVENTS.to_string());
|
||||
}
|
||||
if family == "audit" {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string());
|
||||
}
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string());
|
||||
}
|
||||
|
||||
fn meteora_dbc_anchor_event_is_decoded_only_source(entry_name: &str) -> bool {
|
||||
return matches!(
|
||||
entry_name,
|
||||
"evt_claim_creator_trading_fee_event"
|
||||
| "evt_claim_pool_creation_fee_event"
|
||||
| "evt_claim_protocol_fee_event"
|
||||
| "evt_claim_protocol_liquidity_migration_fee_event"
|
||||
| "evt_claim_trading_fee_event"
|
||||
| "evt_close_claim_fee_operator_event"
|
||||
| "evt_create_claim_fee_operator_event"
|
||||
| "evt_create_config_event"
|
||||
| "evt_create_config_v2_event"
|
||||
| "evt_create_meteora_migration_metadata_event"
|
||||
| "evt_creator_withdraw_surplus_event"
|
||||
| "evt_initialize_pool_event"
|
||||
| "evt_partner_claim_pool_creation_fee_event"
|
||||
| "evt_partner_metadata_event"
|
||||
| "evt_partner_withdraw_migration_fee_event"
|
||||
| "evt_partner_withdraw_surplus_event"
|
||||
| "evt_update_pool_creator_event"
|
||||
| "evt_virtual_pool_metadata_event"
|
||||
| "evt_withdraw_leftover_event"
|
||||
| "evt_withdraw_migration_fee_event"
|
||||
);
|
||||
}
|
||||
|
||||
fn infer_meteora_dbc_event_family(
|
||||
entry_name: &str,
|
||||
entry_kind: &str,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
if entry_kind == crate::ENTRY_KIND_PROGRAM {
|
||||
return None;
|
||||
}
|
||||
if entry_name == "anchor_self_cpi_log" || entry_name == "instruction_audit" {
|
||||
return Some("audit".to_string());
|
||||
}
|
||||
if entry_name == "swap"
|
||||
|| entry_name == "swap2"
|
||||
|| entry_name == "evt_swap_event"
|
||||
|| entry_name == "evt_swap2_event"
|
||||
{
|
||||
return Some("swap".to_string());
|
||||
}
|
||||
if entry_name == "initialize_virtual_pool_with_spl_token"
|
||||
|| entry_name == "initialize_virtual_pool_with_token2022"
|
||||
|| entry_name == "evt_initialize_pool_event"
|
||||
{
|
||||
return Some("pool_create".to_string());
|
||||
}
|
||||
if entry_name == "create_locker" {
|
||||
return Some("pool_lifecycle".to_string());
|
||||
}
|
||||
if entry_name.contains("metadata") {
|
||||
return Some("admin_config".to_string());
|
||||
}
|
||||
if entry_name.contains("fee")
|
||||
|| entry_name.contains("surplus")
|
||||
|| entry_name.contains("leftover")
|
||||
|| entry_name == "zap_protocol_fee"
|
||||
{
|
||||
return Some("fee".to_string());
|
||||
}
|
||||
if entry_name.contains("migrate")
|
||||
|| entry_name.contains("migration")
|
||||
|| entry_name == "evt_curve_complete_event"
|
||||
{
|
||||
return Some("migration".to_string());
|
||||
}
|
||||
if entry_name.contains("lp_token") {
|
||||
return Some("liquidity".to_string());
|
||||
}
|
||||
if entry_name.contains("config")
|
||||
|| entry_name.contains("operator")
|
||||
|| entry_name.contains("metadata")
|
||||
|| entry_name == "transfer_pool_creator"
|
||||
|| entry_name == "evt_update_pool_creator_event"
|
||||
|| entry_name == "evt_partner_metadata_event"
|
||||
|| entry_name == "evt_virtual_pool_metadata_event"
|
||||
{
|
||||
return Some("admin_config".to_string());
|
||||
}
|
||||
return infer_event_family(entry_name, entry_kind);
|
||||
}
|
||||
|
||||
fn meteora_dbc_local_event_kind(entry_name: &str) -> std::option::Option<std::string::String> {
|
||||
if entry_name == "program" || entry_name == "state" {
|
||||
return None;
|
||||
}
|
||||
let normalized_event_name = match entry_name.strip_suffix("_local_idl") {
|
||||
Some(value) => value,
|
||||
None => entry_name,
|
||||
};
|
||||
if normalized_event_name == "anchor_self_cpi_log"
|
||||
|| normalized_event_name == "instruction_audit"
|
||||
{
|
||||
return Some("meteora_dbc.instruction_audit".to_string());
|
||||
}
|
||||
if normalized_event_name == "initialize_virtual_pool_with_spl_token"
|
||||
|| normalized_event_name == "initialize_virtual_pool_with_token2022"
|
||||
{
|
||||
return Some("meteora_dbc.create_pool".to_string());
|
||||
}
|
||||
if entry_name.starts_with("evt_") && !entry_name.ends_with("_local_idl") {
|
||||
return None;
|
||||
}
|
||||
if meteora_dbc_local_instruction_entry_is_known(entry_name)
|
||||
|| meteora_dbc_local_event_entry_is_known(normalized_event_name)
|
||||
{
|
||||
return Some(format!("meteora_dbc.{}", normalized_event_name));
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn meteora_dbc_local_instruction_entry_is_known(entry_name: &str) -> bool {
|
||||
return matches!(
|
||||
entry_name,
|
||||
"anchor_self_cpi_log"
|
||||
| "instruction_audit"
|
||||
| "claim_creator_trading_fee"
|
||||
| "claim_partner_pool_creation_fee"
|
||||
| "claim_protocol_fee"
|
||||
| "claim_protocol_pool_creation_fee"
|
||||
| "claim_trading_fee"
|
||||
| "close_claim_protocol_fee_operator"
|
||||
| "close_operator_account"
|
||||
| "create_config"
|
||||
| "create_locker"
|
||||
| "create_operator_account"
|
||||
| "create_partner_metadata"
|
||||
| "create_virtual_pool_metadata"
|
||||
| "creator_withdraw_surplus"
|
||||
| "initialize_virtual_pool_with_spl_token"
|
||||
| "initialize_virtual_pool_with_token2022"
|
||||
| "migrate_meteora_damm"
|
||||
| "migrate_meteora_damm_claim_lp_token"
|
||||
| "migrate_meteora_damm_lock_lp_token"
|
||||
| "migration_damm_v2"
|
||||
| "migration_damm_v2_create_metadata"
|
||||
| "migration_meteora_damm_create_metadata"
|
||||
| "partner_withdraw_surplus"
|
||||
| "swap"
|
||||
| "swap2"
|
||||
| "transfer_pool_creator"
|
||||
| "withdraw_leftover"
|
||||
| "withdraw_migration_fee"
|
||||
| "zap_protocol_fee"
|
||||
);
|
||||
}
|
||||
|
||||
fn meteora_dbc_local_event_entry_is_known(entry_name: &str) -> bool {
|
||||
return matches!(
|
||||
entry_name,
|
||||
"evt_claim_creator_trading_fee_event"
|
||||
| "evt_claim_pool_creation_fee_event"
|
||||
| "evt_claim_protocol_fee_event"
|
||||
| "evt_claim_protocol_liquidity_migration_fee_event"
|
||||
| "evt_claim_trading_fee_event"
|
||||
| "evt_close_claim_fee_operator_event"
|
||||
| "evt_create_claim_fee_operator_event"
|
||||
| "evt_create_config_event"
|
||||
| "evt_create_config_v2_event"
|
||||
| "evt_create_meteora_migration_metadata_event"
|
||||
| "evt_creator_withdraw_surplus_event"
|
||||
| "evt_curve_complete_event"
|
||||
| "evt_initialize_pool_event"
|
||||
| "evt_partner_claim_pool_creation_fee_event"
|
||||
| "evt_partner_metadata_event"
|
||||
| "evt_partner_withdraw_migration_fee_event"
|
||||
| "evt_partner_withdraw_surplus_event"
|
||||
| "evt_swap_event"
|
||||
| "evt_swap2_event"
|
||||
| "evt_update_pool_creator_event"
|
||||
| "evt_virtual_pool_metadata_event"
|
||||
| "evt_withdraw_leftover_event"
|
||||
| "evt_withdraw_migration_fee_event"
|
||||
);
|
||||
}
|
||||
|
||||
fn infer_raydium_amm_v4_event_family(
|
||||
entry_name: &str,
|
||||
entry_kind: &str,
|
||||
@@ -1479,6 +2129,9 @@ pub(crate) fn known_local_event_kind(
|
||||
if decoder_code == "pump_swap" {
|
||||
return pump_swap_local_event_kind(entry_name);
|
||||
}
|
||||
if decoder_code == "meteora_dbc" {
|
||||
return meteora_dbc_local_event_kind(entry_name);
|
||||
}
|
||||
if decoder_code == "raydium_amm_v4" {
|
||||
return raydium_amm_v4_local_event_kind(entry_name);
|
||||
}
|
||||
|
||||
@@ -200,6 +200,45 @@ fn resolve_instruction_name(
|
||||
Some(discriminator_hex) => discriminator_hex,
|
||||
None => return None,
|
||||
};
|
||||
if program_id == crate::METEORA_DBC_PROGRAM_ID || decoder_code == Some("meteora_dbc") {
|
||||
let name = match discriminator_hex {
|
||||
"e445a52e51cb9a1d" => "meteora_dbc.anchor_self_cpi_log",
|
||||
"e992d18ecf6840bc" => "meteora_dbc.create_pool_legacy",
|
||||
"5fb40aac54aee828" => "meteora_dbc.initialize_pool_legacy",
|
||||
"a677d1b6d66d3ab5" => "meteora_dbc.launch_pool_legacy",
|
||||
"52dcfabd03556b2d" => "meteora_dbc.claim_creator_trading_fee",
|
||||
"faee1a048b0a65f8" => "meteora_dbc.claim_partner_pool_creation_fee",
|
||||
"a5e4853063f9ff21" => "meteora_dbc.claim_protocol_fee",
|
||||
"72cd53bcf0991936" => "meteora_dbc.claim_protocol_pool_creation_fee",
|
||||
"08ec5931987db151" => "meteora_dbc.claim_trading_fee",
|
||||
"082957235030791a" => "meteora_dbc.close_claim_protocol_fee_operator",
|
||||
"ab09d54a7817031d" => "meteora_dbc.close_operator_account",
|
||||
"c9cff3724b6f2fbd" => "meteora_dbc.create_config",
|
||||
"a75a899a4b2f1154" => "meteora_dbc.create_locker",
|
||||
"dd40f695f099e5a3" => "meteora_dbc.create_operator_account",
|
||||
"c0a8eabfbce2e3ff" => "meteora_dbc.create_partner_metadata",
|
||||
"2d61bb67fe6d7c86" => "meteora_dbc.create_virtual_pool_metadata",
|
||||
"a50389071c864c50" => "meteora_dbc.creator_withdraw_surplus",
|
||||
"8c55d7b06636684f" => "meteora_dbc.initialize_virtual_pool_with_spl_token",
|
||||
"a976334e916edc9b" => "meteora_dbc.initialize_virtual_pool_with_token2022",
|
||||
"1b013016b43f76d9" => "meteora_dbc.migrate_meteora_damm",
|
||||
"8b85021e5b917f9a" => "meteora_dbc.migrate_meteora_damm_claim_lp_token",
|
||||
"b137ee9dfb58a52a" => "meteora_dbc.migrate_meteora_damm_lock_lp_token",
|
||||
"9ca9e66735e45040" => "meteora_dbc.migration_damm_v2",
|
||||
"6dbd1324c3b7de52" => "meteora_dbc.migration_damm_v2_create_metadata",
|
||||
"2f5e7e73dde2c285" => "meteora_dbc.migration_meteora_damm_create_metadata",
|
||||
"a8ad4864c962265c" => "meteora_dbc.partner_withdraw_surplus",
|
||||
"3688e18aacb6d6a7" => "meteora_dbc.protocol_withdraw_surplus",
|
||||
"f8c69e91e17587c8" => "meteora_dbc.swap",
|
||||
"414b3f4ceb5b5b88" => "meteora_dbc.swap2",
|
||||
"1407a9213a93a621" => "meteora_dbc.transfer_pool_creator",
|
||||
"14c6caedebf3b742" => "meteora_dbc.withdraw_leftover",
|
||||
"ed8e2d178106dea2" => "meteora_dbc.withdraw_migration_fee",
|
||||
"d59bbb2238b65bf0" => "meteora_dbc.zap_protocol_fee",
|
||||
_ => return None,
|
||||
};
|
||||
return Some(name.to_string());
|
||||
}
|
||||
if program_id == crate::RAYDIUM_AMM_V4_PROGRAM_ID || decoder_code == Some("raydium_amm_v4") {
|
||||
let name = match discriminator_hex {
|
||||
"00" => "raydium_amm_v4.initialize",
|
||||
@@ -536,3 +575,31 @@ fn option_i64_key(value: std::option::Option<i64>) -> std::string::String {
|
||||
None => return "-".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn resolves_meteora_dbc_instruction_observation_names() {
|
||||
let anchor_name = super::resolve_instruction_name(
|
||||
crate::METEORA_DBC_PROGRAM_ID,
|
||||
Some("meteora_dbc"),
|
||||
Some("e445a52e51cb9a1d"),
|
||||
);
|
||||
assert_eq!(anchor_name.as_deref(), Some("meteora_dbc.anchor_self_cpi_log"));
|
||||
let swap2_name = super::resolve_instruction_name(
|
||||
crate::METEORA_DBC_PROGRAM_ID,
|
||||
Some("meteora_dbc"),
|
||||
Some("414b3f4ceb5b5b88"),
|
||||
);
|
||||
assert_eq!(swap2_name.as_deref(), Some("meteora_dbc.swap2"));
|
||||
let create_name = super::resolve_instruction_name(
|
||||
crate::METEORA_DBC_PROGRAM_ID,
|
||||
Some("meteora_dbc"),
|
||||
Some("8c55d7b06636684f"),
|
||||
);
|
||||
assert_eq!(
|
||||
create_name.as_deref(),
|
||||
Some("meteora_dbc.initialize_virtual_pool_with_spl_token")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,6 +497,10 @@ pub use db::DexEventCoverageEntryEntity;
|
||||
pub use db::DexEventCoverageSummaryDto;
|
||||
/// Aggregated DEX event coverage summary row.
|
||||
pub use db::DexEventCoverageSummaryEntity;
|
||||
/// Application-facing normalized fee event amount leg DTO.
|
||||
pub use db::FeeEventAmountDto;
|
||||
/// Persisted normalized fee event amount leg row.
|
||||
pub use db::FeeEventAmountEntity;
|
||||
/// Normalized fee event persisted from useful non-trade DEX events.
|
||||
pub use db::FeeEventDto;
|
||||
/// Persisted fee event row.
|
||||
@@ -655,10 +659,6 @@ pub use db::ProgramInstructionDiscriminatorRowEntity;
|
||||
/// Aggregated instruction discriminator diagnostic row.
|
||||
pub use db::ProgramInstructionDiscriminatorSummaryDto;
|
||||
/// Application-facing protocol candidate DTO.
|
||||
///
|
||||
/// A protocol candidate records a program/instruction that should be inspected
|
||||
/// later because it may correspond to an unsupported DEX, launch surface,
|
||||
/// migration path or protocol-specific non-trade event.
|
||||
pub use db::ProtocolCandidateDto;
|
||||
/// Persisted protocol candidate row.
|
||||
pub use db::ProtocolCandidateEntity;
|
||||
@@ -804,13 +804,22 @@ pub use db::query_dexs_get_by_code;
|
||||
pub use db::query_dexs_list;
|
||||
/// Inserts or updates one normalized DEX row by code.
|
||||
pub use db::query_dexs_upsert;
|
||||
/// Deletes amount legs for one normalized fee event.
|
||||
pub use db::query_fee_event_amounts_backfill_single_leg_from_fee_events;
|
||||
/// Deletes fee amount legs for one parent fee event.
|
||||
pub use db::query_fee_event_amounts_delete_by_fee_event_id;
|
||||
/// Lists amount legs for one normalized fee event.
|
||||
pub use db::query_fee_event_amounts_list_by_fee_event_id;
|
||||
/// Replaces amount legs for one normalized fee event.
|
||||
pub use db::query_fee_event_amounts_replace_for_fee_event;
|
||||
/// Returns one fee event by decoded-event id.
|
||||
pub use db::query_fee_events_get_by_decoded_event_id;
|
||||
/// Lists recent fee events ordered from newest to oldest.
|
||||
pub use db::query_fee_events_list_recent;
|
||||
/// Inserts or updates one normalized fee event row.
|
||||
pub use db::query_fee_events_upsert;
|
||||
/// Inserts one on-chain observation row and returns its numeric id.
|
||||
/// Inserts or updates a fee parent and atomically replaces its amount legs.
|
||||
pub use db::query_fee_events_upsert_with_amount_legs;
|
||||
/// Lists instruction-observation source rows for one transaction signature.
|
||||
pub use db::query_instruction_observation_source_rows_list_by_signature;
|
||||
/// Lists recent instruction-observation source rows.
|
||||
@@ -984,8 +993,6 @@ pub use db::query_program_instruction_discriminator_summaries_list_by_program_id
|
||||
/// Lists protocol candidate summaries ordered by investigation priority.
|
||||
pub use db::query_protocol_candidate_summaries_list_by_priority;
|
||||
/// Deletes protocol candidates for one transaction.
|
||||
///
|
||||
/// This is useful before recomputing candidates for a replayed transaction.
|
||||
pub use db::query_protocol_candidates_delete_by_transaction_id;
|
||||
/// Inserts one protocol candidate row.
|
||||
pub use db::query_protocol_candidates_insert;
|
||||
@@ -1133,6 +1140,8 @@ pub use dex::MeteoraDbcCreatePoolDecoded;
|
||||
pub use dex::MeteoraDbcDecodedEvent;
|
||||
/// Meteora DBC decoder.
|
||||
pub use dex::MeteoraDbcDecoder;
|
||||
/// Decoded Meteora DBC instruction or Anchor event.
|
||||
pub use dex::MeteoraDbcInstructionDecoded;
|
||||
/// Decoded Meteora DBC swap event.
|
||||
pub use dex::MeteoraDbcSwapDecoded;
|
||||
/// Decoded Meteora DLMM create-pool event.
|
||||
@@ -1293,6 +1302,7 @@ pub use dex_event_classification::is_dex_admin_event_kind;
|
||||
pub use dex_event_classification::is_dex_candle_candidate_event_kind;
|
||||
/// Returns true for fee collection DEX events.
|
||||
pub use dex_event_classification::is_dex_fee_event_kind;
|
||||
/// Returns true for decoded audit-only events retained for corpus analysis.
|
||||
pub use dex_event_classification::is_dex_informational_event_kind;
|
||||
/// Returns true for launch or bonding-curve creation DEX events.
|
||||
pub use dex_event_classification::is_dex_launch_event_kind;
|
||||
@@ -1347,9 +1357,6 @@ pub use dex_support_matrix::dex_support_matrix_entry_by_program_id;
|
||||
/// Returns all DEX support matrix entries as owned DTOs.
|
||||
pub use dex_support_matrix::dex_support_matrix_entry_dtos;
|
||||
/// Global error type used by the `kb_lib` crate.
|
||||
///
|
||||
/// The project intentionally avoids `anyhow` and `thiserror`, so this
|
||||
/// enum centralizes the main error families with explicit textual messages.
|
||||
pub use error::Error;
|
||||
/// Generic asynchronous HTTP client.
|
||||
pub use http_client::HttpClient;
|
||||
@@ -1394,19 +1401,10 @@ pub use json_rpc_ws::JsonRpcWsRequest;
|
||||
/// JSON-RPC 2.0 success response.
|
||||
pub use json_rpc_ws::JsonRpcWsSuccessResponse;
|
||||
/// Parses a JSON value into a JSON-RPC incoming message.
|
||||
///
|
||||
/// This parser accepts only server-originating incoming message shapes:
|
||||
/// success responses, error responses, and notifications.
|
||||
pub use json_rpc_ws::is_probable_json_rpc_object_text;
|
||||
/// Parses a raw text message into a JSON-RPC incoming message.
|
||||
///
|
||||
/// This parser accepts only server-originating incoming message shapes:
|
||||
/// success responses, error responses, and notifications.
|
||||
pub use json_rpc_ws::parse_json_rpc_ws_incoming_text;
|
||||
/// Parses a JSON value into a JSON-RPC incoming message.
|
||||
///
|
||||
/// This parser accepts only server-originating incoming message shapes:
|
||||
/// success responses, error responses, and notifications.
|
||||
pub use json_rpc_ws::parse_json_rpc_ws_incoming_value;
|
||||
/// Result of one launch surface attribution.
|
||||
pub use launch_origin::LaunchAttributionResult;
|
||||
@@ -1467,14 +1465,8 @@ pub use pair_analytic_signal::PairAnalyticSignalService;
|
||||
/// One pair-candle aggregation result.
|
||||
pub use pair_candle_aggregation::PairCandleAggregationResult;
|
||||
/// Pair-candle aggregation service.
|
||||
///
|
||||
/// This service materializes a small set of standard timeframes in base storage.
|
||||
/// Arbitrary timeframes are rebuilt on demand through `PairCandleQueryService`.
|
||||
pub use pair_candle_aggregation::PairCandleAggregationService;
|
||||
/// Pair-candle query service.
|
||||
///
|
||||
/// Standard materialized timeframes are served from base storage.
|
||||
/// Arbitrary timeframes are rebuilt on demand from `trade_events`.
|
||||
pub use pair_candle_query::PairCandleQueryService;
|
||||
/// Summary produced by a pair-symbol refresh pass.
|
||||
pub use pair_symbol::PairSymbolRefreshResult;
|
||||
@@ -1493,11 +1485,6 @@ pub use solana_pubsub_ws::SolanaWsTypedNotification;
|
||||
/// Parses a Solana JSON-RPC notification into an official typed payload.
|
||||
pub use solana_pubsub_ws::parse_solana_ws_typed_notification;
|
||||
/// Parses a typed Solana PubSub notification from a generic websocket event.
|
||||
///
|
||||
/// This returns:
|
||||
/// - `Ok(Some(...))` for JSON-RPC notification-bearing events
|
||||
/// - `Ok(None)` for events that do not carry a notification
|
||||
/// - `Err(...)` when a notification is present but cannot be decoded
|
||||
pub use solana_pubsub_ws::parse_solana_ws_typed_notification_from_event;
|
||||
/// One pool-backfill result summary.
|
||||
pub use token_backfill::PoolBackfillResult;
|
||||
@@ -1510,22 +1497,14 @@ pub use token_backfill::SignatureBatchBackfillResult;
|
||||
/// One token-backfill result summary.
|
||||
pub use token_backfill::TokenBackfillResult;
|
||||
/// Historical token backfill service.
|
||||
///
|
||||
/// This service reuses the existing transaction projection and downstream
|
||||
/// DEX pipeline instead of introducing a separate historical code path.
|
||||
pub use token_backfill::TokenBackfillService;
|
||||
/// Summary produced by a token metadata backfill pass.
|
||||
pub use token_metadata::TokenMetadataBackfillResult;
|
||||
/// Service that enriches persisted token rows with mint and display metadata.
|
||||
pub use token_metadata::TokenMetadataBackfillService;
|
||||
/// Guard keeping non-blocking tracing workers alive.
|
||||
///
|
||||
/// The guard must remain alive during the whole application lifetime so that
|
||||
/// buffered log records are flushed correctly.
|
||||
pub use tracing::TracingGuard;
|
||||
/// Initializes the global tracing subscriber.
|
||||
///
|
||||
/// This function is expected to be called once at application startup.
|
||||
pub use tracing::init_tracing;
|
||||
/// One trade-aggregation result.
|
||||
pub use trade_aggregation::TradeAggregationResult;
|
||||
@@ -1543,8 +1522,7 @@ pub use tx_resolution::TransactionResolutionRequest;
|
||||
pub use tx_resolution::TransactionResolutionService;
|
||||
/// One forwarded WebSocket notification envelope for transaction resolution.
|
||||
pub use tx_resolution::WsTransactionResolutionEnvelope;
|
||||
/// Relay that consumes forwarded WS notifications and resolves matching
|
||||
/// signatures through HTTP `getTransaction`.
|
||||
/// Relay that consumes forwarded WS notifications and resolves matching signatures through HTTP `getTransaction`.
|
||||
pub use tx_resolution::WsTransactionResolutionRelay;
|
||||
/// Runtime statistics for one transaction resolution relay worker.
|
||||
pub use tx_resolution::WsTransactionResolutionRelayStats;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -114,6 +114,24 @@ impl TradeAggregationService {
|
||||
continue;
|
||||
},
|
||||
};
|
||||
let payload_base_vault_address =
|
||||
crate::trade_aggregation::extract_payload_string_by_candidate_keys(
|
||||
&payload,
|
||||
&["baseVault", "base_vault", "baseVaultAddress", "base_vault_address"],
|
||||
);
|
||||
let payload_quote_vault_address =
|
||||
crate::trade_aggregation::extract_payload_string_by_candidate_keys(
|
||||
&payload,
|
||||
&["quoteVault", "quote_vault", "quoteVaultAddress", "quote_vault_address"],
|
||||
);
|
||||
let effective_base_vault_address = match base_vault_address.as_deref() {
|
||||
Some(base_vault_address) => Some(base_vault_address),
|
||||
None => payload_base_vault_address.as_deref(),
|
||||
};
|
||||
let effective_quote_vault_address = match quote_vault_address.as_deref() {
|
||||
Some(quote_vault_address) => Some(quote_vault_address),
|
||||
None => payload_quote_vault_address.as_deref(),
|
||||
};
|
||||
if !crate::is_decoded_event_trade_candidate(decoded_event.event_kind.as_str(), &payload)
|
||||
{
|
||||
tracing::debug!(
|
||||
@@ -150,8 +168,8 @@ impl TradeAggregationService {
|
||||
quote_token_mint: quote_token_mint.as_deref(),
|
||||
base_token_decimals,
|
||||
quote_token_decimals,
|
||||
base_vault_address: base_vault_address.as_deref(),
|
||||
quote_vault_address: quote_vault_address.as_deref(),
|
||||
base_vault_address: effective_base_vault_address,
|
||||
quote_vault_address: effective_quote_vault_address,
|
||||
};
|
||||
let amount_resolution =
|
||||
crate::trade_amount_resolution::resolve_trade_amounts(&amount_input).await;
|
||||
@@ -212,6 +230,29 @@ impl TradeAggregationService {
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_payload_string_by_candidate_keys(
|
||||
payload: &serde_json::Value,
|
||||
keys: &[&str],
|
||||
) -> std::option::Option<std::string::String> {
|
||||
let object = match payload.as_object() {
|
||||
Some(object) => object,
|
||||
None => return None,
|
||||
};
|
||||
for key in keys {
|
||||
let value = object.get(*key);
|
||||
let value = match value {
|
||||
Some(value) => value,
|
||||
None => continue,
|
||||
};
|
||||
if let Some(text) = value.as_str() {
|
||||
if !text.trim().is_empty() {
|
||||
return Some(text.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn should_skip_pump_fun_duplicate_trade_event(
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
decoded_events: &[crate::DexDecodedEventDto],
|
||||
|
||||
@@ -253,6 +253,36 @@ pub(crate) async fn resolve_trade_amounts(
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if input.decoded_event.event_kind.starts_with("meteora_dbc.")
|
||||
&& (base_amount_raw.is_none() || quote_amount_raw.is_none())
|
||||
{
|
||||
let resolution_result =
|
||||
crate::trade_amount_resolution::apply_meteora_dbc_payload_transfer_amount_fallback(
|
||||
input,
|
||||
&mut base_amount_raw,
|
||||
&mut quote_amount_raw,
|
||||
&mut resolved_trade_side,
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = resolution_result {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if input.decoded_event.event_kind.starts_with("meteora_dbc.")
|
||||
&& (base_amount_raw.is_none() || quote_amount_raw.is_none())
|
||||
{
|
||||
let resolution_result =
|
||||
crate::trade_amount_resolution::apply_meteora_dbc_flattened_cpi_amount_fallback(
|
||||
input,
|
||||
&mut base_amount_raw,
|
||||
&mut quote_amount_raw,
|
||||
&mut resolved_trade_side,
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = resolution_result {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if input.decoded_event.event_kind.starts_with("meteora_dlmm.")
|
||||
&& (base_amount_raw.is_none() || quote_amount_raw.is_none())
|
||||
{
|
||||
@@ -283,6 +313,21 @@ pub(crate) async fn resolve_trade_amounts(
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if input.decoded_event.event_kind.starts_with("meteora_dbc.")
|
||||
&& (base_amount_raw.is_none() || quote_amount_raw.is_none())
|
||||
{
|
||||
let resolution_result = crate::trade_amount_resolution::apply_vault_balance_delta_fallback(
|
||||
input,
|
||||
input.base_vault_address,
|
||||
input.quote_vault_address,
|
||||
&mut base_amount_raw,
|
||||
&mut quote_amount_raw,
|
||||
&mut price_quote_per_base,
|
||||
);
|
||||
if let Err(error) = resolution_result {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if input.decoded_event.event_kind.starts_with("raydium_amm_v4.") {
|
||||
let vault_side = crate::trade_amount_resolution::infer_trade_side_from_vault_balance_deltas(
|
||||
input.transaction.meta_json.as_deref(),
|
||||
@@ -316,6 +361,17 @@ pub(crate) async fn resolve_trade_amounts(
|
||||
resolved_trade_side = vault_side;
|
||||
}
|
||||
}
|
||||
if input.decoded_event.event_kind.starts_with("meteora_dbc.") {
|
||||
let vault_side = crate::trade_amount_resolution::infer_trade_side_from_vault_balance_deltas(
|
||||
input.transaction.meta_json.as_deref(),
|
||||
input.transaction.transaction_json.as_str(),
|
||||
input.base_vault_address,
|
||||
input.quote_vault_address,
|
||||
);
|
||||
if vault_side.is_some() {
|
||||
resolved_trade_side = vault_side;
|
||||
}
|
||||
}
|
||||
if price_quote_per_base.is_none() {
|
||||
price_quote_per_base =
|
||||
crate::trade_metric_update::compute_price_quote_per_base_from_raw_amounts_with_decimals(
|
||||
@@ -452,10 +508,7 @@ async fn apply_pump_swap_amount_fallbacks(
|
||||
};
|
||||
let (input_vault_address, output_vault_address, input_token_account, output_token_account) =
|
||||
if input.decoded_event.event_kind.ends_with(".buy")
|
||||
|| input
|
||||
.decoded_event
|
||||
.event_kind
|
||||
.ends_with(".buy_exact_quote_in")
|
||||
|| input.decoded_event.event_kind.ends_with(".buy_exact_quote_in")
|
||||
{
|
||||
(
|
||||
effective_quote_vault_address,
|
||||
@@ -815,13 +868,14 @@ async fn apply_pump_fun_amount_fallback(
|
||||
*price_quote_per_base = inferred.2;
|
||||
}
|
||||
if base_amount_raw.is_none() || quote_amount_raw.is_none() || price_quote_per_base.is_none() {
|
||||
let sibling_result = crate::trade_amount_resolution::apply_pump_fun_trade_event_sibling_amount_fallback(
|
||||
input,
|
||||
base_amount_raw,
|
||||
quote_amount_raw,
|
||||
price_quote_per_base,
|
||||
)
|
||||
.await;
|
||||
let sibling_result =
|
||||
crate::trade_amount_resolution::apply_pump_fun_trade_event_sibling_amount_fallback(
|
||||
input,
|
||||
base_amount_raw,
|
||||
quote_amount_raw,
|
||||
price_quote_per_base,
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = sibling_result {
|
||||
return Err(error);
|
||||
}
|
||||
@@ -1111,6 +1165,160 @@ async fn apply_meteora_damm_v1_flattened_cpi_amount_fallback(
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn apply_meteora_dbc_flattened_cpi_amount_fallback(
|
||||
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
||||
base_amount_raw: &mut std::option::Option<std::string::String>,
|
||||
quote_amount_raw: &mut std::option::Option<std::string::String>,
|
||||
resolved_trade_side: &mut std::option::Option<crate::SwapTradeSide>,
|
||||
) -> Result<(), crate::Error> {
|
||||
return crate::trade_amount_resolution::apply_flattened_cpi_amount_fallback(
|
||||
input,
|
||||
"meteora_dbc",
|
||||
base_amount_raw,
|
||||
quote_amount_raw,
|
||||
resolved_trade_side,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn apply_meteora_dbc_payload_transfer_amount_fallback(
|
||||
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
||||
base_amount_raw: &mut std::option::Option<std::string::String>,
|
||||
quote_amount_raw: &mut std::option::Option<std::string::String>,
|
||||
resolved_trade_side: &mut std::option::Option<crate::SwapTradeSide>,
|
||||
) -> Result<(), crate::Error> {
|
||||
let payload_token_a_mint = crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["tokenAMint", "token_a_mint", "baseMint", "base_mint"],
|
||||
);
|
||||
let payload_token_b_mint = crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["tokenBMint", "token_b_mint", "quoteMint", "quote_mint"],
|
||||
);
|
||||
let payload_base_vault = crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["baseVault", "base_vault", "baseVaultAddress", "base_vault_address"],
|
||||
);
|
||||
let payload_quote_vault = crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["quoteVault", "quote_vault", "quoteVaultAddress", "quote_vault_address"],
|
||||
);
|
||||
let payload_token_a_mint = match payload_token_a_mint {
|
||||
Some(payload_token_a_mint) => payload_token_a_mint,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let payload_token_b_mint = match payload_token_b_mint {
|
||||
Some(payload_token_b_mint) => payload_token_b_mint,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let payload_base_vault = match payload_base_vault {
|
||||
Some(payload_base_vault) => payload_base_vault,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let payload_quote_vault = match payload_quote_vault {
|
||||
Some(payload_quote_vault) => payload_quote_vault,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let decoded_instruction_result = crate::trade_amount_resolution::load_decoded_instruction(
|
||||
input.database,
|
||||
input.decoded_event,
|
||||
)
|
||||
.await;
|
||||
let decoded_instruction = match decoded_instruction_result {
|
||||
Ok(Some(decoded_instruction)) => decoded_instruction,
|
||||
Ok(None) => return Ok(()),
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
|
||||
input.database,
|
||||
input.decoded_event.transaction_id,
|
||||
)
|
||||
.await;
|
||||
let instructions = match instructions_result {
|
||||
Ok(instructions) => instructions,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let payload_order_result = resolve_amounts_from_flattened_cpi_transfer_window(
|
||||
&decoded_instruction,
|
||||
&instructions,
|
||||
Some(payload_token_a_mint.as_str()),
|
||||
Some(payload_token_b_mint.as_str()),
|
||||
Some(payload_base_vault.as_str()),
|
||||
Some(payload_quote_vault.as_str()),
|
||||
);
|
||||
let payload_order = match payload_order_result {
|
||||
Ok(payload_order) => payload_order,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
if payload_order.base_amount_raw.is_none() || payload_order.quote_amount_raw.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
if crate::trade_amount_resolution::optional_mint_pair_matches_payload_order(
|
||||
input.base_token_mint,
|
||||
input.quote_token_mint,
|
||||
payload_token_a_mint.as_str(),
|
||||
payload_token_b_mint.as_str(),
|
||||
) {
|
||||
if base_amount_raw.is_none() {
|
||||
*base_amount_raw = payload_order.base_amount_raw;
|
||||
}
|
||||
if quote_amount_raw.is_none() {
|
||||
*quote_amount_raw = payload_order.quote_amount_raw;
|
||||
}
|
||||
if resolved_trade_side.is_none() {
|
||||
*resolved_trade_side = payload_order.resolved_trade_side;
|
||||
}
|
||||
tracing::debug!(
|
||||
event_kind = %input.decoded_event.event_kind,
|
||||
decoded_event_id = ?input.decoded_event.id,
|
||||
transaction_signature = %input.transaction.signature,
|
||||
pool_account = ?input.decoded_event.pool_account,
|
||||
payload_token_a_mint = %payload_token_a_mint,
|
||||
payload_token_b_mint = %payload_token_b_mint,
|
||||
payload_base_vault = %payload_base_vault,
|
||||
payload_quote_vault = %payload_quote_vault,
|
||||
base_amount_raw = ?base_amount_raw,
|
||||
quote_amount_raw = ?quote_amount_raw,
|
||||
resolved_trade_side = ?resolved_trade_side,
|
||||
"meteora_dbc trade amounts recovered from payload-token CPI transfer window"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
if crate::trade_amount_resolution::optional_mint_pair_matches_payload_reverse_order(
|
||||
input.base_token_mint,
|
||||
input.quote_token_mint,
|
||||
payload_token_a_mint.as_str(),
|
||||
payload_token_b_mint.as_str(),
|
||||
) {
|
||||
if base_amount_raw.is_none() {
|
||||
*base_amount_raw = payload_order.quote_amount_raw;
|
||||
}
|
||||
if quote_amount_raw.is_none() {
|
||||
*quote_amount_raw = payload_order.base_amount_raw;
|
||||
}
|
||||
if resolved_trade_side.is_none() {
|
||||
*resolved_trade_side = crate::trade_amount_resolution::reverse_swap_trade_side(
|
||||
payload_order.resolved_trade_side,
|
||||
);
|
||||
}
|
||||
tracing::debug!(
|
||||
event_kind = %input.decoded_event.event_kind,
|
||||
decoded_event_id = ?input.decoded_event.id,
|
||||
transaction_signature = %input.transaction.signature,
|
||||
pool_account = ?input.decoded_event.pool_account,
|
||||
payload_token_a_mint = %payload_token_a_mint,
|
||||
payload_token_b_mint = %payload_token_b_mint,
|
||||
payload_base_vault = %payload_base_vault,
|
||||
payload_quote_vault = %payload_quote_vault,
|
||||
base_amount_raw = ?base_amount_raw,
|
||||
quote_amount_raw = ?quote_amount_raw,
|
||||
resolved_trade_side = ?resolved_trade_side,
|
||||
"meteora_dbc trade amounts recovered from reversed payload-token CPI transfer window"
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
async fn apply_flattened_cpi_amount_fallback(
|
||||
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
||||
protocol_label: &str,
|
||||
@@ -1488,6 +1696,41 @@ fn infer_trade_side_from_transfer_directions(
|
||||
}
|
||||
}
|
||||
|
||||
fn optional_mint_pair_matches_payload_order(
|
||||
base_token_mint: std::option::Option<&str>,
|
||||
quote_token_mint: std::option::Option<&str>,
|
||||
payload_token_a_mint: &str,
|
||||
payload_token_b_mint: &str,
|
||||
) -> bool {
|
||||
return crate::trade_amount_resolution::string_option_equals(
|
||||
base_token_mint,
|
||||
payload_token_a_mint,
|
||||
) && crate::trade_amount_resolution::string_option_equals(quote_token_mint, payload_token_b_mint);
|
||||
}
|
||||
|
||||
fn optional_mint_pair_matches_payload_reverse_order(
|
||||
base_token_mint: std::option::Option<&str>,
|
||||
quote_token_mint: std::option::Option<&str>,
|
||||
payload_token_a_mint: &str,
|
||||
payload_token_b_mint: &str,
|
||||
) -> bool {
|
||||
return crate::trade_amount_resolution::string_option_equals(
|
||||
base_token_mint,
|
||||
payload_token_b_mint,
|
||||
) && crate::trade_amount_resolution::string_option_equals(quote_token_mint, payload_token_a_mint);
|
||||
}
|
||||
|
||||
fn reverse_swap_trade_side(
|
||||
trade_side: std::option::Option<crate::SwapTradeSide>,
|
||||
) -> std::option::Option<crate::SwapTradeSide> {
|
||||
match trade_side {
|
||||
Some(crate::SwapTradeSide::BuyBase) => return Some(crate::SwapTradeSide::SellBase),
|
||||
Some(crate::SwapTradeSide::SellBase) => return Some(crate::SwapTradeSide::BuyBase),
|
||||
Some(crate::SwapTradeSide::Unknown) => return Some(crate::SwapTradeSide::Unknown),
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
|
||||
fn string_option_equals(left: std::option::Option<&str>, right: &str) -> bool {
|
||||
let left = match left {
|
||||
Some(left) => left.trim(),
|
||||
@@ -1589,7 +1832,6 @@ async fn load_decoded_instruction(
|
||||
return Ok(instruction_option);
|
||||
}
|
||||
|
||||
|
||||
fn normalize_pump_swap_anchor_buy_exact_quote_in_amounts(
|
||||
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
||||
base_amount_raw: &mut std::option::Option<std::string::String>,
|
||||
|
||||
Reference in New Issue
Block a user