diff --git a/CHANGELOG.md b/CHANGELOG.md index 6da7cce..4217bd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,3 +44,4 @@ 0.7.11 - Ajout du premier support FluxBeam avec décodage create_pool/swap, persistance des événements décodés et détection métier automatique pool/pair/listing 0.7.12 - Ajout du premier support DexLab Swap/Pool avec décodage create_pool/swap, persistance des événements décodés et détection métier automatique pool/pair/listing 0.7.13 - Extension de la couche launch origins avec Bags et Moonit, ajout d’un enregistrement programmatique des mappings Bags, et détection automatique Moonit via suffixe de token mint +0.7.14 - Ajout d’une couche consolidée de traçabilité fondatrice multi-DEX avec enregistrement des pool origins, rattachement au decoded event, au pool/pair/listing et à l’éventuelle launch attribution diff --git a/Cargo.toml b/Cargo.toml index 1e773f2..581d4dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ ] [workspace.package] -version = "0.7.13" +version = "0.7.14" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot" diff --git a/ROADMAP.md b/ROADMAP.md index 99959af..12a7033 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -587,15 +587,13 @@ Réalisé : - conservation d’une séparation stricte entre origine de lancement et protocole on-chain. ### 6.046. Version `0.7.14` — Consolidation multi-DEX -Objectif : unifier le comportement des connecteurs DEX v1 avant l’ouverture des couches analytiques plus riches. +Réalisé : -À faire : - -- homogénéiser les événements métier produits par tous les connecteurs, -- consolider la vision `token <-> pool <-> pair <-> protocole`, -- rattacher plus finement une paire à son pool de création et à sa signature fondatrice, -- améliorer l’idempotence et la traçabilité inter-protocoles, -- préparer la base des futurs événements enrichis de liquidité, swaps et activité. +- ajout d’une couche commune `pool origins` pour enregistrer la première signature vue par le modèle pour chaque pool détecté, +- rattachement d’un pool à son `decoded_event`, à son `pair`, à son `pool_listing` et à son éventuelle `launch_attribution`, +- amélioration de la traçabilité inter-protocoles sans modifier les connecteurs DEX déjà validés, +- conservation d’une logique idempotente avec mise à jour douce des liens `pair / listing / launch attribution`, +- préparation de la future couche analytique sur une base multi-DEX plus cohérente. ### 6.047. Version `0.7.15` — Wallets, holdings et participants observés Objectif : préparer le suivi des acteurs on-chain autour des pools et tokens détectés. diff --git a/kb_lib/src/db.rs b/kb_lib/src/db.rs index 5a541d6..bad8f32 100644 --- a/kb_lib/src/db.rs +++ b/kb_lib/src/db.rs @@ -16,101 +16,121 @@ mod types; pub use connection::KbDatabase; pub use connection::KbDatabaseConnection; pub use dtos::KbAnalysisSignalDto; +pub use dtos::KbChainInstructionDto; +pub use dtos::KbChainSlotDto; +pub use dtos::KbChainTransactionDto; pub use dtos::KbDbMetadataDto; pub use dtos::KbDbRuntimeEventDto; +pub use dtos::KbDexDecodedEventDto; pub use dtos::KbDexDto; pub use dtos::KbKnownHttpEndpointDto; pub use dtos::KbKnownWsEndpointDto; +pub use dtos::KbLaunchAttributionDto; +pub use dtos::KbLaunchSurfaceDto; +pub use dtos::KbLaunchSurfaceKeyDto; pub use dtos::KbLiquidityEventDto; pub use dtos::KbObservedTokenDto; pub use dtos::KbOnchainObservationDto; pub use dtos::KbPairDto; pub use dtos::KbPoolDto; pub use dtos::KbPoolListingDto; +pub use dtos::KbPoolOriginDto; pub use dtos::KbPoolTokenDto; pub use dtos::KbSwapDto; pub use dtos::KbTokenBurnEventDto; pub use dtos::KbTokenDto; pub use dtos::KbTokenMintEventDto; -pub use dtos::KbChainInstructionDto; -pub use dtos::KbChainSlotDto; -pub use dtos::KbChainTransactionDto; -pub use dtos::KbDexDecodedEventDto; pub use entities::KbAnalysisSignalEntity; +pub use entities::KbChainInstructionEntity; +pub use entities::KbChainSlotEntity; +pub use entities::KbChainTransactionEntity; pub use entities::KbDbMetadataEntity; pub use entities::KbDbRuntimeEventEntity; +pub use entities::KbDexDecodedEventEntity; pub use entities::KbDexEntity; pub use entities::KbKnownHttpEndpointEntity; pub use entities::KbKnownWsEndpointEntity; +pub use entities::KbLaunchAttributionEntity; +pub use entities::KbLaunchSurfaceEntity; +pub use entities::KbLaunchSurfaceKeyEntity; pub use entities::KbLiquidityEventEntity; pub use entities::KbObservedTokenEntity; pub use entities::KbOnchainObservationEntity; pub use entities::KbPairEntity; pub use entities::KbPoolEntity; pub use entities::KbPoolListingEntity; +pub use entities::KbPoolOriginEntity; pub use entities::KbPoolTokenEntity; pub use entities::KbSwapEntity; pub use entities::KbTokenBurnEventEntity; pub use entities::KbTokenEntity; pub use entities::KbTokenMintEventEntity; -pub use entities::KbChainInstructionEntity; -pub use entities::KbChainSlotEntity; -pub use entities::KbChainTransactionEntity; -pub use entities::KbDexDecodedEventEntity; +pub use queries::delete_chain_instructions_by_transaction_id; +pub use queries::get_chain_slot; +pub use queries::get_chain_transaction_by_signature; pub use queries::get_db_metadata; +pub use queries::get_dex_by_code; +pub use queries::get_dex_decoded_event_by_key; pub use queries::get_known_http_endpoint; pub use queries::get_known_ws_endpoint; +pub use queries::get_launch_attribution_by_decoded_event_id; +pub use queries::get_launch_surface_by_code; +pub use queries::get_launch_surface_key_by_match; pub use queries::get_observed_token_by_mint; +pub use queries::get_pair_by_pool_id; +pub use queries::get_pool_by_address; +pub use queries::get_pool_listing_by_pool_id; +pub use queries::get_pool_origin_by_pool_id; pub use queries::get_token_by_mint; pub use queries::insert_analysis_signal; +pub use queries::insert_chain_instruction; pub use queries::insert_db_runtime_event; pub use queries::insert_onchain_observation; +pub use queries::list_chain_instructions_by_transaction_id; pub use queries::list_db_metadata; +pub use queries::list_dex_decoded_events_by_transaction_id; pub use queries::list_dexes; pub use queries::list_known_http_endpoints; pub use queries::list_known_ws_endpoints; +pub use queries::list_launch_attributions_by_pool_id; +pub use queries::list_launch_surface_keys_by_surface_id; +pub use queries::list_launch_surfaces; pub use queries::list_observed_tokens; +pub use queries::list_pairs; +pub use queries::list_pool_listings; +pub use queries::list_pool_origins; +pub use queries::list_pool_tokens_by_pool_id; +pub use queries::list_pools; pub use queries::list_recent_analysis_signals; +pub use queries::list_recent_chain_slots; +pub use queries::list_recent_chain_transactions; pub use queries::list_recent_db_runtime_events; pub use queries::list_recent_liquidity_events; pub use queries::list_recent_onchain_observations; pub use queries::list_recent_swaps; pub use queries::list_recent_token_burn_events; pub use queries::list_recent_token_mint_events; +pub use queries::upsert_chain_slot; +pub use queries::upsert_chain_transaction; pub use queries::upsert_db_metadata; pub use queries::upsert_dex; +pub use queries::upsert_dex_decoded_event; pub use queries::upsert_known_http_endpoint; pub use queries::upsert_known_ws_endpoint; +pub use queries::upsert_launch_attribution; +pub use queries::upsert_launch_surface; +pub use queries::upsert_launch_surface_key; pub use queries::upsert_liquidity_event; pub use queries::upsert_observed_token; pub use queries::upsert_pair; pub use queries::upsert_pool; pub use queries::upsert_pool_listing; +pub use queries::upsert_pool_origin; pub use queries::upsert_pool_token; pub use queries::upsert_swap; pub use queries::upsert_token; pub use queries::upsert_token_burn_event; pub use queries::upsert_token_mint_event; -pub use queries::get_dex_by_code; -pub use queries::get_pair_by_pool_id; -pub use queries::get_pool_by_address; -pub use queries::get_pool_listing_by_pool_id; -pub use queries::list_pairs; -pub use queries::list_pool_listings; -pub use queries::list_pool_tokens_by_pool_id; -pub use queries::list_pools; -pub use queries::delete_chain_instructions_by_transaction_id; -pub use queries::get_chain_slot; -pub use queries::get_chain_transaction_by_signature; -pub use queries::insert_chain_instruction; -pub use queries::list_chain_instructions_by_transaction_id; -pub use queries::list_recent_chain_slots; -pub use queries::list_recent_chain_transactions; -pub use queries::upsert_chain_slot; -pub use queries::upsert_chain_transaction; -pub use queries::get_dex_decoded_event_by_key; -pub use queries::list_dex_decoded_events_by_transaction_id; -pub use queries::upsert_dex_decoded_event; pub use types::KbAnalysisSignalSeverity; pub use types::KbDatabaseBackend; pub use types::KbDbRuntimeEventLevel; @@ -121,21 +141,3 @@ pub use types::KbPoolKind; pub use types::KbPoolStatus; pub use types::KbPoolTokenRole; pub use types::KbSwapTradeSide; - -pub use dtos::KbLaunchSurfaceDto; -pub use dtos::KbLaunchSurfaceKeyDto; -pub use dtos::KbLaunchAttributionDto; - -pub use entities::KbLaunchSurfaceEntity; -pub use entities::KbLaunchSurfaceKeyEntity; -pub use entities::KbLaunchAttributionEntity; - -pub use queries::upsert_launch_surface; -pub use queries::get_launch_surface_by_code; -pub use queries::list_launch_surfaces; -pub use queries::upsert_launch_surface_key; -pub use queries::get_launch_surface_key_by_match; -pub use queries::list_launch_surface_keys_by_surface_id; -pub use queries::upsert_launch_attribution; -pub use queries::get_launch_attribution_by_decoded_event_id; -pub use queries::list_launch_attributions_by_pool_id; diff --git a/kb_lib/src/db/dtos.rs b/kb_lib/src/db/dtos.rs index 89e2bbe..dde9c58 100644 --- a/kb_lib/src/db/dtos.rs +++ b/kb_lib/src/db/dtos.rs @@ -3,51 +3,53 @@ //! Database data transfer objects. mod analysis_signal; +mod chain_instruction; +mod chain_slot; +mod chain_transaction; mod db_metadata; mod db_runtime_event; mod dex; +mod dex_decoded_event; mod known_http_endpoint; mod known_ws_endpoint; +mod launch_attribution; +mod launch_surface; +mod launch_surface_key; mod liquidity_event; mod observed_token; mod onchain_observation; mod pair; mod pool; mod pool_listing; +mod pool_origin; mod pool_token; mod swap; mod token; mod token_burn_event; mod token_mint_event; -mod chain_instruction; -mod chain_slot; -mod chain_transaction; -mod dex_decoded_event; -mod launch_surface; -mod launch_surface_key; -mod launch_attribution; pub use analysis_signal::KbAnalysisSignalDto; +pub use chain_instruction::KbChainInstructionDto; +pub use chain_slot::KbChainSlotDto; +pub use chain_transaction::KbChainTransactionDto; pub use db_metadata::KbDbMetadataDto; pub use db_runtime_event::KbDbRuntimeEventDto; pub use dex::KbDexDto; +pub use dex_decoded_event::KbDexDecodedEventDto; pub use known_http_endpoint::KbKnownHttpEndpointDto; pub use known_ws_endpoint::KbKnownWsEndpointDto; +pub use launch_attribution::KbLaunchAttributionDto; +pub use launch_surface::KbLaunchSurfaceDto; +pub use launch_surface_key::KbLaunchSurfaceKeyDto; pub use liquidity_event::KbLiquidityEventDto; pub use observed_token::KbObservedTokenDto; pub use onchain_observation::KbOnchainObservationDto; pub use pair::KbPairDto; pub use pool::KbPoolDto; pub use pool_listing::KbPoolListingDto; +pub use pool_origin::KbPoolOriginDto; pub use pool_token::KbPoolTokenDto; pub use swap::KbSwapDto; pub use token::KbTokenDto; pub use token_burn_event::KbTokenBurnEventDto; pub use token_mint_event::KbTokenMintEventDto; -pub use chain_instruction::KbChainInstructionDto; -pub use chain_slot::KbChainSlotDto; -pub use chain_transaction::KbChainTransactionDto; -pub use dex_decoded_event::KbDexDecodedEventDto; -pub use launch_surface::KbLaunchSurfaceDto; -pub use launch_surface_key::KbLaunchSurfaceKeyDto; -pub use launch_attribution::KbLaunchAttributionDto; diff --git a/kb_lib/src/db/dtos/pool_origin.rs b/kb_lib/src/db/dtos/pool_origin.rs new file mode 100644 index 0000000..871a5d1 --- /dev/null +++ b/kb_lib/src/db/dtos/pool_origin.rs @@ -0,0 +1,124 @@ +// file: kb_lib/src/db/dtos/pool_origin.rs + +//! Pool origin DTO. + +/// Application-facing pool-origin DTO. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct KbPoolOriginDto { + /// Optional numeric primary key. + pub id: std::option::Option, + /// Related DEX id. + pub dex_id: i64, + /// Related pool id. + pub pool_id: i64, + /// Optional related pair id. + pub pair_id: std::option::Option, + /// Optional related pool listing id. + pub pool_listing_id: std::option::Option, + /// Founding transaction id in the local model. + pub founding_transaction_id: i64, + /// Founding decoded-event id in the local model. + pub founding_decoded_event_id: i64, + /// Founding signature in the local model. + pub founding_signature: std::string::String, + /// Founding protocol name. + pub founding_protocol_name: std::string::String, + /// Founding event kind. + pub founding_event_kind: std::string::String, + /// Discovery source kind. + pub source_kind: crate::KbObservationSourceKind, + /// Optional source endpoint logical name. + pub source_endpoint_name: std::option::Option, + /// Optional related launch attribution id. + pub launch_attribution_id: std::option::Option, + /// Creation timestamp. + pub created_at: chrono::DateTime, + /// Update timestamp. + pub updated_at: chrono::DateTime, +} + +impl KbPoolOriginDto { + /// Creates a new pool-origin DTO. + pub fn new( + dex_id: i64, + pool_id: i64, + pair_id: std::option::Option, + pool_listing_id: std::option::Option, + founding_transaction_id: i64, + founding_decoded_event_id: i64, + founding_signature: std::string::String, + founding_protocol_name: std::string::String, + founding_event_kind: std::string::String, + source_kind: crate::KbObservationSourceKind, + source_endpoint_name: std::option::Option, + launch_attribution_id: std::option::Option, + ) -> Self { + let now = chrono::Utc::now(); + Self { + id: None, + dex_id, + pool_id, + pair_id, + pool_listing_id, + founding_transaction_id, + founding_decoded_event_id, + founding_signature, + founding_protocol_name, + founding_event_kind, + source_kind, + source_endpoint_name, + launch_attribution_id, + created_at: now, + updated_at: now, + } + } +} + +impl TryFrom for KbPoolOriginDto { + type Error = crate::KbError; + + fn try_from(entity: crate::KbPoolOriginEntity) -> Result { + let source_kind_result = crate::KbObservationSourceKind::from_i16(entity.source_kind); + let source_kind = match source_kind_result { + Ok(source_kind) => source_kind, + Err(error) => return Err(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::KbError::Db(format!( + "cannot parse pool_origin created_at '{}': {}", + entity.created_at, error + ))); + } + }; + let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at); + let updated_at = match updated_at_result { + Ok(updated_at) => updated_at.with_timezone(&chrono::Utc), + Err(error) => { + return Err(crate::KbError::Db(format!( + "cannot parse pool_origin updated_at '{}': {}", + entity.updated_at, error + ))); + } + }; + Ok(Self { + id: Some(entity.id), + dex_id: entity.dex_id, + pool_id: entity.pool_id, + pair_id: entity.pair_id, + pool_listing_id: entity.pool_listing_id, + founding_transaction_id: entity.founding_transaction_id, + founding_decoded_event_id: entity.founding_decoded_event_id, + founding_signature: entity.founding_signature, + founding_protocol_name: entity.founding_protocol_name, + founding_event_kind: entity.founding_event_kind, + source_kind, + source_endpoint_name: entity.source_endpoint_name, + launch_attribution_id: entity.launch_attribution_id, + created_at, + updated_at, + }) + } +} diff --git a/kb_lib/src/db/entities.rs b/kb_lib/src/db/entities.rs index 7c8dde8..14ddc53 100644 --- a/kb_lib/src/db/entities.rs +++ b/kb_lib/src/db/entities.rs @@ -5,51 +5,53 @@ //! These types are close to persisted rows and SQL query results. mod analysis_signal; +mod chain_instruction; +mod chain_slot; +mod chain_transaction; mod db_metadata; mod db_runtime_event; mod dex; +mod dex_decoded_event; mod known_http_endpoint; mod known_ws_endpoint; +mod launch_attribution; +mod launch_surface; +mod launch_surface_key; mod liquidity_event; mod observed_token; mod onchain_observation; mod pair; mod pool; mod pool_listing; +mod pool_origin; mod pool_token; mod swap; mod token; mod token_burn_event; mod token_mint_event; -mod chain_instruction; -mod chain_slot; -mod chain_transaction; -mod dex_decoded_event; -mod launch_surface; -mod launch_surface_key; -mod launch_attribution; pub use analysis_signal::KbAnalysisSignalEntity; +pub use chain_instruction::KbChainInstructionEntity; +pub use chain_slot::KbChainSlotEntity; +pub use chain_transaction::KbChainTransactionEntity; pub use db_metadata::KbDbMetadataEntity; pub use db_runtime_event::KbDbRuntimeEventEntity; pub use dex::KbDexEntity; +pub use dex_decoded_event::KbDexDecodedEventEntity; pub use known_http_endpoint::KbKnownHttpEndpointEntity; pub use known_ws_endpoint::KbKnownWsEndpointEntity; +pub use launch_attribution::KbLaunchAttributionEntity; +pub use launch_surface::KbLaunchSurfaceEntity; +pub use launch_surface_key::KbLaunchSurfaceKeyEntity; pub use liquidity_event::KbLiquidityEventEntity; pub use observed_token::KbObservedTokenEntity; pub use onchain_observation::KbOnchainObservationEntity; pub use pair::KbPairEntity; pub use pool::KbPoolEntity; pub use pool_listing::KbPoolListingEntity; +pub use pool_origin::KbPoolOriginEntity; pub use pool_token::KbPoolTokenEntity; pub use swap::KbSwapEntity; pub use token::KbTokenEntity; pub use token_burn_event::KbTokenBurnEventEntity; pub use token_mint_event::KbTokenMintEventEntity; -pub use chain_instruction::KbChainInstructionEntity; -pub use chain_slot::KbChainSlotEntity; -pub use chain_transaction::KbChainTransactionEntity; -pub use dex_decoded_event::KbDexDecodedEventEntity; -pub use launch_surface::KbLaunchSurfaceEntity; -pub use launch_surface_key::KbLaunchSurfaceKeyEntity; -pub use launch_attribution::KbLaunchAttributionEntity; diff --git a/kb_lib/src/db/entities/pool_origin.rs b/kb_lib/src/db/entities/pool_origin.rs new file mode 100644 index 0000000..c17564d --- /dev/null +++ b/kb_lib/src/db/entities/pool_origin.rs @@ -0,0 +1,38 @@ +// file: kb_lib/src/db/entities/pool_origin.rs + +//! Pool origin entity. + +/// Persisted pool-origin row. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)] +pub struct KbPoolOriginEntity { + /// Numeric primary key. + pub id: i64, + /// Related DEX id. + pub dex_id: i64, + /// Related pool id. + pub pool_id: i64, + /// Optional related pair id. + pub pair_id: std::option::Option, + /// Optional related pool listing id. + pub pool_listing_id: std::option::Option, + /// Founding transaction id in the local model. + pub founding_transaction_id: i64, + /// Founding decoded-event id in the local model. + pub founding_decoded_event_id: i64, + /// Founding signature in the local model. + pub founding_signature: std::string::String, + /// Founding protocol name. + pub founding_protocol_name: std::string::String, + /// Founding event kind. + pub founding_event_kind: std::string::String, + /// Discovery source kind. + pub source_kind: i16, + /// Optional source endpoint logical name. + pub source_endpoint_name: std::option::Option, + /// Optional related launch attribution id. + pub launch_attribution_id: std::option::Option, + /// Creation timestamp encoded as RFC3339 UTC text. + pub created_at: std::string::String, + /// Update timestamp encoded as RFC3339 UTC text. + pub updated_at: std::string::String, +} diff --git a/kb_lib/src/db/queries.rs b/kb_lib/src/db/queries.rs index 02fbd06..aec7ca0 100644 --- a/kb_lib/src/db/queries.rs +++ b/kb_lib/src/db/queries.rs @@ -7,32 +7,42 @@ //! Database queries. mod analysis_signal; +mod chain_instruction; +mod chain_slot; +mod chain_transaction; mod db_metadata; mod db_runtime_event; mod dex; +mod dex_decoded_event; mod known_http_endpoint; mod known_ws_endpoint; +mod launch_attribution; +mod launch_surface; +mod launch_surface_key; mod liquidity_event; mod observed_token; mod onchain_observation; mod pair; mod pool; mod pool_listing; +mod pool_origin; mod pool_token; mod swap; mod token; mod token_burn_event; mod token_mint_event; -mod chain_instruction; -mod chain_slot; -mod chain_transaction; -mod dex_decoded_event; -mod launch_surface; -mod launch_surface_key; -mod launch_attribution; pub use analysis_signal::insert_analysis_signal; pub use analysis_signal::list_recent_analysis_signals; +pub use chain_instruction::delete_chain_instructions_by_transaction_id; +pub use chain_instruction::insert_chain_instruction; +pub use chain_instruction::list_chain_instructions_by_transaction_id; +pub use chain_slot::get_chain_slot; +pub use chain_slot::list_recent_chain_slots; +pub use chain_slot::upsert_chain_slot; +pub use chain_transaction::get_chain_transaction_by_signature; +pub use chain_transaction::list_recent_chain_transactions; +pub use chain_transaction::upsert_chain_transaction; pub use db_metadata::get_db_metadata; pub use db_metadata::list_db_metadata; pub use db_metadata::upsert_db_metadata; @@ -41,12 +51,24 @@ pub use db_runtime_event::list_recent_db_runtime_events; pub use dex::get_dex_by_code; pub use dex::list_dexes; pub use dex::upsert_dex; +pub use dex_decoded_event::get_dex_decoded_event_by_key; +pub use dex_decoded_event::list_dex_decoded_events_by_transaction_id; +pub use dex_decoded_event::upsert_dex_decoded_event; pub use known_http_endpoint::get_known_http_endpoint; pub use known_http_endpoint::list_known_http_endpoints; pub use known_http_endpoint::upsert_known_http_endpoint; pub use known_ws_endpoint::get_known_ws_endpoint; pub use known_ws_endpoint::list_known_ws_endpoints; pub use known_ws_endpoint::upsert_known_ws_endpoint; +pub use launch_attribution::get_launch_attribution_by_decoded_event_id; +pub use launch_attribution::list_launch_attributions_by_pool_id; +pub use launch_attribution::upsert_launch_attribution; +pub use launch_surface::get_launch_surface_by_code; +pub use launch_surface::list_launch_surfaces; +pub use launch_surface::upsert_launch_surface; +pub use launch_surface_key::get_launch_surface_key_by_match; +pub use launch_surface_key::list_launch_surface_keys_by_surface_id; +pub use launch_surface_key::upsert_launch_surface_key; pub use liquidity_event::list_recent_liquidity_events; pub use liquidity_event::upsert_liquidity_event; pub use observed_token::get_observed_token_by_mint; @@ -63,6 +85,9 @@ pub use pool::upsert_pool; pub use pool_listing::get_pool_listing_by_pool_id; pub use pool_listing::list_pool_listings; pub use pool_listing::upsert_pool_listing; +pub use pool_origin::get_pool_origin_by_pool_id; +pub use pool_origin::list_pool_origins; +pub use pool_origin::upsert_pool_origin; pub use pool_token::list_pool_tokens_by_pool_id; pub use pool_token::upsert_pool_token; pub use swap::list_recent_swaps; @@ -73,24 +98,3 @@ pub use token_burn_event::list_recent_token_burn_events; pub use token_burn_event::upsert_token_burn_event; pub use token_mint_event::list_recent_token_mint_events; pub use token_mint_event::upsert_token_mint_event; -pub use chain_instruction::list_chain_instructions_by_transaction_id; -pub use chain_instruction::insert_chain_instruction; -pub use chain_instruction::delete_chain_instructions_by_transaction_id; -pub use chain_slot::list_recent_chain_slots; -pub use chain_slot::upsert_chain_slot; -pub use chain_slot::get_chain_slot; -pub use chain_transaction::list_recent_chain_transactions; -pub use chain_transaction::upsert_chain_transaction; -pub use chain_transaction::get_chain_transaction_by_signature; -pub use dex_decoded_event::get_dex_decoded_event_by_key; -pub use dex_decoded_event::list_dex_decoded_events_by_transaction_id; -pub use dex_decoded_event::upsert_dex_decoded_event; -pub use launch_surface::upsert_launch_surface; -pub use launch_surface::get_launch_surface_by_code; -pub use launch_surface::list_launch_surfaces; -pub use launch_surface_key::upsert_launch_surface_key; -pub use launch_surface_key::get_launch_surface_key_by_match; -pub use launch_surface_key::list_launch_surface_keys_by_surface_id; -pub use launch_attribution::upsert_launch_attribution; -pub use launch_attribution::get_launch_attribution_by_decoded_event_id; -pub use launch_attribution::list_launch_attributions_by_pool_id; diff --git a/kb_lib/src/db/queries/pool_origin.rs b/kb_lib/src/db/queries/pool_origin.rs new file mode 100644 index 0000000..16cf575 --- /dev/null +++ b/kb_lib/src/db/queries/pool_origin.rs @@ -0,0 +1,183 @@ +// file: kb_lib/src/db/queries/pool_origin.rs + +//! Queries for `kb_pool_origins`. + +/// Inserts or updates one pool-origin row and returns its stable internal id. +pub async fn upsert_pool_origin( + database: &crate::KbDatabase, + dto: &crate::KbPoolOriginDto, +) -> Result { + match database.connection() { + crate::KbDatabaseConnection::Sqlite(pool) => { + let query_result = sqlx::query( + r#" +INSERT INTO kb_pool_origins ( + dex_id, + pool_id, + pair_id, + pool_listing_id, + founding_transaction_id, + founding_decoded_event_id, + founding_signature, + founding_protocol_name, + founding_event_kind, + source_kind, + source_endpoint_name, + launch_attribution_id, + created_at, + updated_at +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +ON CONFLICT(pool_id) DO UPDATE SET + pair_id = COALESCE(kb_pool_origins.pair_id, excluded.pair_id), + pool_listing_id = COALESCE(kb_pool_origins.pool_listing_id, excluded.pool_listing_id), + launch_attribution_id = COALESCE(kb_pool_origins.launch_attribution_id, excluded.launch_attribution_id), + updated_at = excluded.updated_at + "#, + ) + .bind(dto.dex_id) + .bind(dto.pool_id) + .bind(dto.pair_id) + .bind(dto.pool_listing_id) + .bind(dto.founding_transaction_id) + .bind(dto.founding_decoded_event_id) + .bind(dto.founding_signature.clone()) + .bind(dto.founding_protocol_name.clone()) + .bind(dto.founding_event_kind.clone()) + .bind(dto.source_kind.to_i16()) + .bind(dto.source_endpoint_name.clone()) + .bind(dto.launch_attribution_id) + .bind(dto.created_at.to_rfc3339()) + .bind(dto.updated_at.to_rfc3339()) + .execute(pool) + .await; + if let Err(error) = query_result { + return Err(crate::KbError::Db(format!( + "cannot upsert kb_pool_origins on sqlite: {}", + error + ))); + } + let id_result = sqlx::query_scalar::( + r#" +SELECT id +FROM kb_pool_origins +WHERE pool_id = ? +LIMIT 1 + "#, + ) + .bind(dto.pool_id) + .fetch_one(pool) + .await; + match id_result { + Ok(id) => Ok(id), + Err(error) => Err(crate::KbError::Db(format!( + "cannot fetch kb_pool_origins id for pool_id '{}' on sqlite: {}", + dto.pool_id, error + ))), + } + } + } +} + +/// Returns one pool-origin row identified by its pool id, if it exists. +pub async fn get_pool_origin_by_pool_id( + database: &crate::KbDatabase, + pool_id: i64, +) -> Result, crate::KbError> { + match database.connection() { + crate::KbDatabaseConnection::Sqlite(pool) => { + let query_result = sqlx::query_as::( + r#" +SELECT + id, + dex_id, + pool_id, + pair_id, + pool_listing_id, + founding_transaction_id, + founding_decoded_event_id, + founding_signature, + founding_protocol_name, + founding_event_kind, + source_kind, + source_endpoint_name, + launch_attribution_id, + created_at, + updated_at +FROM kb_pool_origins +WHERE pool_id = ? +LIMIT 1 + "#, + ) + .bind(pool_id) + .fetch_optional(pool) + .await; + let entity_option = match query_result { + Ok(entity_option) => entity_option, + Err(error) => { + return Err(crate::KbError::Db(format!( + "cannot read kb_pool_origins by pool_id '{}' on sqlite: {}", + pool_id, error + ))); + } + }; + match entity_option { + Some(entity) => crate::KbPoolOriginDto::try_from(entity).map(Some), + None => Ok(None), + } + } + } +} + +/// Lists all pool-origin rows ordered by creation time then id. +pub async fn list_pool_origins( + database: &crate::KbDatabase, +) -> Result, crate::KbError> { + match database.connection() { + crate::KbDatabaseConnection::Sqlite(pool) => { + let query_result = sqlx::query_as::( + r#" +SELECT + id, + dex_id, + pool_id, + pair_id, + pool_listing_id, + founding_transaction_id, + founding_decoded_event_id, + founding_signature, + founding_protocol_name, + founding_event_kind, + source_kind, + source_endpoint_name, + launch_attribution_id, + created_at, + updated_at +FROM kb_pool_origins +ORDER BY created_at ASC, id ASC + "#, + ) + .fetch_all(pool) + .await; + let entities = match query_result { + Ok(entities) => entities, + Err(error) => { + return Err(crate::KbError::Db(format!( + "cannot list kb_pool_origins on sqlite: {}", + error + ))); + } + }; + let mut dtos = std::vec::Vec::new(); + for entity in entities { + let dto_result = crate::KbPoolOriginDto::try_from(entity); + let dto = match dto_result { + Ok(dto) => dto, + Err(error) => return Err(error), + }; + dtos.push(dto); + } + Ok(dtos) + } + } +} diff --git a/kb_lib/src/db/schema.rs b/kb_lib/src/db/schema.rs index 72a2f34..369f396 100644 --- a/kb_lib/src/db/schema.rs +++ b/kb_lib/src/db/schema.rs @@ -254,6 +254,26 @@ pub(crate) async fn ensure_schema(database: &crate::KbDatabase) -> Result<(), cr if let Err(error) = result { return Err(error); } + let result = create_kb_pool_origins_table(pool).await; + if let Err(error) = result { + return Err(error); + } + let result = create_kb_idx_pool_origins_dex_id(pool).await; + if let Err(error) = result { + return Err(error); + } + let result = create_kb_idx_pool_origins_pair_id(pool).await; + if let Err(error) = result { + return Err(error); + } + let result = create_kb_idx_pool_origins_listing_id(pool).await; + if let Err(error) = result { + return Err(error); + } + let result = create_kb_idx_pool_origins_transaction_id(pool).await; + if let Err(error) = result { + return Err(error); + } Ok(()) } } @@ -1392,3 +1412,89 @@ ON kb_launch_attributions(pool_id) ) .await } + +async fn create_kb_pool_origins_table(pool: &sqlx::SqlitePool) -> Result<(), crate::KbError> { + execute_sqlite_schema_statement( + pool, + "create_kb_pool_origins_table", + r#" +CREATE TABLE IF NOT EXISTS kb_pool_origins ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + dex_id INTEGER NOT NULL, + pool_id INTEGER NOT NULL UNIQUE, + pair_id INTEGER NULL, + pool_listing_id INTEGER NULL, + founding_transaction_id INTEGER NOT NULL, + founding_decoded_event_id INTEGER NOT NULL UNIQUE, + founding_signature TEXT NOT NULL, + founding_protocol_name TEXT NOT NULL, + founding_event_kind TEXT NOT NULL, + source_kind INTEGER NOT NULL, + source_endpoint_name TEXT NULL, + launch_attribution_id INTEGER NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + FOREIGN KEY(dex_id) REFERENCES kb_dexes(id) ON DELETE CASCADE, + FOREIGN KEY(pool_id) REFERENCES kb_pools(id) ON DELETE CASCADE, + FOREIGN KEY(pair_id) REFERENCES kb_pairs(id) ON DELETE SET NULL, + FOREIGN KEY(pool_listing_id) REFERENCES kb_pool_listings(id) ON DELETE SET NULL, + FOREIGN KEY(founding_transaction_id) REFERENCES kb_chain_transactions(id) ON DELETE CASCADE, + FOREIGN KEY(founding_decoded_event_id) REFERENCES kb_dex_decoded_events(id) ON DELETE CASCADE, + FOREIGN KEY(launch_attribution_id) REFERENCES kb_launch_attributions(id) ON DELETE SET NULL +) + "#, + ) + .await +} + +async fn create_kb_idx_pool_origins_dex_id(pool: &sqlx::SqlitePool) -> Result<(), crate::KbError> { + execute_sqlite_schema_statement( + pool, + "create_kb_idx_pool_origins_dex_id", + r#" +CREATE INDEX IF NOT EXISTS kb_idx_pool_origins_dex_id +ON kb_pool_origins(dex_id) + "#, + ) + .await +} + +async fn create_kb_idx_pool_origins_pair_id(pool: &sqlx::SqlitePool) -> Result<(), crate::KbError> { + execute_sqlite_schema_statement( + pool, + "create_kb_idx_pool_origins_pair_id", + r#" +CREATE INDEX IF NOT EXISTS kb_idx_pool_origins_pair_id +ON kb_pool_origins(pair_id) + "#, + ) + .await +} + +async fn create_kb_idx_pool_origins_listing_id( + pool: &sqlx::SqlitePool, +) -> Result<(), crate::KbError> { + execute_sqlite_schema_statement( + pool, + "create_kb_idx_pool_origins_listing_id", + r#" +CREATE INDEX IF NOT EXISTS kb_idx_pool_origins_listing_id +ON kb_pool_origins(pool_listing_id) + "#, + ) + .await +} + +async fn create_kb_idx_pool_origins_transaction_id( + pool: &sqlx::SqlitePool, +) -> Result<(), crate::KbError> { + execute_sqlite_schema_statement( + pool, + "create_kb_idx_pool_origins_transaction_id", + r#" +CREATE INDEX IF NOT EXISTS kb_idx_pool_origins_transaction_id +ON kb_pool_origins(founding_transaction_id) + "#, + ) + .await +} diff --git a/kb_lib/src/dex.rs b/kb_lib/src/dex.rs index f3427d3..cf30871 100644 --- a/kb_lib/src/dex.rs +++ b/kb_lib/src/dex.rs @@ -17,47 +17,39 @@ pub use dexlab::KbDexlabCreatePoolDecoded; pub use dexlab::KbDexlabDecodedEvent; pub use dexlab::KbDexlabDecoder; pub use dexlab::KbDexlabSwapDecoded; - pub use fluxbeam::KB_FLUXBEAM_PROGRAM_ID; pub use fluxbeam::KbFluxbeamCreatePoolDecoded; pub use fluxbeam::KbFluxbeamDecodedEvent; pub use fluxbeam::KbFluxbeamDecoder; pub use fluxbeam::KbFluxbeamSwapDecoded; - pub use meteora_damm_v1::KB_METEORA_DAMM_V1_PROGRAM_ID; pub use meteora_damm_v1::KbMeteoraDammV1CreatePoolDecoded; pub use meteora_damm_v1::KbMeteoraDammV1DecodedEvent; pub use meteora_damm_v1::KbMeteoraDammV1Decoder; pub use meteora_damm_v1::KbMeteoraDammV1SwapDecoded; - pub use meteora_damm_v2::KB_METEORA_DAMM_V2_PROGRAM_ID; pub use meteora_damm_v2::KbMeteoraDammV2CreatePoolDecoded; pub use meteora_damm_v2::KbMeteoraDammV2DecodedEvent; pub use meteora_damm_v2::KbMeteoraDammV2Decoder; pub use meteora_damm_v2::KbMeteoraDammV2SwapDecoded; - pub use meteora_dbc::KB_METEORA_DBC_PROGRAM_ID; pub use meteora_dbc::KbMeteoraDbcCreatePoolDecoded; pub use meteora_dbc::KbMeteoraDbcDecodedEvent; pub use meteora_dbc::KbMeteoraDbcDecoder; pub use meteora_dbc::KbMeteoraDbcSwapDecoded; - pub use orca_whirlpools::KB_ORCA_WHIRLPOOLS_PROGRAM_ID; pub use orca_whirlpools::KbOrcaWhirlpoolsCreatePoolDecoded; pub use orca_whirlpools::KbOrcaWhirlpoolsDecodedEvent; pub use orca_whirlpools::KbOrcaWhirlpoolsDecoder; pub use orca_whirlpools::KbOrcaWhirlpoolsSwapDecoded; - pub use pump_fun::KB_PUMP_FUN_PROGRAM_ID; pub use pump_fun::KbPumpFunCreateV2TokenDecoded; pub use pump_fun::KbPumpFunDecodedEvent; pub use pump_fun::KbPumpFunDecoder; - pub use pump_swap::KB_PUMP_SWAP_PROGRAM_ID; pub use pump_swap::KbPumpSwapDecodedEvent; pub use pump_swap::KbPumpSwapDecoder; pub use pump_swap::KbPumpSwapTradeDecoded; - pub use raydium_amm_v4::KB_RAYDIUM_AMM_V4_PROGRAM_ID; pub use raydium_amm_v4::KbRaydiumAmmV4DecodedEvent; pub use raydium_amm_v4::KbRaydiumAmmV4Decoder; diff --git a/kb_lib/src/lib.rs b/kb_lib/src/lib.rs index ba3edeb..199e1b0 100644 --- a/kb_lib/src/lib.rs +++ b/kb_lib/src/lib.rs @@ -8,44 +8,235 @@ #![deny(unreachable_pub)] #![warn(missing_docs)] -mod constants; -mod error; mod config; -mod tracing; -mod types; -mod ws_client; -mod json_rpc_ws; -mod solana_pubsub_ws; -mod ws_manager; -mod http_client; -mod http_pool; +mod constants; mod db; mod detect; -mod tx_resolution; -mod tx_model; mod dex; mod dex_decode; mod dex_detect; +mod error; +mod http_client; +mod http_pool; +mod json_rpc_ws; mod launch_origin; +mod pool_origin; +mod solana_pubsub_ws; +mod tracing; +mod tx_model; +mod tx_resolution; +mod types; +mod ws_client; +mod ws_manager; -pub use constants::*; -pub use error::KbError; pub use config::KbAppConfig; pub use config::KbConfig; pub use config::KbDataConfig; +pub use config::KbDatabaseConfig; pub use config::KbHttpEndpointConfig; pub use config::KbLoggingConfig; pub use config::KbSolanaConfig; -pub use config::KbWsEndpointConfig; -pub use config::KbDatabaseConfig; pub use config::KbSqliteDatabaseConfig; -pub use tracing::KbTracingGuard; -pub use tracing::init_tracing; -pub use types::KbConnectionState; -pub use ws_client::WsClient; -pub use ws_client::WsEvent; -pub use ws_client::WsOutgoingMessage; -pub use ws_client::WsSubscriptionInfo; +pub use config::KbWsEndpointConfig; +pub use constants::*; +pub use db::KbAnalysisSignalDto; +pub use db::KbAnalysisSignalEntity; +pub use db::KbAnalysisSignalSeverity; +pub use db::KbChainInstructionDto; +pub use db::KbChainInstructionEntity; +pub use db::KbChainSlotDto; +pub use db::KbChainSlotEntity; +pub use db::KbChainTransactionDto; +pub use db::KbChainTransactionEntity; +pub use db::KbDatabase; +pub use db::KbDatabaseBackend; +pub use db::KbDatabaseConnection; +pub use db::KbDbMetadataDto; +pub use db::KbDbMetadataEntity; +pub use db::KbDbRuntimeEventDto; +pub use db::KbDbRuntimeEventEntity; +pub use db::KbDbRuntimeEventLevel; +pub use db::KbDexDecodedEventDto; +pub use db::KbDexDecodedEventEntity; +pub use db::KbDexDto; +pub use db::KbDexEntity; +pub use db::KbKnownHttpEndpointDto; +pub use db::KbKnownHttpEndpointEntity; +pub use db::KbKnownWsEndpointDto; +pub use db::KbKnownWsEndpointEntity; +pub use db::KbLaunchAttributionDto; +pub use db::KbLaunchAttributionEntity; +pub use db::KbLaunchSurfaceDto; +pub use db::KbLaunchSurfaceEntity; +pub use db::KbLaunchSurfaceKeyDto; +pub use db::KbLaunchSurfaceKeyEntity; +pub use db::KbLiquidityEventDto; +pub use db::KbLiquidityEventEntity; +pub use db::KbLiquidityEventKind; +pub use db::KbObservationSourceKind; +pub use db::KbObservedTokenDto; +pub use db::KbObservedTokenEntity; +pub use db::KbObservedTokenStatus; +pub use db::KbOnchainObservationDto; +pub use db::KbOnchainObservationEntity; +pub use db::KbPairDto; +pub use db::KbPairEntity; +pub use db::KbPoolDto; +pub use db::KbPoolEntity; +pub use db::KbPoolKind; +pub use db::KbPoolListingDto; +pub use db::KbPoolListingEntity; +pub use db::KbPoolOriginDto; +pub use db::KbPoolOriginEntity; +pub use db::KbPoolStatus; +pub use db::KbPoolTokenDto; +pub use db::KbPoolTokenEntity; +pub use db::KbPoolTokenRole; +pub use db::KbSwapDto; +pub use db::KbSwapEntity; +pub use db::KbSwapTradeSide; +pub use db::KbTokenBurnEventDto; +pub use db::KbTokenBurnEventEntity; +pub use db::KbTokenDto; +pub use db::KbTokenEntity; +pub use db::KbTokenMintEventDto; +pub use db::KbTokenMintEventEntity; +pub use db::delete_chain_instructions_by_transaction_id; +pub use db::get_chain_slot; +pub use db::get_chain_transaction_by_signature; +pub use db::get_db_metadata; +pub use db::get_dex_by_code; +pub use db::get_dex_decoded_event_by_key; +pub use db::get_known_http_endpoint; +pub use db::get_known_ws_endpoint; +pub use db::get_launch_attribution_by_decoded_event_id; +pub use db::get_launch_surface_by_code; +pub use db::get_launch_surface_key_by_match; +pub use db::get_observed_token_by_mint; +pub use db::get_pair_by_pool_id; +pub use db::get_pool_by_address; +pub use db::get_pool_listing_by_pool_id; +pub use db::get_pool_origin_by_pool_id; +pub use db::get_token_by_mint; +pub use db::insert_analysis_signal; +pub use db::insert_chain_instruction; +pub use db::insert_db_runtime_event; +pub use db::insert_onchain_observation; +pub use db::list_chain_instructions_by_transaction_id; +pub use db::list_db_metadata; +pub use db::list_dex_decoded_events_by_transaction_id; +pub use db::list_dexes; +pub use db::list_known_http_endpoints; +pub use db::list_known_ws_endpoints; +pub use db::list_launch_attributions_by_pool_id; +pub use db::list_launch_surface_keys_by_surface_id; +pub use db::list_launch_surfaces; +pub use db::list_observed_tokens; +pub use db::list_pairs; +pub use db::list_pool_listings; +pub use db::list_pool_origins; +pub use db::list_pool_tokens_by_pool_id; +pub use db::list_pools; +pub use db::list_recent_analysis_signals; +pub use db::list_recent_chain_slots; +pub use db::list_recent_chain_transactions; +pub use db::list_recent_db_runtime_events; +pub use db::list_recent_liquidity_events; +pub use db::list_recent_onchain_observations; +pub use db::list_recent_swaps; +pub use db::list_recent_token_burn_events; +pub use db::list_recent_token_mint_events; +pub use db::upsert_chain_slot; +pub use db::upsert_chain_transaction; +pub use db::upsert_db_metadata; +pub use db::upsert_dex; +pub use db::upsert_dex_decoded_event; +pub use db::upsert_known_http_endpoint; +pub use db::upsert_known_ws_endpoint; +pub use db::upsert_launch_attribution; +pub use db::upsert_launch_surface; +pub use db::upsert_launch_surface_key; +pub use db::upsert_liquidity_event; +pub use db::upsert_observed_token; +pub use db::upsert_pair; +pub use db::upsert_pool; +pub use db::upsert_pool_listing; +pub use db::upsert_pool_origin; +pub use db::upsert_pool_token; +pub use db::upsert_swap; +pub use db::upsert_token; +pub use db::upsert_token_burn_event; +pub use db::upsert_token_mint_event; +pub use detect::KbDetectionObservationInput; +pub use detect::KbDetectionPersistenceService; +pub use detect::KbDetectionPoolCandidateInput; +pub use detect::KbDetectionPoolCandidateResult; +pub use detect::KbDetectionSignalInput; +pub use detect::KbDetectionTokenCandidateInput; +pub use detect::KbDetectionTokenCandidateResult; +pub use detect::KbSolanaWsDetectionOutcome; +pub use detect::KbSolanaWsDetectionService; +pub use detect::KbWsDetectionNotificationEnvelope; +pub use detect::KbWsDetectionRelay; +pub use detect::KbWsDetectionRelayStats; +pub use dex::KB_DEXLAB_PROGRAM_ID; +pub use dex::KB_FLUXBEAM_PROGRAM_ID; +pub use dex::KB_METEORA_DAMM_V1_PROGRAM_ID; +pub use dex::KB_METEORA_DAMM_V2_PROGRAM_ID; +pub use dex::KB_METEORA_DBC_PROGRAM_ID; +pub use dex::KB_ORCA_WHIRLPOOLS_PROGRAM_ID; +pub use dex::KB_PUMP_FUN_PROGRAM_ID; +pub use dex::KB_PUMP_SWAP_PROGRAM_ID; +pub use dex::KB_RAYDIUM_AMM_V4_PROGRAM_ID; +pub use dex::KbDexlabCreatePoolDecoded; +pub use dex::KbDexlabDecodedEvent; +pub use dex::KbDexlabDecoder; +pub use dex::KbDexlabSwapDecoded; +pub use dex::KbFluxbeamCreatePoolDecoded; +pub use dex::KbFluxbeamDecodedEvent; +pub use dex::KbFluxbeamDecoder; +pub use dex::KbFluxbeamSwapDecoded; +pub use dex::KbMeteoraDammV1CreatePoolDecoded; +pub use dex::KbMeteoraDammV1DecodedEvent; +pub use dex::KbMeteoraDammV1Decoder; +pub use dex::KbMeteoraDammV1SwapDecoded; +pub use dex::KbMeteoraDammV2CreatePoolDecoded; +pub use dex::KbMeteoraDammV2DecodedEvent; +pub use dex::KbMeteoraDammV2Decoder; +pub use dex::KbMeteoraDammV2SwapDecoded; +pub use dex::KbMeteoraDbcCreatePoolDecoded; +pub use dex::KbMeteoraDbcDecodedEvent; +pub use dex::KbMeteoraDbcDecoder; +pub use dex::KbMeteoraDbcSwapDecoded; +pub use dex::KbOrcaWhirlpoolsCreatePoolDecoded; +pub use dex::KbOrcaWhirlpoolsDecodedEvent; +pub use dex::KbOrcaWhirlpoolsDecoder; +pub use dex::KbOrcaWhirlpoolsSwapDecoded; +pub use dex::KbPumpFunCreateV2TokenDecoded; +pub use dex::KbPumpFunDecodedEvent; +pub use dex::KbPumpFunDecoder; +pub use dex::KbPumpSwapDecodedEvent; +pub use dex::KbPumpSwapDecoder; +pub use dex::KbPumpSwapTradeDecoded; +pub use dex::KbRaydiumAmmV4DecodedEvent; +pub use dex::KbRaydiumAmmV4Decoder; +pub use dex::KbRaydiumAmmV4Initialize2PoolDecoded; +pub use dex_decode::KbDexDecodeService; +pub use dex_detect::KbDexDetectService; +pub use dex_detect::KbDexPoolDetectionResult; +pub use error::KbError; +pub use http_client::HttpClient; +pub use http_client::KbHttpEndpointStatus; +pub use http_client::KbHttpMethodClass; +pub use http_client::KbJsonRpcHttpErrorObject; +pub use http_client::KbJsonRpcHttpErrorResponse; +pub use http_client::KbJsonRpcHttpRequest; +pub use http_client::KbJsonRpcHttpResponse; +pub use http_client::KbJsonRpcHttpSuccessResponse; +pub use http_client::parse_kb_json_rpc_http_response_text; +pub use http_client::parse_kb_json_rpc_http_response_value; +pub use http_pool::HttpEndpointPool; +pub use http_pool::KbHttpPoolClientSnapshot; pub use json_rpc_ws::KbJsonRpcWsErrorObject; pub use json_rpc_ws::KbJsonRpcWsErrorResponse; pub use json_rpc_ws::KbJsonRpcWsIncomingMessage; @@ -56,210 +247,27 @@ pub use json_rpc_ws::KbJsonRpcWsSuccessResponse; pub use json_rpc_ws::kb_is_probable_json_rpc_object_text; pub use json_rpc_ws::parse_kb_json_rpc_ws_incoming_text; pub use json_rpc_ws::parse_kb_json_rpc_ws_incoming_value; +pub use launch_origin::KbLaunchAttributionResult; +pub use launch_origin::KbLaunchOriginService; +pub use pool_origin::KbPoolOriginResult; +pub use pool_origin::KbPoolOriginService; pub use solana_pubsub_ws::KbSolanaWsTypedNotification; pub use solana_pubsub_ws::parse_kb_solana_ws_typed_notification; pub use solana_pubsub_ws::parse_kb_solana_ws_typed_notification_from_event; -pub use ws_manager::WsManagedEndpointSnapshot; -pub use ws_manager::WsManager; -pub use ws_manager::WsManagerSnapshot; -pub use http_client::HttpClient; -pub use http_client::KbJsonRpcHttpErrorObject; -pub use http_client::KbJsonRpcHttpErrorResponse; -pub use http_client::KbJsonRpcHttpRequest; -pub use http_client::KbJsonRpcHttpResponse; -pub use http_client::KbJsonRpcHttpSuccessResponse; -pub use http_client::KbHttpEndpointStatus; -pub use http_client::KbHttpMethodClass; -pub use http_client::parse_kb_json_rpc_http_response_text; -pub use http_client::parse_kb_json_rpc_http_response_value; -pub use http_pool::HttpEndpointPool; -pub use http_pool::KbHttpPoolClientSnapshot; -pub use db::KbDatabase; -pub use db::KbDatabaseBackend; -pub use db::KbDatabaseConnection; -pub use db::KbDbMetadataDto; -pub use db::KbDbMetadataEntity; -pub use db::get_db_metadata; -pub use db::list_db_metadata; -pub use db::upsert_db_metadata; -pub use db::KbDbRuntimeEventDto; -pub use db::KbDbRuntimeEventEntity; -pub use db::KbDbRuntimeEventLevel; -pub use db::KbKnownHttpEndpointDto; -pub use db::KbKnownHttpEndpointEntity; -pub use db::KbKnownWsEndpointDto; -pub use db::KbKnownWsEndpointEntity; -pub use db::KbObservedTokenDto; -pub use db::KbObservedTokenEntity; -pub use db::KbObservedTokenStatus; -pub use db::KbAnalysisSignalDto; -pub use db::KbAnalysisSignalEntity; -pub use db::KbAnalysisSignalSeverity; -pub use db::KbObservationSourceKind; -pub use db::KbOnchainObservationDto; -pub use db::KbOnchainObservationEntity; -pub use db::KbDexDto; -pub use db::KbDexEntity; -pub use db::KbPairDto; -pub use db::KbPairEntity; -pub use db::KbPoolDto; -pub use db::KbPoolEntity; -pub use db::KbPoolKind; -pub use db::KbPoolListingDto; -pub use db::KbPoolListingEntity; -pub use db::KbPoolStatus; -pub use db::KbPoolTokenDto; -pub use db::KbPoolTokenEntity; -pub use db::KbPoolTokenRole; -pub use db::KbTokenDto; -pub use db::KbTokenEntity; -pub use db::KbLiquidityEventDto; -pub use db::KbLiquidityEventEntity; -pub use db::KbLiquidityEventKind; -pub use db::KbSwapDto; -pub use db::KbSwapEntity; -pub use db::KbSwapTradeSide; -pub use db::KbTokenBurnEventDto; -pub use db::KbTokenBurnEventEntity; -pub use db::KbTokenMintEventDto; -pub use db::KbTokenMintEventEntity; -pub use db::KbChainInstructionDto; -pub use db::KbChainSlotDto; -pub use db::KbChainTransactionDto; -pub use db::KbChainInstructionEntity; -pub use db::KbChainSlotEntity; -pub use db::KbChainTransactionEntity; -pub use db::KbDexDecodedEventDto; -pub use db::KbDexDecodedEventEntity; -pub use db::KbLaunchSurfaceDto; -pub use db::KbLaunchSurfaceKeyDto; -pub use db::KbLaunchAttributionDto; -pub use db::KbLaunchSurfaceEntity; -pub use db::KbLaunchSurfaceKeyEntity; -pub use db::KbLaunchAttributionEntity; -pub use db::upsert_launch_surface; -pub use db::get_launch_surface_by_code; -pub use db::list_launch_surfaces; -pub use db::upsert_launch_surface_key; -pub use db::get_launch_surface_key_by_match; -pub use db::list_launch_surface_keys_by_surface_id; -pub use db::upsert_launch_attribution; -pub use db::get_launch_attribution_by_decoded_event_id; -pub use db::list_launch_attributions_by_pool_id; -pub use db::delete_chain_instructions_by_transaction_id; -pub use db::get_chain_slot; -pub use db::get_chain_transaction_by_signature; -pub use db::insert_chain_instruction; -pub use db::list_chain_instructions_by_transaction_id; -pub use db::list_recent_chain_slots; -pub use db::list_recent_chain_transactions; -pub use db::upsert_chain_slot; -pub use db::upsert_chain_transaction; -pub use db::list_recent_liquidity_events; -pub use db::list_recent_swaps; -pub use db::list_recent_token_burn_events; -pub use db::list_recent_token_mint_events; -pub use db::upsert_liquidity_event; -pub use db::upsert_swap; -pub use db::upsert_token_burn_event; -pub use db::upsert_token_mint_event; -pub use db::get_token_by_mint; -pub use db::list_dexes; -pub use db::upsert_dex; -pub use db::upsert_pair; -pub use db::upsert_pool; -pub use db::upsert_pool_listing; -pub use db::upsert_pool_token; -pub use db::upsert_token; -pub use db::insert_analysis_signal; -pub use db::insert_onchain_observation; -pub use db::list_recent_analysis_signals; -pub use db::list_recent_onchain_observations; -pub use db::get_observed_token_by_mint; -pub use db::list_observed_tokens; -pub use db::upsert_observed_token; -pub use db::get_known_http_endpoint; -pub use db::get_known_ws_endpoint; -pub use db::insert_db_runtime_event; -pub use db::list_known_http_endpoints; -pub use db::list_known_ws_endpoints; -pub use db::list_recent_db_runtime_events; -pub use db::upsert_known_http_endpoint; -pub use db::upsert_known_ws_endpoint; -pub use db::get_dex_by_code; -pub use db::get_pair_by_pool_id; -pub use db::get_pool_by_address; -pub use db::get_pool_listing_by_pool_id; -pub use db::list_pairs; -pub use db::list_pool_listings; -pub use db::list_pool_tokens_by_pool_id; -pub use db::list_pools; -pub use db::get_dex_decoded_event_by_key; -pub use db::list_dex_decoded_events_by_transaction_id; -pub use db::upsert_dex_decoded_event; -pub use detect::KbDetectionObservationInput; -pub use detect::KbDetectionPersistenceService; -pub use detect::KbDetectionSignalInput; -pub use detect::KbDetectionTokenCandidateInput; -pub use detect::KbDetectionTokenCandidateResult; -pub use detect::KbSolanaWsDetectionOutcome; -pub use detect::KbSolanaWsDetectionService; -pub use detect::KbWsDetectionNotificationEnvelope; -pub use detect::KbWsDetectionRelay; -pub use detect::KbWsDetectionRelayStats; -pub use detect::KbDetectionPoolCandidateInput; -pub use detect::KbDetectionPoolCandidateResult; +pub use tracing::KbTracingGuard; +pub use tracing::init_tracing; +pub use tx_model::KbTransactionModelService; pub use tx_resolution::KbTransactionResolutionOutcome; pub use tx_resolution::KbTransactionResolutionRequest; pub use tx_resolution::KbTransactionResolutionService; pub use tx_resolution::KbWsTransactionResolutionEnvelope; pub use tx_resolution::KbWsTransactionResolutionRelay; pub use tx_resolution::KbWsTransactionResolutionRelayStats; -pub use tx_model::KbTransactionModelService; -pub use dex::KB_RAYDIUM_AMM_V4_PROGRAM_ID; -pub use dex::KbRaydiumAmmV4DecodedEvent; -pub use dex::KbRaydiumAmmV4Decoder; -pub use dex::KbRaydiumAmmV4Initialize2PoolDecoded; -pub use dex::KB_PUMP_FUN_PROGRAM_ID; -pub use dex::KbPumpFunCreateV2TokenDecoded; -pub use dex::KbPumpFunDecodedEvent; -pub use dex::KbPumpFunDecoder; -pub use dex::KB_PUMP_SWAP_PROGRAM_ID; -pub use dex::KbPumpSwapDecodedEvent; -pub use dex::KbPumpSwapDecoder; -pub use dex::KbPumpSwapTradeDecoded; -pub use dex::KB_METEORA_DBC_PROGRAM_ID; -pub use dex::KbMeteoraDbcCreatePoolDecoded; -pub use dex::KbMeteoraDbcDecodedEvent; -pub use dex::KbMeteoraDbcDecoder; -pub use dex::KbMeteoraDbcSwapDecoded; -pub use dex::KB_METEORA_DAMM_V1_PROGRAM_ID; -pub use dex::KbMeteoraDammV1CreatePoolDecoded; -pub use dex::KbMeteoraDammV1DecodedEvent; -pub use dex::KbMeteoraDammV1Decoder; -pub use dex::KbMeteoraDammV1SwapDecoded; -pub use dex::KB_METEORA_DAMM_V2_PROGRAM_ID; -pub use dex::KbMeteoraDammV2CreatePoolDecoded; -pub use dex::KbMeteoraDammV2DecodedEvent; -pub use dex::KbMeteoraDammV2Decoder; -pub use dex::KbMeteoraDammV2SwapDecoded; -pub use dex_decode::KbDexDecodeService; -pub use dex_detect::KbDexDetectService; -pub use dex_detect::KbDexPoolDetectionResult; -pub use dex::KB_ORCA_WHIRLPOOLS_PROGRAM_ID; -pub use dex::KbOrcaWhirlpoolsCreatePoolDecoded; -pub use dex::KbOrcaWhirlpoolsDecodedEvent; -pub use dex::KbOrcaWhirlpoolsDecoder; -pub use dex::KbOrcaWhirlpoolsSwapDecoded; -pub use dex::KB_FLUXBEAM_PROGRAM_ID; -pub use dex::KbFluxbeamCreatePoolDecoded; -pub use dex::KbFluxbeamDecodedEvent; -pub use dex::KbFluxbeamDecoder; -pub use dex::KbFluxbeamSwapDecoded; -pub use dex::KB_DEXLAB_PROGRAM_ID; -pub use dex::KbDexlabCreatePoolDecoded; -pub use dex::KbDexlabDecodedEvent; -pub use dex::KbDexlabDecoder; -pub use dex::KbDexlabSwapDecoded; -pub use launch_origin::KbLaunchAttributionResult; -pub use launch_origin::KbLaunchOriginService; +pub use types::KbConnectionState; +pub use ws_client::WsClient; +pub use ws_client::WsEvent; +pub use ws_client::WsOutgoingMessage; +pub use ws_client::WsSubscriptionInfo; +pub use ws_manager::WsManagedEndpointSnapshot; +pub use ws_manager::WsManager; +pub use ws_manager::WsManagerSnapshot; diff --git a/kb_lib/src/pool_origin.rs b/kb_lib/src/pool_origin.rs new file mode 100644 index 0000000..c4a0d64 --- /dev/null +++ b/kb_lib/src/pool_origin.rs @@ -0,0 +1,428 @@ +// file: kb_lib/src/pool_origin.rs + +//! Cross-DEX pool-origin recording service. + +/// One recorded pool-origin result. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct KbPoolOriginResult { + /// Persisted pool-origin id. + pub pool_origin_id: i64, + /// Related pool id. + pub pool_id: i64, + /// Optional related pair id. + pub pair_id: std::option::Option, + /// Optional related pool listing id. + pub pool_listing_id: std::option::Option, + /// Whether the pool-origin row was newly created. + pub created_origin: bool, +} + +/// Pool-origin consolidation service. +#[derive(Debug, Clone)] +pub struct KbPoolOriginService { + database: std::sync::Arc, + persistence: crate::KbDetectionPersistenceService, +} + +impl KbPoolOriginService { + /// Creates a new pool-origin service. + pub fn new(database: std::sync::Arc) -> Self { + let persistence = crate::KbDetectionPersistenceService::new(database.clone()); + Self { + database, + persistence, + } + } + + /// Records pool-origin rows for one resolved transaction signature. + pub async fn record_transaction_by_signature( + &self, + signature: &str, + ) -> Result, crate::KbError> { + let transaction_result = + crate::get_chain_transaction_by_signature(self.database.as_ref(), signature).await; + let transaction_option = match transaction_result { + Ok(transaction_option) => transaction_option, + Err(error) => return Err(error), + }; + let transaction = match transaction_option { + Some(transaction) => transaction, + None => { + return Err(crate::KbError::InvalidState(format!( + "cannot record pool origins for unknown transaction '{}'", + signature + ))); + } + }; + + let transaction_id = match transaction.id { + Some(transaction_id) => transaction_id, + None => { + return Err(crate::KbError::InvalidState(format!( + "transaction '{}' has no internal id", + signature + ))); + } + }; + + let decoded_events_result = crate::list_dex_decoded_events_by_transaction_id( + self.database.as_ref(), + transaction_id, + ) + .await; + let decoded_events = match decoded_events_result { + Ok(decoded_events) => decoded_events, + Err(error) => return Err(error), + }; + + let mut seen_pool_ids = std::collections::HashSet::::new(); + let mut results = std::vec::Vec::new(); + + for decoded_event in &decoded_events { + let decoded_event_id = match decoded_event.id { + Some(decoded_event_id) => decoded_event_id, + None => { + return Err(crate::KbError::InvalidState( + "decoded event has no internal id".to_string(), + )); + } + }; + + let pool_address = match decoded_event.pool_account.clone() { + Some(pool_address) => pool_address, + None => continue, + }; + + let pool_result = + crate::get_pool_by_address(self.database.as_ref(), pool_address.as_str()).await; + let pool_option = match pool_result { + Ok(pool_option) => pool_option, + Err(error) => return Err(error), + }; + let pool = match pool_option { + Some(pool) => pool, + None => continue, + }; + + let pool_id = match pool.id { + Some(pool_id) => pool_id, + None => { + return Err(crate::KbError::InvalidState(format!( + "pool '{}' has no internal id", + pool.address + ))); + } + }; + + if seen_pool_ids.contains(&pool_id) { + continue; + } + seen_pool_ids.insert(pool_id); + + let pair_result = crate::get_pair_by_pool_id(self.database.as_ref(), pool_id).await; + let pair_option = match pair_result { + Ok(pair_option) => pair_option, + Err(error) => return Err(error), + }; + let pair_id = match pair_option { + Some(pair) => pair.id, + None => None, + }; + + let listing_result = + crate::get_pool_listing_by_pool_id(self.database.as_ref(), pool_id).await; + let listing_option = match listing_result { + Ok(listing_option) => listing_option, + Err(error) => return Err(error), + }; + let pool_listing_id = match listing_option { + Some(listing) => listing.id, + None => None, + }; + + let launch_attribution_result = crate::get_launch_attribution_by_decoded_event_id( + self.database.as_ref(), + decoded_event_id, + ) + .await; + let launch_attribution_option = match launch_attribution_result { + Ok(launch_attribution_option) => launch_attribution_option, + Err(error) => return Err(error), + }; + let launch_attribution_id = match launch_attribution_option { + Some(launch_attribution) => launch_attribution.id, + None => None, + }; + + let existing_result = + crate::get_pool_origin_by_pool_id(self.database.as_ref(), pool_id).await; + let existing_option = match existing_result { + Ok(existing_option) => existing_option, + Err(error) => return Err(error), + }; + let created_origin = existing_option.is_none(); + + let dto = crate::KbPoolOriginDto::new( + pool.dex_id, + pool_id, + pair_id, + pool_listing_id, + transaction_id, + decoded_event_id, + transaction.signature.clone(), + decoded_event.protocol_name.clone(), + decoded_event.event_kind.clone(), + crate::KbObservationSourceKind::HttpRpc, + transaction.source_endpoint_name.clone(), + launch_attribution_id, + ); + + let upsert_result = crate::upsert_pool_origin(self.database.as_ref(), &dto).await; + let pool_origin_id = match upsert_result { + Ok(pool_origin_id) => pool_origin_id, + Err(error) => return Err(error), + }; + + if created_origin { + let payload = serde_json::json!({ + "poolId": pool_id, + "pairId": pair_id, + "poolListingId": pool_listing_id, + "foundingSignature": transaction.signature, + "protocolName": decoded_event.protocol_name, + "eventKind": decoded_event.event_kind, + "launchAttributionId": launch_attribution_id + }); + + let observation_result = self + .persistence + .record_observation(&crate::KbDetectionObservationInput::new( + "dex.pool_origin".to_string(), + crate::KbObservationSourceKind::HttpRpc, + transaction.source_endpoint_name.clone(), + transaction.signature.clone(), + transaction.slot, + payload.clone(), + )) + .await; + let observation_id = match observation_result { + Ok(observation_id) => observation_id, + Err(error) => return Err(error), + }; + + let signal_result = self + .persistence + .record_signal(&crate::KbDetectionSignalInput::new( + "signal.dex.pool_origin.recorded".to_string(), + crate::KbAnalysisSignalSeverity::Low, + transaction.signature.clone(), + Some(observation_id), + None, + payload, + )) + .await; + if let Err(error) = signal_result { + return Err(error); + } + } + + results.push(crate::KbPoolOriginResult { + pool_origin_id, + pool_id, + pair_id, + pool_listing_id, + created_origin, + }); + } + + Ok(results) + } +} + +#[cfg(test)] +mod tests { + async fn make_database() -> std::sync::Arc { + 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("pool_origin.sqlite3"); + let config = crate::KbDatabaseConfig { + enabled: true, + backend: crate::KbDatabaseBackend::Sqlite, + sqlite: crate::KbSqliteDatabaseConfig { + 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::KbDatabase::connect_and_initialize(&config).await; + let database = match database_result { + Ok(database) => database, + Err(error) => panic!("database init must succeed: {}", error), + }; + std::sync::Arc::new(database) + } + + async fn seed_bags_backed_meteora_dbc_transaction( + database: std::sync::Arc, + signature: &str, + ) { + let transaction_model = crate::KbTransactionModelService::new(database.clone()); + let dex_decode = crate::KbDexDecodeService::new(database.clone()); + let dex_detect = crate::KbDexDetectService::new(database.clone()); + let launch_origin = crate::KbLaunchOriginService::new(database.clone()); + + let bags_register_result = launch_origin + .register_bags_pool_mapping( + "DbcOriginToken111".to_string().as_str(), + Some("DbcOriginConfig111".to_string()), + Some("DbcOriginPool111".to_string()), + None, + ) + .await; + if let Err(error) = bags_register_result { + panic!("bags mapping registration must succeed: {}", error); + } + + let resolved_transaction = serde_json::json!({ + "slot": 920001, + "blockTime": 1779200001, + "version": 0, + "transaction": { + "message": { + "instructions": [ + { + "programId": crate::KB_METEORA_DBC_PROGRAM_ID, + "program": "meteora-dbc", + "stackHeight": 1, + "accounts": [ + "DbcOriginPool111", + "DbcOriginToken111", + "So11111111111111111111111111111111111111112", + "DbcOriginConfig111", + "DbcOriginCreator111" + ], + "parsed": { + "info": { + "pool": "DbcOriginPool111", + "baseMint": "DbcOriginToken111", + "quoteMint": "So11111111111111111111111111111111111111112", + "poolConfig": "DbcOriginConfig111", + "creator": "DbcOriginCreator111" + } + }, + "data": "opaque" + } + ] + } + }, + "meta": { + "err": null, + "logMessages": [ + "Program log: Instruction: CreatePool" + ] + } + }); + + let project_result = transaction_model + .persist_resolved_transaction( + signature, + Some("helius_primary_http".to_string()), + &resolved_transaction, + ) + .await; + if let Err(error) = project_result { + panic!("projection must succeed: {}", error); + } + + let decode_result = dex_decode.decode_transaction_by_signature(signature).await; + if let Err(error) = decode_result { + panic!("dex decode must succeed: {}", error); + } + + let detect_result = dex_detect.detect_transaction_by_signature(signature).await; + if let Err(error) = detect_result { + panic!("dex detect must succeed: {}", error); + } + + let attribution_result = launch_origin.attribute_transaction_by_signature(signature).await; + if let Err(error) = attribution_result { + panic!("launch attribution must succeed: {}", error); + } + } + + #[tokio::test] + async fn record_transaction_by_signature_creates_pool_origin() { + let database = make_database().await; + seed_bags_backed_meteora_dbc_transaction(database.clone(), "sig-pool-origin-1").await; + + let service = crate::KbPoolOriginService::new(database.clone()); + let record_result = service + .record_transaction_by_signature("sig-pool-origin-1") + .await; + let results = match record_result { + Ok(results) => results, + Err(error) => panic!("pool-origin recording must succeed: {}", error), + }; + + assert_eq!(results.len(), 1); + assert!(results[0].created_origin); + + let fetched_result = + crate::get_pool_origin_by_pool_id(database.as_ref(), results[0].pool_id).await; + let fetched_option = match fetched_result { + Ok(fetched_option) => fetched_option, + Err(error) => panic!("pool-origin fetch must succeed: {}", error), + }; + let fetched = match fetched_option { + Some(fetched) => fetched, + None => panic!("pool origin must exist"), + }; + + assert_eq!(fetched.pool_id, results[0].pool_id); + assert_eq!(fetched.founding_signature, "sig-pool-origin-1".to_string()); + assert_eq!(fetched.founding_protocol_name, "meteora_dbc".to_string()); + assert_eq!(fetched.founding_event_kind, "meteora_dbc.create_pool".to_string()); + assert!(fetched.launch_attribution_id.is_some()); + } + + #[tokio::test] + async fn record_transaction_by_signature_is_idempotent() { + let database = make_database().await; + seed_bags_backed_meteora_dbc_transaction(database.clone(), "sig-pool-origin-2").await; + + let service = crate::KbPoolOriginService::new(database.clone()); + + let first_result = service + .record_transaction_by_signature("sig-pool-origin-2") + .await; + let first_results = match first_result { + Ok(first_results) => first_results, + Err(error) => panic!("first pool-origin recording must succeed: {}", error), + }; + assert_eq!(first_results.len(), 1); + assert!(first_results[0].created_origin); + + let second_result = service + .record_transaction_by_signature("sig-pool-origin-2") + .await; + let second_results = match second_result { + Ok(second_results) => second_results, + Err(error) => panic!("second pool-origin recording must succeed: {}", error), + }; + assert_eq!(second_results.len(), 1); + assert!(!second_results[0].created_origin); + + let listed_result = crate::list_pool_origins(database.as_ref()).await; + let listed = match listed_result { + Ok(listed) => listed, + Err(error) => panic!("pool-origin list must succeed: {}", error), + }; + assert_eq!(listed.len(), 1); + } +} diff --git a/kb_lib/src/tx_resolution.rs b/kb_lib/src/tx_resolution.rs index 42cb363..f46ca66 100644 --- a/kb_lib/src/tx_resolution.rs +++ b/kb_lib/src/tx_resolution.rs @@ -102,6 +102,7 @@ pub struct KbTransactionResolutionService { dex_decode_service: crate::KbDexDecodeService, dex_detect_service: crate::KbDexDetectService, launch_origin_service: crate::KbLaunchOriginService, + pool_origin_service: crate::KbPoolOriginService, http_role: std::string::String, resolved_signatures: std::sync::Arc>>, @@ -119,6 +120,7 @@ impl KbTransactionResolutionService { let dex_decode_service = crate::KbDexDecodeService::new(database.clone()); let dex_detect_service = crate::KbDexDetectService::new(database.clone()); let launch_origin_service = crate::KbLaunchOriginService::new(database.clone()); + let pool_origin_service = crate::KbPoolOriginService::new(database.clone()); Self { http_pool, persistence, @@ -126,6 +128,7 @@ impl KbTransactionResolutionService { dex_decode_service, dex_detect_service, launch_origin_service, + pool_origin_service, http_role, resolved_signatures: std::sync::Arc::new(tokio::sync::Mutex::new( std::collections::HashSet::new(), @@ -321,6 +324,15 @@ impl KbTransactionResolutionService { Err(error) => return Err(error), }; let launch_attribution_count = launch_attributions.len(); + let pool_origins_result = self + .pool_origin_service + .record_transaction_by_signature(request.signature.as_str()) + .await; + let pool_origins = match pool_origins_result { + Ok(pool_origins) => pool_origins, + Err(error) => return Err(error), + }; + let pool_origin_count = pool_origins.len(); let detection_results = match detection_results_result { Ok(detection_results) => detection_results, Err(error) => return Err(error), @@ -336,6 +348,7 @@ impl KbTransactionResolutionService { "decodedEventCount": decoded_event_count, "detectedObjectCount": detected_object_count, "launchAttributionCount": launch_attribution_count, + "poolOriginCount": pool_origin_count, "triggerPayload": request.trigger_payload.clone(), "transaction": transaction_value });