0.5.4
This commit is contained in:
@@ -20,3 +20,4 @@
|
||||
0.5.1 - Ajout des premières tables métier SQLite pour les endpoints connus HTTP/WS et les événements runtime, avec séparation entities/dtos/queries/types
|
||||
0.5.2 - Ajout de la table des tokens observés, de leur statut local et des premières requêtes de persistance associées
|
||||
0.5.3 - Préparation du stockage local des événements techniques et des signaux utiles à l’analyse, avec distinction runtime / on-chain / métier
|
||||
0.5.4 - Ajout du modèle métier normalisé initial pour les DEX, tokens, pools, paires, composition des pools et listings
|
||||
|
||||
@@ -8,7 +8,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
|
||||
|
||||
@@ -357,9 +357,7 @@ Réalisé :
|
||||
- préparation de la traçabilité de provenance par type de source et endpoint, sans remettre en cause l’unicité locale d’un token par mint.
|
||||
|
||||
### 6.22. Version `0.5.4` — Modèle métier normalisé initial
|
||||
Objectif : poser la couche relationnelle métier entre les observations brutes et la future détection technique.
|
||||
|
||||
À faire :
|
||||
Réalisé :
|
||||
|
||||
- ajouter les tables de référence métier pour les DEX, tokens, pools et paires,
|
||||
- distinguer clairement objets de référence et événements d’activité,
|
||||
|
||||
@@ -18,25 +18,39 @@ pub use crate::db::connection::KbDatabaseConnection;
|
||||
pub use crate::db::dtos::KbAnalysisSignalDto;
|
||||
pub use crate::db::dtos::KbDbMetadataDto;
|
||||
pub use crate::db::dtos::KbDbRuntimeEventDto;
|
||||
pub use crate::db::dtos::KbDexDto;
|
||||
pub use crate::db::dtos::KbKnownHttpEndpointDto;
|
||||
pub use crate::db::dtos::KbKnownWsEndpointDto;
|
||||
pub use crate::db::dtos::KbObservedTokenDto;
|
||||
pub use crate::db::dtos::KbOnchainObservationDto;
|
||||
pub use crate::db::dtos::KbPairDto;
|
||||
pub use crate::db::dtos::KbPoolDto;
|
||||
pub use crate::db::dtos::KbPoolListingDto;
|
||||
pub use crate::db::dtos::KbPoolTokenDto;
|
||||
pub use crate::db::dtos::KbTokenDto;
|
||||
pub use crate::db::entities::KbAnalysisSignalEntity;
|
||||
pub use crate::db::entities::KbDbMetadataEntity;
|
||||
pub use crate::db::entities::KbDbRuntimeEventEntity;
|
||||
pub use crate::db::entities::KbDexEntity;
|
||||
pub use crate::db::entities::KbKnownHttpEndpointEntity;
|
||||
pub use crate::db::entities::KbKnownWsEndpointEntity;
|
||||
pub use crate::db::entities::KbObservedTokenEntity;
|
||||
pub use crate::db::entities::KbOnchainObservationEntity;
|
||||
pub use crate::db::entities::KbPairEntity;
|
||||
pub use crate::db::entities::KbPoolEntity;
|
||||
pub use crate::db::entities::KbPoolListingEntity;
|
||||
pub use crate::db::entities::KbPoolTokenEntity;
|
||||
pub use crate::db::entities::KbTokenEntity;
|
||||
pub use crate::db::queries::get_db_metadata;
|
||||
pub use crate::db::queries::get_known_http_endpoint;
|
||||
pub use crate::db::queries::get_known_ws_endpoint;
|
||||
pub use crate::db::queries::get_observed_token_by_mint;
|
||||
pub use crate::db::queries::get_token_by_mint;
|
||||
pub use crate::db::queries::insert_analysis_signal;
|
||||
pub use crate::db::queries::insert_db_runtime_event;
|
||||
pub use crate::db::queries::insert_onchain_observation;
|
||||
pub use crate::db::queries::list_db_metadata;
|
||||
pub use crate::db::queries::list_dexes;
|
||||
pub use crate::db::queries::list_known_http_endpoints;
|
||||
pub use crate::db::queries::list_known_ws_endpoints;
|
||||
pub use crate::db::queries::list_observed_tokens;
|
||||
@@ -44,11 +58,20 @@ pub use crate::db::queries::list_recent_analysis_signals;
|
||||
pub use crate::db::queries::list_recent_db_runtime_events;
|
||||
pub use crate::db::queries::list_recent_onchain_observations;
|
||||
pub use crate::db::queries::upsert_db_metadata;
|
||||
pub use crate::db::queries::upsert_dex;
|
||||
pub use crate::db::queries::upsert_known_http_endpoint;
|
||||
pub use crate::db::queries::upsert_known_ws_endpoint;
|
||||
pub use crate::db::queries::upsert_observed_token;
|
||||
pub use crate::db::queries::upsert_pair;
|
||||
pub use crate::db::queries::upsert_pool;
|
||||
pub use crate::db::queries::upsert_pool_listing;
|
||||
pub use crate::db::queries::upsert_pool_token;
|
||||
pub use crate::db::queries::upsert_token;
|
||||
pub use crate::db::types::KbAnalysisSignalSeverity;
|
||||
pub use crate::db::types::KbDatabaseBackend;
|
||||
pub use crate::db::types::KbDbRuntimeEventLevel;
|
||||
pub use crate::db::types::KbObservationSourceKind;
|
||||
pub use crate::db::types::KbObservedTokenStatus;
|
||||
pub use crate::db::types::KbPoolKind;
|
||||
pub use crate::db::types::KbPoolStatus;
|
||||
pub use crate::db::types::KbPoolTokenRole;
|
||||
|
||||
@@ -5,15 +5,27 @@
|
||||
mod analysis_signal;
|
||||
mod db_metadata;
|
||||
mod db_runtime_event;
|
||||
mod dex;
|
||||
mod known_http_endpoint;
|
||||
mod known_ws_endpoint;
|
||||
mod observed_token;
|
||||
mod onchain_observation;
|
||||
mod pair;
|
||||
mod pool;
|
||||
mod pool_listing;
|
||||
mod pool_token;
|
||||
mod token;
|
||||
|
||||
pub use crate::db::dtos::analysis_signal::KbAnalysisSignalDto;
|
||||
pub use crate::db::dtos::db_metadata::KbDbMetadataDto;
|
||||
pub use crate::db::dtos::db_runtime_event::KbDbRuntimeEventDto;
|
||||
pub use crate::db::dtos::dex::KbDexDto;
|
||||
pub use crate::db::dtos::known_http_endpoint::KbKnownHttpEndpointDto;
|
||||
pub use crate::db::dtos::known_ws_endpoint::KbKnownWsEndpointDto;
|
||||
pub use crate::db::dtos::observed_token::KbObservedTokenDto;
|
||||
pub use crate::db::dtos::onchain_observation::KbOnchainObservationDto;
|
||||
pub use crate::db::dtos::pair::KbPairDto;
|
||||
pub use crate::db::dtos::pool::KbPoolDto;
|
||||
pub use crate::db::dtos::pool_listing::KbPoolListingDto;
|
||||
pub use crate::db::dtos::pool_token::KbPoolTokenDto;
|
||||
pub use crate::db::dtos::token::KbTokenDto;
|
||||
|
||||
84
kb_lib/src/db/dtos/dex.rs
Normal file
84
kb_lib/src/db/dtos/dex.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
// file: kb_lib/src/db/dtos/dex.rs
|
||||
|
||||
//! DEX DTO.
|
||||
|
||||
/// Application-facing normalized DEX DTO.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbDexDto {
|
||||
/// Optional numeric primary key.
|
||||
pub id: std::option::Option<i64>,
|
||||
/// Stable short code.
|
||||
pub code: std::string::String,
|
||||
/// Display name.
|
||||
pub name: std::string::String,
|
||||
/// Optional primary program id.
|
||||
pub program_id: std::option::Option<std::string::String>,
|
||||
/// Optional router program id.
|
||||
pub router_program_id: std::option::Option<std::string::String>,
|
||||
/// Whether this DEX is enabled.
|
||||
pub is_enabled: bool,
|
||||
/// Creation timestamp.
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
/// Update timestamp.
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl KbDexDto {
|
||||
/// Creates a new DEX DTO.
|
||||
pub fn new(
|
||||
code: std::string::String,
|
||||
name: std::string::String,
|
||||
program_id: std::option::Option<std::string::String>,
|
||||
router_program_id: std::option::Option<std::string::String>,
|
||||
is_enabled: bool,
|
||||
) -> Self {
|
||||
let now = chrono::Utc::now();
|
||||
Self {
|
||||
id: None,
|
||||
code,
|
||||
name,
|
||||
program_id,
|
||||
router_program_id,
|
||||
is_enabled,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<crate::KbDexEntity> for KbDexDto {
|
||||
type Error = crate::KbError;
|
||||
|
||||
fn try_from(entity: crate::KbDexEntity) -> Result<Self, Self::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 dex 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 dex updated_at '{}': {}",
|
||||
entity.updated_at, error
|
||||
)));
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
id: Some(entity.id),
|
||||
code: entity.code,
|
||||
name: entity.name,
|
||||
program_id: entity.program_id,
|
||||
router_program_id: entity.router_program_id,
|
||||
is_enabled: entity.is_enabled != 0,
|
||||
created_at,
|
||||
updated_at,
|
||||
})
|
||||
}
|
||||
}
|
||||
84
kb_lib/src/db/dtos/pair.rs
Normal file
84
kb_lib/src/db/dtos/pair.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
// file: kb_lib/src/db/dtos/pair.rs
|
||||
|
||||
//! Normalized pair DTO.
|
||||
|
||||
/// Application-facing normalized pair DTO.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbPairDto {
|
||||
/// Optional numeric primary key.
|
||||
pub id: std::option::Option<i64>,
|
||||
/// Related DEX id.
|
||||
pub dex_id: i64,
|
||||
/// Related pool id.
|
||||
pub pool_id: i64,
|
||||
/// Base token id.
|
||||
pub base_token_id: i64,
|
||||
/// Quote token id.
|
||||
pub quote_token_id: i64,
|
||||
/// Optional display symbol.
|
||||
pub symbol: std::option::Option<std::string::String>,
|
||||
/// First seen timestamp.
|
||||
pub first_seen_at: chrono::DateTime<chrono::Utc>,
|
||||
/// Update timestamp.
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl KbPairDto {
|
||||
/// Creates a new pair DTO.
|
||||
pub fn new(
|
||||
dex_id: i64,
|
||||
pool_id: i64,
|
||||
base_token_id: i64,
|
||||
quote_token_id: i64,
|
||||
symbol: std::option::Option<std::string::String>,
|
||||
) -> Self {
|
||||
let now = chrono::Utc::now();
|
||||
Self {
|
||||
id: None,
|
||||
dex_id,
|
||||
pool_id,
|
||||
base_token_id,
|
||||
quote_token_id,
|
||||
symbol,
|
||||
first_seen_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<crate::KbPairEntity> for KbPairDto {
|
||||
type Error = crate::KbError;
|
||||
|
||||
fn try_from(entity: crate::KbPairEntity) -> Result<Self, Self::Error> {
|
||||
let first_seen_at_result = chrono::DateTime::parse_from_rfc3339(&entity.first_seen_at);
|
||||
let first_seen_at = match first_seen_at_result {
|
||||
Ok(first_seen_at) => first_seen_at.with_timezone(&chrono::Utc),
|
||||
Err(error) => {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot parse pair first_seen_at '{}': {}",
|
||||
entity.first_seen_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 pair updated_at '{}': {}",
|
||||
entity.updated_at, error
|
||||
)));
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
id: Some(entity.id),
|
||||
dex_id: entity.dex_id,
|
||||
pool_id: entity.pool_id,
|
||||
base_token_id: entity.base_token_id,
|
||||
quote_token_id: entity.quote_token_id,
|
||||
symbol: entity.symbol,
|
||||
first_seen_at,
|
||||
updated_at,
|
||||
})
|
||||
}
|
||||
}
|
||||
89
kb_lib/src/db/dtos/pool.rs
Normal file
89
kb_lib/src/db/dtos/pool.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
// file: kb_lib/src/db/dtos/pool.rs
|
||||
|
||||
//! Normalized pool DTO.
|
||||
|
||||
/// Application-facing normalized pool DTO.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbPoolDto {
|
||||
/// Optional numeric primary key.
|
||||
pub id: std::option::Option<i64>,
|
||||
/// Related DEX id.
|
||||
pub dex_id: i64,
|
||||
/// Pool address.
|
||||
pub address: std::string::String,
|
||||
/// Pool kind.
|
||||
pub pool_kind: crate::KbPoolKind,
|
||||
/// Pool status.
|
||||
pub status: crate::KbPoolStatus,
|
||||
/// First seen timestamp.
|
||||
pub first_seen_at: chrono::DateTime<chrono::Utc>,
|
||||
/// Update timestamp.
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl KbPoolDto {
|
||||
/// Creates a new pool DTO.
|
||||
pub fn new(
|
||||
dex_id: i64,
|
||||
address: std::string::String,
|
||||
pool_kind: crate::KbPoolKind,
|
||||
status: crate::KbPoolStatus,
|
||||
) -> Self {
|
||||
let now = chrono::Utc::now();
|
||||
Self {
|
||||
id: None,
|
||||
dex_id,
|
||||
address,
|
||||
pool_kind,
|
||||
status,
|
||||
first_seen_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<crate::KbPoolEntity> for KbPoolDto {
|
||||
type Error = crate::KbError;
|
||||
|
||||
fn try_from(entity: crate::KbPoolEntity) -> Result<Self, Self::Error> {
|
||||
let pool_kind_result = crate::KbPoolKind::from_i16(entity.pool_kind);
|
||||
let pool_kind = match pool_kind_result {
|
||||
Ok(pool_kind) => pool_kind,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let status_result = crate::KbPoolStatus::from_i16(entity.status);
|
||||
let status = match status_result {
|
||||
Ok(status) => status,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let first_seen_at_result = chrono::DateTime::parse_from_rfc3339(&entity.first_seen_at);
|
||||
let first_seen_at = match first_seen_at_result {
|
||||
Ok(first_seen_at) => first_seen_at.with_timezone(&chrono::Utc),
|
||||
Err(error) => {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot parse pool first_seen_at '{}': {}",
|
||||
entity.first_seen_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 updated_at '{}': {}",
|
||||
entity.updated_at, error
|
||||
)));
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
id: Some(entity.id),
|
||||
dex_id: entity.dex_id,
|
||||
address: entity.address,
|
||||
pool_kind,
|
||||
status,
|
||||
first_seen_at,
|
||||
updated_at,
|
||||
})
|
||||
}
|
||||
}
|
||||
104
kb_lib/src/db/dtos/pool_listing.rs
Normal file
104
kb_lib/src/db/dtos/pool_listing.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
// file: kb_lib/src/db/dtos/pool_listing.rs
|
||||
|
||||
//! Pool listing DTO.
|
||||
|
||||
/// Application-facing normalized pool listing DTO.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbPoolListingDto {
|
||||
/// Optional numeric primary key.
|
||||
pub id: std::option::Option<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<i64>,
|
||||
/// Discovery source family.
|
||||
pub source_kind: crate::KbObservationSourceKind,
|
||||
/// Optional source endpoint logical name.
|
||||
pub source_endpoint_name: std::option::Option<std::string::String>,
|
||||
/// Detection timestamp.
|
||||
pub detected_at: chrono::DateTime<chrono::Utc>,
|
||||
/// Optional initial base reserve estimate.
|
||||
pub initial_base_reserve: std::option::Option<f64>,
|
||||
/// Optional initial quote reserve estimate.
|
||||
pub initial_quote_reserve: std::option::Option<f64>,
|
||||
/// Optional initial price estimate in quote units.
|
||||
pub initial_price_quote: std::option::Option<f64>,
|
||||
/// Update timestamp.
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl KbPoolListingDto {
|
||||
/// Creates a new pool listing DTO.
|
||||
pub fn new(
|
||||
dex_id: i64,
|
||||
pool_id: i64,
|
||||
pair_id: std::option::Option<i64>,
|
||||
source_kind: crate::KbObservationSourceKind,
|
||||
source_endpoint_name: std::option::Option<std::string::String>,
|
||||
initial_base_reserve: std::option::Option<f64>,
|
||||
initial_quote_reserve: std::option::Option<f64>,
|
||||
initial_price_quote: std::option::Option<f64>,
|
||||
) -> Self {
|
||||
let now = chrono::Utc::now();
|
||||
Self {
|
||||
id: None,
|
||||
dex_id,
|
||||
pool_id,
|
||||
pair_id,
|
||||
source_kind,
|
||||
source_endpoint_name,
|
||||
detected_at: now,
|
||||
initial_base_reserve,
|
||||
initial_quote_reserve,
|
||||
initial_price_quote,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<crate::KbPoolListingEntity> for KbPoolListingDto {
|
||||
type Error = crate::KbError;
|
||||
|
||||
fn try_from(entity: crate::KbPoolListingEntity) -> Result<Self, Self::Error> {
|
||||
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 detected_at_result = chrono::DateTime::parse_from_rfc3339(&entity.detected_at);
|
||||
let detected_at = match detected_at_result {
|
||||
Ok(detected_at) => detected_at.with_timezone(&chrono::Utc),
|
||||
Err(error) => {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot parse pool_listing detected_at '{}': {}",
|
||||
entity.detected_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_listing 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,
|
||||
source_kind,
|
||||
source_endpoint_name: entity.source_endpoint_name,
|
||||
detected_at,
|
||||
initial_base_reserve: entity.initial_base_reserve,
|
||||
initial_quote_reserve: entity.initial_quote_reserve,
|
||||
initial_price_quote: entity.initial_price_quote,
|
||||
updated_at,
|
||||
})
|
||||
}
|
||||
}
|
||||
89
kb_lib/src/db/dtos/pool_token.rs
Normal file
89
kb_lib/src/db/dtos/pool_token.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
// file: kb_lib/src/db/dtos/pool_token.rs
|
||||
|
||||
//! Pool token DTO.
|
||||
|
||||
/// Application-facing normalized pool token DTO.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbPoolTokenDto {
|
||||
/// Optional numeric primary key.
|
||||
pub id: std::option::Option<i64>,
|
||||
/// Related pool id.
|
||||
pub pool_id: i64,
|
||||
/// Related token id.
|
||||
pub token_id: i64,
|
||||
/// Token role.
|
||||
pub role: crate::KbPoolTokenRole,
|
||||
/// Optional vault address.
|
||||
pub vault_address: std::option::Option<std::string::String>,
|
||||
/// Optional token order inside the pool.
|
||||
pub token_order: std::option::Option<i64>,
|
||||
/// Creation timestamp.
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
/// Update timestamp.
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl KbPoolTokenDto {
|
||||
/// Creates a new pool token DTO.
|
||||
pub fn new(
|
||||
pool_id: i64,
|
||||
token_id: i64,
|
||||
role: crate::KbPoolTokenRole,
|
||||
vault_address: std::option::Option<std::string::String>,
|
||||
token_order: std::option::Option<i64>,
|
||||
) -> Self {
|
||||
let now = chrono::Utc::now();
|
||||
Self {
|
||||
id: None,
|
||||
pool_id,
|
||||
token_id,
|
||||
role,
|
||||
vault_address,
|
||||
token_order,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<crate::KbPoolTokenEntity> for KbPoolTokenDto {
|
||||
type Error = crate::KbError;
|
||||
|
||||
fn try_from(entity: crate::KbPoolTokenEntity) -> Result<Self, Self::Error> {
|
||||
let role_result = crate::KbPoolTokenRole::from_i16(entity.role);
|
||||
let role = match role_result {
|
||||
Ok(role) => role,
|
||||
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_token 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_token updated_at '{}': {}",
|
||||
entity.updated_at, error
|
||||
)));
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
id: Some(entity.id),
|
||||
pool_id: entity.pool_id,
|
||||
token_id: entity.token_id,
|
||||
role,
|
||||
vault_address: entity.vault_address,
|
||||
token_order: entity.token_order,
|
||||
created_at,
|
||||
updated_at,
|
||||
})
|
||||
}
|
||||
}
|
||||
104
kb_lib/src/db/dtos/token.rs
Normal file
104
kb_lib/src/db/dtos/token.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
// file: kb_lib/src/db/dtos/token.rs
|
||||
|
||||
//! Normalized token DTO.
|
||||
|
||||
/// Application-facing normalized token DTO.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbTokenDto {
|
||||
/// Optional numeric primary key.
|
||||
pub id: std::option::Option<i64>,
|
||||
/// Mint address.
|
||||
pub mint: std::string::String,
|
||||
/// Optional token symbol.
|
||||
pub symbol: std::option::Option<std::string::String>,
|
||||
/// Optional token display name.
|
||||
pub name: std::option::Option<std::string::String>,
|
||||
/// Optional decimals value.
|
||||
pub decimals: std::option::Option<u8>,
|
||||
/// Token program id.
|
||||
pub token_program: std::string::String,
|
||||
/// Whether this token is typically used as quote token.
|
||||
pub is_quote_token: bool,
|
||||
/// First seen timestamp.
|
||||
pub first_seen_at: chrono::DateTime<chrono::Utc>,
|
||||
/// Update timestamp.
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl KbTokenDto {
|
||||
/// Creates a new token DTO.
|
||||
pub fn new(
|
||||
mint: std::string::String,
|
||||
symbol: std::option::Option<std::string::String>,
|
||||
name: std::option::Option<std::string::String>,
|
||||
decimals: std::option::Option<u8>,
|
||||
token_program: std::string::String,
|
||||
is_quote_token: bool,
|
||||
) -> Self {
|
||||
let now = chrono::Utc::now();
|
||||
Self {
|
||||
id: None,
|
||||
mint,
|
||||
symbol,
|
||||
name,
|
||||
decimals,
|
||||
token_program,
|
||||
is_quote_token,
|
||||
first_seen_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<crate::KbTokenEntity> for KbTokenDto {
|
||||
type Error = crate::KbError;
|
||||
|
||||
fn try_from(entity: crate::KbTokenEntity) -> Result<Self, Self::Error> {
|
||||
let first_seen_at_result = chrono::DateTime::parse_from_rfc3339(&entity.first_seen_at);
|
||||
let first_seen_at = match first_seen_at_result {
|
||||
Ok(first_seen_at) => first_seen_at.with_timezone(&chrono::Utc),
|
||||
Err(error) => {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot parse token first_seen_at '{}': {}",
|
||||
entity.first_seen_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 token updated_at '{}': {}",
|
||||
entity.updated_at, error
|
||||
)));
|
||||
}
|
||||
};
|
||||
let decimals = match entity.decimals {
|
||||
Some(decimals) => {
|
||||
let decimals_result = u8::try_from(decimals);
|
||||
match decimals_result {
|
||||
Ok(decimals) => Some(decimals),
|
||||
Err(error) => {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot convert token decimals '{}' to u8: {}",
|
||||
decimals, error
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
Ok(Self {
|
||||
id: Some(entity.id),
|
||||
mint: entity.mint,
|
||||
symbol: entity.symbol,
|
||||
name: entity.name,
|
||||
decimals,
|
||||
token_program: entity.token_program,
|
||||
is_quote_token: entity.is_quote_token != 0,
|
||||
first_seen_at,
|
||||
updated_at,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -7,15 +7,27 @@
|
||||
mod analysis_signal;
|
||||
mod db_metadata;
|
||||
mod db_runtime_event;
|
||||
mod dex;
|
||||
mod known_http_endpoint;
|
||||
mod known_ws_endpoint;
|
||||
mod observed_token;
|
||||
mod onchain_observation;
|
||||
mod pair;
|
||||
mod pool;
|
||||
mod pool_listing;
|
||||
mod pool_token;
|
||||
mod token;
|
||||
|
||||
pub use crate::db::entities::analysis_signal::KbAnalysisSignalEntity;
|
||||
pub use crate::db::entities::db_metadata::KbDbMetadataEntity;
|
||||
pub use crate::db::entities::db_runtime_event::KbDbRuntimeEventEntity;
|
||||
pub use crate::db::entities::dex::KbDexEntity;
|
||||
pub use crate::db::entities::known_http_endpoint::KbKnownHttpEndpointEntity;
|
||||
pub use crate::db::entities::known_ws_endpoint::KbKnownWsEndpointEntity;
|
||||
pub use crate::db::entities::observed_token::KbObservedTokenEntity;
|
||||
pub use crate::db::entities::onchain_observation::KbOnchainObservationEntity;
|
||||
pub use crate::db::entities::pair::KbPairEntity;
|
||||
pub use crate::db::entities::pool::KbPoolEntity;
|
||||
pub use crate::db::entities::pool_listing::KbPoolListingEntity;
|
||||
pub use crate::db::entities::pool_token::KbPoolTokenEntity;
|
||||
pub use crate::db::entities::token::KbTokenEntity;
|
||||
|
||||
24
kb_lib/src/db/entities/dex.rs
Normal file
24
kb_lib/src/db/entities/dex.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
// file: kb_lib/src/db/entities/dex.rs
|
||||
|
||||
//! DEX entity.
|
||||
|
||||
/// Persisted normalized DEX row.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
|
||||
pub struct KbDexEntity {
|
||||
/// Numeric primary key.
|
||||
pub id: i64,
|
||||
/// Stable short code.
|
||||
pub code: std::string::String,
|
||||
/// Display name.
|
||||
pub name: std::string::String,
|
||||
/// Optional primary program id.
|
||||
pub program_id: std::option::Option<std::string::String>,
|
||||
/// Optional router program id.
|
||||
pub router_program_id: std::option::Option<std::string::String>,
|
||||
/// Whether this DEX is enabled.
|
||||
pub is_enabled: i64,
|
||||
/// 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,
|
||||
}
|
||||
24
kb_lib/src/db/entities/pair.rs
Normal file
24
kb_lib/src/db/entities/pair.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
// file: kb_lib/src/db/entities/pair.rs
|
||||
|
||||
//! Normalized pair entity.
|
||||
|
||||
/// Persisted normalized pair row.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
|
||||
pub struct KbPairEntity {
|
||||
/// Numeric primary key.
|
||||
pub id: i64,
|
||||
/// Related DEX id.
|
||||
pub dex_id: i64,
|
||||
/// Related pool id.
|
||||
pub pool_id: i64,
|
||||
/// Base token id.
|
||||
pub base_token_id: i64,
|
||||
/// Quote token id.
|
||||
pub quote_token_id: i64,
|
||||
/// Optional display symbol.
|
||||
pub symbol: std::option::Option<std::string::String>,
|
||||
/// First seen timestamp encoded as RFC3339 UTC text.
|
||||
pub first_seen_at: std::string::String,
|
||||
/// Update timestamp encoded as RFC3339 UTC text.
|
||||
pub updated_at: std::string::String,
|
||||
}
|
||||
22
kb_lib/src/db/entities/pool.rs
Normal file
22
kb_lib/src/db/entities/pool.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
// file: kb_lib/src/db/entities/pool.rs
|
||||
|
||||
//! Normalized pool entity.
|
||||
|
||||
/// Persisted normalized pool row.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
|
||||
pub struct KbPoolEntity {
|
||||
/// Numeric primary key.
|
||||
pub id: i64,
|
||||
/// Related DEX id.
|
||||
pub dex_id: i64,
|
||||
/// Pool address.
|
||||
pub address: std::string::String,
|
||||
/// Pool kind stored as stable integer.
|
||||
pub pool_kind: i16,
|
||||
/// Pool status stored as stable integer.
|
||||
pub status: i16,
|
||||
/// First seen timestamp encoded as RFC3339 UTC text.
|
||||
pub first_seen_at: std::string::String,
|
||||
/// Update timestamp encoded as RFC3339 UTC text.
|
||||
pub updated_at: std::string::String,
|
||||
}
|
||||
30
kb_lib/src/db/entities/pool_listing.rs
Normal file
30
kb_lib/src/db/entities/pool_listing.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
// file: kb_lib/src/db/entities/pool_listing.rs
|
||||
|
||||
//! Pool listing entity.
|
||||
|
||||
/// Persisted normalized pool listing row.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
|
||||
pub struct KbPoolListingEntity {
|
||||
/// 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<i64>,
|
||||
/// Discovery source family stored as stable integer.
|
||||
pub source_kind: i16,
|
||||
/// Optional source endpoint logical name.
|
||||
pub source_endpoint_name: std::option::Option<std::string::String>,
|
||||
/// Detection timestamp encoded as RFC3339 UTC text.
|
||||
pub detected_at: std::string::String,
|
||||
/// Optional initial base reserve estimate.
|
||||
pub initial_base_reserve: std::option::Option<f64>,
|
||||
/// Optional initial quote reserve estimate.
|
||||
pub initial_quote_reserve: std::option::Option<f64>,
|
||||
/// Optional initial price estimate in quote units.
|
||||
pub initial_price_quote: std::option::Option<f64>,
|
||||
/// Update timestamp encoded as RFC3339 UTC text.
|
||||
pub updated_at: std::string::String,
|
||||
}
|
||||
24
kb_lib/src/db/entities/pool_token.rs
Normal file
24
kb_lib/src/db/entities/pool_token.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
// file: kb_lib/src/db/entities/pool_token.rs
|
||||
|
||||
//! Pool token composition entity.
|
||||
|
||||
/// Persisted normalized pool token composition row.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
|
||||
pub struct KbPoolTokenEntity {
|
||||
/// Numeric primary key.
|
||||
pub id: i64,
|
||||
/// Related pool id.
|
||||
pub pool_id: i64,
|
||||
/// Related token id.
|
||||
pub token_id: i64,
|
||||
/// Token role stored as stable integer.
|
||||
pub role: i16,
|
||||
/// Optional vault address.
|
||||
pub vault_address: std::option::Option<std::string::String>,
|
||||
/// Optional token order inside the pool.
|
||||
pub token_order: std::option::Option<i64>,
|
||||
/// 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,
|
||||
}
|
||||
26
kb_lib/src/db/entities/token.rs
Normal file
26
kb_lib/src/db/entities/token.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
// file: kb_lib/src/db/entities/token.rs
|
||||
|
||||
//! Normalized token entity.
|
||||
|
||||
/// Persisted normalized token row.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
|
||||
pub struct KbTokenEntity {
|
||||
/// Numeric primary key.
|
||||
pub id: i64,
|
||||
/// Mint address.
|
||||
pub mint: std::string::String,
|
||||
/// Optional token symbol.
|
||||
pub symbol: std::option::Option<std::string::String>,
|
||||
/// Optional token display name.
|
||||
pub name: std::option::Option<std::string::String>,
|
||||
/// Optional decimals value.
|
||||
pub decimals: std::option::Option<i64>,
|
||||
/// Token program id.
|
||||
pub token_program: std::string::String,
|
||||
/// Whether this token is typically used as quote token.
|
||||
pub is_quote_token: i64,
|
||||
/// First seen timestamp encoded as RFC3339 UTC text.
|
||||
pub first_seen_at: std::string::String,
|
||||
/// Update timestamp encoded as RFC3339 UTC text.
|
||||
pub updated_at: std::string::String,
|
||||
}
|
||||
@@ -5,10 +5,16 @@
|
||||
mod analysis_signal;
|
||||
mod db_metadata;
|
||||
mod db_runtime_event;
|
||||
mod dex;
|
||||
mod known_http_endpoint;
|
||||
mod known_ws_endpoint;
|
||||
mod observed_token;
|
||||
mod onchain_observation;
|
||||
mod pair;
|
||||
mod pool;
|
||||
mod pool_listing;
|
||||
mod pool_token;
|
||||
mod token;
|
||||
|
||||
pub use crate::db::queries::analysis_signal::insert_analysis_signal;
|
||||
pub use crate::db::queries::analysis_signal::list_recent_analysis_signals;
|
||||
@@ -17,6 +23,8 @@ pub use crate::db::queries::db_metadata::list_db_metadata;
|
||||
pub use crate::db::queries::db_metadata::upsert_db_metadata;
|
||||
pub use crate::db::queries::db_runtime_event::insert_db_runtime_event;
|
||||
pub use crate::db::queries::db_runtime_event::list_recent_db_runtime_events;
|
||||
pub use crate::db::queries::dex::list_dexes;
|
||||
pub use crate::db::queries::dex::upsert_dex;
|
||||
pub use crate::db::queries::known_http_endpoint::get_known_http_endpoint;
|
||||
pub use crate::db::queries::known_http_endpoint::list_known_http_endpoints;
|
||||
pub use crate::db::queries::known_http_endpoint::upsert_known_http_endpoint;
|
||||
@@ -28,3 +36,9 @@ pub use crate::db::queries::observed_token::list_observed_tokens;
|
||||
pub use crate::db::queries::observed_token::upsert_observed_token;
|
||||
pub use crate::db::queries::onchain_observation::insert_onchain_observation;
|
||||
pub use crate::db::queries::onchain_observation::list_recent_onchain_observations;
|
||||
pub use crate::db::queries::pair::upsert_pair;
|
||||
pub use crate::db::queries::pool::upsert_pool;
|
||||
pub use crate::db::queries::pool_listing::upsert_pool_listing;
|
||||
pub use crate::db::queries::pool_token::upsert_pool_token;
|
||||
pub use crate::db::queries::token::get_token_by_mint;
|
||||
pub use crate::db::queries::token::upsert_token;
|
||||
|
||||
113
kb_lib/src/db/queries/dex.rs
Normal file
113
kb_lib/src/db/queries/dex.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
// file: kb_lib/src/db/queries/dex.rs
|
||||
|
||||
//! Queries for `kb_dexes`.
|
||||
|
||||
/// Inserts or updates one normalized DEX row by code.
|
||||
pub async fn upsert_dex(
|
||||
database: &crate::KbDatabase,
|
||||
dto: &crate::KbDexDto,
|
||||
) -> Result<i64, crate::KbError> {
|
||||
match database.connection() {
|
||||
crate::KbDatabaseConnection::Sqlite(pool) => {
|
||||
let query_result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO kb_dexes (
|
||||
code,
|
||||
name,
|
||||
program_id,
|
||||
router_program_id,
|
||||
is_enabled,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(code) DO UPDATE SET
|
||||
name = excluded.name,
|
||||
program_id = excluded.program_id,
|
||||
router_program_id = excluded.router_program_id,
|
||||
is_enabled = excluded.is_enabled,
|
||||
updated_at = excluded.updated_at
|
||||
"#,
|
||||
)
|
||||
.bind(dto.code.clone())
|
||||
.bind(dto.name.clone())
|
||||
.bind(dto.program_id.clone())
|
||||
.bind(dto.router_program_id.clone())
|
||||
.bind(if dto.is_enabled { 1_i64 } else { 0_i64 })
|
||||
.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_dexes on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
|
||||
r#"
|
||||
SELECT id
|
||||
FROM kb_dexes
|
||||
WHERE code = ?
|
||||
LIMIT 1
|
||||
"#,
|
||||
)
|
||||
.bind(dto.code.clone())
|
||||
.fetch_one(pool)
|
||||
.await;
|
||||
match id_result {
|
||||
Ok(id) => Ok(id),
|
||||
Err(error) => Err(crate::KbError::Db(format!(
|
||||
"cannot fetch kb_dexes id for code '{}' on sqlite: {}",
|
||||
dto.code, error
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Lists normalized DEX rows.
|
||||
pub async fn list_dexes(
|
||||
database: &crate::KbDatabase,
|
||||
) -> Result<std::vec::Vec<crate::KbDexDto>, crate::KbError> {
|
||||
match database.connection() {
|
||||
crate::KbDatabaseConnection::Sqlite(pool) => {
|
||||
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbDexEntity>(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
code,
|
||||
name,
|
||||
program_id,
|
||||
router_program_id,
|
||||
is_enabled,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM kb_dexes
|
||||
ORDER BY code ASC
|
||||
"#,
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await;
|
||||
let entities = match query_result {
|
||||
Ok(entities) => entities,
|
||||
Err(error) => {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot list kb_dexes on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
};
|
||||
let mut dtos = std::vec::Vec::new();
|
||||
for entity in entities {
|
||||
let dto_result = crate::KbDexDto::try_from(entity);
|
||||
let dto = match dto_result {
|
||||
Ok(dto) => dto,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
dtos.push(dto);
|
||||
}
|
||||
Ok(dtos)
|
||||
}
|
||||
}
|
||||
}
|
||||
68
kb_lib/src/db/queries/pair.rs
Normal file
68
kb_lib/src/db/queries/pair.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
// file: kb_lib/src/db/queries/pair.rs
|
||||
|
||||
//! Queries for `kb_pairs`.
|
||||
|
||||
/// Inserts or updates one normalized pair row by pool id.
|
||||
pub async fn upsert_pair(
|
||||
database: &crate::KbDatabase,
|
||||
dto: &crate::KbPairDto,
|
||||
) -> Result<i64, crate::KbError> {
|
||||
match database.connection() {
|
||||
crate::KbDatabaseConnection::Sqlite(pool) => {
|
||||
let query_result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO kb_pairs (
|
||||
dex_id,
|
||||
pool_id,
|
||||
base_token_id,
|
||||
quote_token_id,
|
||||
symbol,
|
||||
first_seen_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(pool_id) DO UPDATE SET
|
||||
dex_id = excluded.dex_id,
|
||||
base_token_id = excluded.base_token_id,
|
||||
quote_token_id = excluded.quote_token_id,
|
||||
symbol = excluded.symbol,
|
||||
updated_at = excluded.updated_at
|
||||
"#,
|
||||
)
|
||||
.bind(dto.dex_id)
|
||||
.bind(dto.pool_id)
|
||||
.bind(dto.base_token_id)
|
||||
.bind(dto.quote_token_id)
|
||||
.bind(dto.symbol.clone())
|
||||
.bind(dto.first_seen_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_pairs on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
|
||||
r#"
|
||||
SELECT id
|
||||
FROM kb_pairs
|
||||
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_pairs id for pool_id '{}' on sqlite: {}",
|
||||
dto.pool_id,
|
||||
error
|
||||
))),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
64
kb_lib/src/db/queries/pool.rs
Normal file
64
kb_lib/src/db/queries/pool.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
// file: kb_lib/src/db/queries/pool.rs
|
||||
|
||||
//! Queries for `kb_pools`.
|
||||
|
||||
/// Inserts or updates one normalized pool row by address.
|
||||
pub async fn upsert_pool(
|
||||
database: &crate::KbDatabase,
|
||||
dto: &crate::KbPoolDto,
|
||||
) -> Result<i64, crate::KbError> {
|
||||
match database.connection() {
|
||||
crate::KbDatabaseConnection::Sqlite(pool) => {
|
||||
let query_result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO kb_pools (
|
||||
dex_id,
|
||||
address,
|
||||
pool_kind,
|
||||
status,
|
||||
first_seen_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(address) DO UPDATE SET
|
||||
dex_id = excluded.dex_id,
|
||||
pool_kind = excluded.pool_kind,
|
||||
status = excluded.status,
|
||||
updated_at = excluded.updated_at
|
||||
"#,
|
||||
)
|
||||
.bind(dto.dex_id)
|
||||
.bind(dto.address.clone())
|
||||
.bind(dto.pool_kind.to_i16())
|
||||
.bind(dto.status.to_i16())
|
||||
.bind(dto.first_seen_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_pools on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
|
||||
r#"
|
||||
SELECT id
|
||||
FROM kb_pools
|
||||
WHERE address = ?
|
||||
LIMIT 1
|
||||
"#,
|
||||
)
|
||||
.bind(dto.address.clone())
|
||||
.fetch_one(pool)
|
||||
.await;
|
||||
match id_result {
|
||||
Ok(id) => Ok(id),
|
||||
Err(error) => Err(crate::KbError::Db(format!(
|
||||
"cannot fetch kb_pools id for address '{}' on sqlite: {}",
|
||||
dto.address, error
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
211
kb_lib/src/db/queries/pool_listing.rs
Normal file
211
kb_lib/src/db/queries/pool_listing.rs
Normal file
@@ -0,0 +1,211 @@
|
||||
// file: kb_lib/src/db/queries/pool_listing.rs
|
||||
|
||||
//! Queries for `kb_pool_listings`.
|
||||
|
||||
/// Inserts or updates one normalized pool listing row by pool id.
|
||||
pub async fn upsert_pool_listing(
|
||||
database: &crate::KbDatabase,
|
||||
dto: &crate::KbPoolListingDto,
|
||||
) -> Result<i64, crate::KbError> {
|
||||
match database.connection() {
|
||||
crate::KbDatabaseConnection::Sqlite(pool) => {
|
||||
let query_result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO kb_pool_listings (
|
||||
dex_id,
|
||||
pool_id,
|
||||
pair_id,
|
||||
source_kind,
|
||||
source_endpoint_name,
|
||||
detected_at,
|
||||
initial_base_reserve,
|
||||
initial_quote_reserve,
|
||||
initial_price_quote,
|
||||
updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(pool_id) DO UPDATE SET
|
||||
dex_id = excluded.dex_id,
|
||||
pair_id = excluded.pair_id,
|
||||
source_kind = excluded.source_kind,
|
||||
source_endpoint_name = excluded.source_endpoint_name,
|
||||
detected_at = excluded.detected_at,
|
||||
initial_base_reserve = excluded.initial_base_reserve,
|
||||
initial_quote_reserve = excluded.initial_quote_reserve,
|
||||
initial_price_quote = excluded.initial_price_quote,
|
||||
updated_at = excluded.updated_at
|
||||
"#,
|
||||
)
|
||||
.bind(dto.dex_id)
|
||||
.bind(dto.pool_id)
|
||||
.bind(dto.pair_id)
|
||||
.bind(dto.source_kind.to_i16())
|
||||
.bind(dto.source_endpoint_name.clone())
|
||||
.bind(dto.detected_at.to_rfc3339())
|
||||
.bind(dto.initial_base_reserve)
|
||||
.bind(dto.initial_quote_reserve)
|
||||
.bind(dto.initial_price_quote)
|
||||
.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_listings on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
|
||||
r#"
|
||||
SELECT id
|
||||
FROM kb_pool_listings
|
||||
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_listings id for pool_id '{}' on sqlite: {}",
|
||||
dto.pool_id, error
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[tokio::test]
|
||||
async fn normalized_model_roundtrip_works() {
|
||||
let tempdir = tempfile::tempdir().expect("tempdir must succeed");
|
||||
let database_path = tempdir.path().join("normalized_model.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 = crate::KbDatabase::connect_and_initialize(&config)
|
||||
.await
|
||||
.expect("database init must succeed");
|
||||
let dex_id = crate::upsert_dex(
|
||||
&database,
|
||||
&crate::KbDexDto::new(
|
||||
"raydium".to_string(),
|
||||
"Raydium".to_string(),
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("dex upsert must succeed");
|
||||
let base_token_id = crate::upsert_token(
|
||||
&database,
|
||||
&crate::KbTokenDto::new(
|
||||
"So11111111111111111111111111111111111111112".to_string(),
|
||||
Some("WSOL".to_string()),
|
||||
Some("Wrapped SOL".to_string()),
|
||||
Some(9),
|
||||
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
|
||||
true,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("base token upsert must succeed");
|
||||
let quote_token_id = crate::upsert_token(
|
||||
&database,
|
||||
&crate::KbTokenDto::new(
|
||||
"DezX111111111111111111111111111111111111111".to_string(),
|
||||
Some("TEST".to_string()),
|
||||
Some("Test Token".to_string()),
|
||||
Some(6),
|
||||
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
|
||||
false,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("quote token upsert must succeed");
|
||||
let pool_id = crate::upsert_pool(
|
||||
&database,
|
||||
&crate::KbPoolDto::new(
|
||||
dex_id,
|
||||
"Pool111111111111111111111111111111111111111".to_string(),
|
||||
crate::KbPoolKind::Amm,
|
||||
crate::KbPoolStatus::Active,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("pool upsert must succeed");
|
||||
let pair_id = crate::upsert_pair(
|
||||
&database,
|
||||
&crate::KbPairDto::new(
|
||||
dex_id,
|
||||
pool_id,
|
||||
quote_token_id,
|
||||
base_token_id,
|
||||
Some("TEST/WSOL".to_string()),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("pair upsert must succeed");
|
||||
let _pool_token_a_id = crate::upsert_pool_token(
|
||||
&database,
|
||||
&crate::KbPoolTokenDto::new(
|
||||
pool_id,
|
||||
quote_token_id,
|
||||
crate::KbPoolTokenRole::Base,
|
||||
None,
|
||||
Some(0),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("pool token a upsert must succeed");
|
||||
let _pool_token_b_id = crate::upsert_pool_token(
|
||||
&database,
|
||||
&crate::KbPoolTokenDto::new(
|
||||
pool_id,
|
||||
base_token_id,
|
||||
crate::KbPoolTokenRole::Quote,
|
||||
None,
|
||||
Some(1),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("pool token b upsert must succeed");
|
||||
let listing_id = crate::upsert_pool_listing(
|
||||
&database,
|
||||
&crate::KbPoolListingDto::new(
|
||||
dex_id,
|
||||
pool_id,
|
||||
Some(pair_id),
|
||||
crate::KbObservationSourceKind::WsRpc,
|
||||
Some("mainnet_public_ws_slots".to_string()),
|
||||
Some(1000.0),
|
||||
Some(42.0),
|
||||
Some(0.042),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("pool listing upsert must succeed");
|
||||
assert!(listing_id > 0);
|
||||
let dexes = crate::list_dexes(&database)
|
||||
.await
|
||||
.expect("dex list must succeed");
|
||||
assert_eq!(dexes.len(), 1);
|
||||
let token =
|
||||
crate::get_token_by_mint(&database, "So11111111111111111111111111111111111111112")
|
||||
.await
|
||||
.expect("token get must succeed");
|
||||
assert!(token.is_some());
|
||||
}
|
||||
}
|
||||
67
kb_lib/src/db/queries/pool_token.rs
Normal file
67
kb_lib/src/db/queries/pool_token.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
// file: kb_lib/src/db/queries/pool_token.rs
|
||||
|
||||
//! Queries for `kb_pool_tokens`.
|
||||
|
||||
/// Inserts or updates one normalized pool token composition row.
|
||||
pub async fn upsert_pool_token(
|
||||
database: &crate::KbDatabase,
|
||||
dto: &crate::KbPoolTokenDto,
|
||||
) -> Result<i64, crate::KbError> {
|
||||
match database.connection() {
|
||||
crate::KbDatabaseConnection::Sqlite(pool) => {
|
||||
let query_result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO kb_pool_tokens (
|
||||
pool_id,
|
||||
token_id,
|
||||
role,
|
||||
vault_address,
|
||||
token_order,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(pool_id, token_id, role) DO UPDATE SET
|
||||
vault_address = excluded.vault_address,
|
||||
token_order = excluded.token_order,
|
||||
updated_at = excluded.updated_at
|
||||
"#,
|
||||
)
|
||||
.bind(dto.pool_id)
|
||||
.bind(dto.token_id)
|
||||
.bind(dto.role.to_i16())
|
||||
.bind(dto.vault_address.clone())
|
||||
.bind(dto.token_order)
|
||||
.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_tokens on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
|
||||
r#"
|
||||
SELECT id
|
||||
FROM kb_pool_tokens
|
||||
WHERE pool_id = ? AND token_id = ? AND role = ?
|
||||
LIMIT 1
|
||||
"#,
|
||||
)
|
||||
.bind(dto.pool_id)
|
||||
.bind(dto.token_id)
|
||||
.bind(dto.role.to_i16())
|
||||
.fetch_one(pool)
|
||||
.await;
|
||||
match id_result {
|
||||
Ok(id) => Ok(id),
|
||||
Err(error) => Err(crate::KbError::Db(format!(
|
||||
"cannot fetch kb_pool_tokens id on sqlite: {}",
|
||||
error
|
||||
))),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
121
kb_lib/src/db/queries/token.rs
Normal file
121
kb_lib/src/db/queries/token.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
// file: kb_lib/src/db/queries/token.rs
|
||||
|
||||
//! Queries for `kb_tokens`.
|
||||
|
||||
/// Inserts or updates one normalized token row by mint.
|
||||
pub async fn upsert_token(
|
||||
database: &crate::KbDatabase,
|
||||
dto: &crate::KbTokenDto,
|
||||
) -> Result<i64, crate::KbError> {
|
||||
match database.connection() {
|
||||
crate::KbDatabaseConnection::Sqlite(pool) => {
|
||||
let decimals = dto.decimals.map(i64::from);
|
||||
let query_result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO kb_tokens (
|
||||
mint,
|
||||
symbol,
|
||||
name,
|
||||
decimals,
|
||||
token_program,
|
||||
is_quote_token,
|
||||
first_seen_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(mint) DO UPDATE SET
|
||||
symbol = excluded.symbol,
|
||||
name = excluded.name,
|
||||
decimals = excluded.decimals,
|
||||
token_program = excluded.token_program,
|
||||
is_quote_token = excluded.is_quote_token,
|
||||
updated_at = excluded.updated_at
|
||||
"#,
|
||||
)
|
||||
.bind(dto.mint.clone())
|
||||
.bind(dto.symbol.clone())
|
||||
.bind(dto.name.clone())
|
||||
.bind(decimals)
|
||||
.bind(dto.token_program.clone())
|
||||
.bind(if dto.is_quote_token { 1_i64 } else { 0_i64 })
|
||||
.bind(dto.first_seen_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_tokens on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
|
||||
r#"
|
||||
SELECT id
|
||||
FROM kb_tokens
|
||||
WHERE mint = ?
|
||||
LIMIT 1
|
||||
"#,
|
||||
)
|
||||
.bind(dto.mint.clone())
|
||||
.fetch_one(pool)
|
||||
.await;
|
||||
match id_result {
|
||||
Ok(id) => Ok(id),
|
||||
Err(error) => Err(crate::KbError::Db(format!(
|
||||
"cannot fetch kb_tokens id for mint '{}' on sqlite: {}",
|
||||
dto.mint, error
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads one normalized token row by mint.
|
||||
pub async fn get_token_by_mint(
|
||||
database: &crate::KbDatabase,
|
||||
mint: &str,
|
||||
) -> Result<std::option::Option<crate::KbTokenDto>, crate::KbError> {
|
||||
match database.connection() {
|
||||
crate::KbDatabaseConnection::Sqlite(pool) => {
|
||||
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbTokenEntity>(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
mint,
|
||||
symbol,
|
||||
name,
|
||||
decimals,
|
||||
token_program,
|
||||
is_quote_token,
|
||||
first_seen_at,
|
||||
updated_at
|
||||
FROM kb_tokens
|
||||
WHERE mint = ?
|
||||
LIMIT 1
|
||||
"#,
|
||||
)
|
||||
.bind(mint)
|
||||
.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_tokens '{}' on sqlite: {}",
|
||||
mint, error
|
||||
)));
|
||||
}
|
||||
};
|
||||
match entity_option {
|
||||
Some(entity) => {
|
||||
let dto_result = crate::KbTokenDto::try_from(entity);
|
||||
match dto_result {
|
||||
Ok(dto) => Ok(Some(dto)),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
pub(crate) async fn ensure_schema(database: &crate::KbDatabase) -> Result<(), crate::KbError> {
|
||||
match database.connection() {
|
||||
crate::KbDatabaseConnection::Sqlite(pool) => {
|
||||
let metadata_table_result = sqlx::query(
|
||||
let statements = vec![
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS kb_db_metadata (
|
||||
key TEXT NOT NULL PRIMARY KEY,
|
||||
@@ -14,16 +14,6 @@ CREATE TABLE IF NOT EXISTS kb_db_metadata (
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = metadata_table_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create table kb_db_metadata on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let known_http_endpoints_result = sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS kb_known_http_endpoints (
|
||||
name TEXT NOT NULL PRIMARY KEY,
|
||||
@@ -35,16 +25,6 @@ CREATE TABLE IF NOT EXISTS kb_known_http_endpoints (
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = known_http_endpoints_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create table kb_known_http_endpoints on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let known_ws_endpoints_result = sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS kb_known_ws_endpoints (
|
||||
name TEXT NOT NULL PRIMARY KEY,
|
||||
@@ -56,16 +36,6 @@ CREATE TABLE IF NOT EXISTS kb_known_ws_endpoints (
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = known_ws_endpoints_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create table kb_known_ws_endpoints on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let runtime_events_result = sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS kb_db_runtime_events (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -76,30 +46,10 @@ CREATE TABLE IF NOT EXISTS kb_db_runtime_events (
|
||||
created_at TEXT NOT NULL
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = runtime_events_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create table kb_db_runtime_events on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let runtime_events_index_result = sqlx::query(
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS kb_idx_db_runtime_events_created_at
|
||||
ON kb_db_runtime_events (created_at)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = runtime_events_index_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create index kb_idx_db_runtime_events_created_at on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let observed_tokens_result = sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS kb_observed_tokens (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -114,44 +64,14 @@ CREATE TABLE IF NOT EXISTS kb_observed_tokens (
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = observed_tokens_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create table kb_observed_tokens on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let observed_tokens_mint_index_result = sqlx::query(
|
||||
r#"
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS kb_idx_observed_tokens_mint
|
||||
ON kb_observed_tokens (mint)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = observed_tokens_mint_index_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create index kb_idx_observed_tokens_mint on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let observed_tokens_status_index_result = sqlx::query(
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS kb_idx_observed_tokens_status
|
||||
ON kb_observed_tokens (status)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = observed_tokens_status_index_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create index kb_idx_observed_tokens_status on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let onchain_observations_result = sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS kb_onchain_observations (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -164,44 +84,14 @@ CREATE TABLE IF NOT EXISTS kb_onchain_observations (
|
||||
observed_at TEXT NOT NULL
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = onchain_observations_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create table kb_onchain_observations on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let onchain_observations_object_key_index_result = sqlx::query(
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS kb_idx_onchain_observations_object_key
|
||||
ON kb_onchain_observations (object_key)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = onchain_observations_object_key_index_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create index kb_idx_onchain_observations_object_key on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let onchain_observations_observed_at_index_result = sqlx::query(
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS kb_idx_onchain_observations_observed_at
|
||||
ON kb_onchain_observations (observed_at)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = onchain_observations_observed_at_index_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create index kb_idx_onchain_observations_observed_at on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let analysis_signals_result = sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS kb_analysis_signals (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -215,43 +105,118 @@ CREATE TABLE IF NOT EXISTS kb_analysis_signals (
|
||||
FOREIGN KEY(related_observation_id) REFERENCES kb_onchain_observations(id)
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = analysis_signals_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create table kb_analysis_signals on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let analysis_signals_object_key_index_result = sqlx::query(
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS kb_idx_analysis_signals_object_key
|
||||
ON kb_analysis_signals (object_key)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = analysis_signals_object_key_index_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create index kb_idx_analysis_signals_object_key on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
}
|
||||
let analysis_signals_created_at_index_result = sqlx::query(
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS kb_idx_analysis_signals_created_at
|
||||
ON kb_analysis_signals (created_at)
|
||||
"#,
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS kb_dexes (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
program_id TEXT NULL,
|
||||
router_program_id TEXT NULL,
|
||||
is_enabled INTEGER NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
"#,
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS kb_tokens (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
mint TEXT NOT NULL UNIQUE,
|
||||
symbol TEXT NULL,
|
||||
name TEXT NULL,
|
||||
decimals INTEGER NULL,
|
||||
token_program TEXT NOT NULL,
|
||||
is_quote_token INTEGER NOT NULL,
|
||||
first_seen_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
"#,
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS kb_pools (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
dex_id INTEGER NOT NULL,
|
||||
address TEXT NOT NULL UNIQUE,
|
||||
pool_kind INTEGER NOT NULL,
|
||||
status INTEGER NOT NULL,
|
||||
first_seen_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
FOREIGN KEY(dex_id) REFERENCES kb_dexes(id)
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
if let Err(error) = analysis_signals_created_at_index_result {
|
||||
"#,
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS kb_idx_pools_dex_id
|
||||
ON kb_pools (dex_id)
|
||||
"#,
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS kb_pairs (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
dex_id INTEGER NOT NULL,
|
||||
pool_id INTEGER NOT NULL UNIQUE,
|
||||
base_token_id INTEGER NOT NULL,
|
||||
quote_token_id INTEGER NOT NULL,
|
||||
symbol TEXT NULL,
|
||||
first_seen_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
FOREIGN KEY(dex_id) REFERENCES kb_dexes(id),
|
||||
FOREIGN KEY(pool_id) REFERENCES kb_pools(id),
|
||||
FOREIGN KEY(base_token_id) REFERENCES kb_tokens(id),
|
||||
FOREIGN KEY(quote_token_id) REFERENCES kb_tokens(id)
|
||||
)
|
||||
"#,
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS kb_pool_tokens (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
pool_id INTEGER NOT NULL,
|
||||
token_id INTEGER NOT NULL,
|
||||
role INTEGER NOT NULL,
|
||||
vault_address TEXT NULL,
|
||||
token_order INTEGER NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
FOREIGN KEY(pool_id) REFERENCES kb_pools(id),
|
||||
FOREIGN KEY(token_id) REFERENCES kb_tokens(id),
|
||||
UNIQUE(pool_id, token_id, role)
|
||||
)
|
||||
"#,
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS kb_pool_listings (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
dex_id INTEGER NOT NULL,
|
||||
pool_id INTEGER NOT NULL UNIQUE,
|
||||
pair_id INTEGER NULL,
|
||||
source_kind INTEGER NOT NULL,
|
||||
source_endpoint_name TEXT NULL,
|
||||
detected_at TEXT NOT NULL,
|
||||
initial_base_reserve REAL NULL,
|
||||
initial_quote_reserve REAL NULL,
|
||||
initial_price_quote REAL NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
FOREIGN KEY(dex_id) REFERENCES kb_dexes(id),
|
||||
FOREIGN KEY(pool_id) REFERENCES kb_pools(id),
|
||||
FOREIGN KEY(pair_id) REFERENCES kb_pairs(id)
|
||||
)
|
||||
"#,
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS kb_idx_pool_listings_detected_at
|
||||
ON kb_pool_listings (detected_at)
|
||||
"#,
|
||||
];
|
||||
for statement in statements {
|
||||
let execute_result = sqlx::query(statement).execute(pool).await;
|
||||
if let Err(error) = execute_result {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
"cannot create index kb_idx_analysis_signals_created_at on sqlite: {}",
|
||||
error
|
||||
"cannot initialize sqlite schema statement '{}': {}",
|
||||
statement, error
|
||||
)));
|
||||
}
|
||||
}
|
||||
let schema_version = crate::KbDbMetadataDto::new(
|
||||
"schema_version".to_string(),
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
|
||||
@@ -6,10 +6,16 @@ mod analysis_signal_severity;
|
||||
mod database_backend;
|
||||
mod observation_source_kind;
|
||||
mod observed_token_status;
|
||||
mod pool_kind;
|
||||
mod pool_status;
|
||||
mod pool_token_role;
|
||||
mod runtime_event_level;
|
||||
|
||||
pub use crate::db::types::analysis_signal_severity::KbAnalysisSignalSeverity;
|
||||
pub use crate::db::types::database_backend::KbDatabaseBackend;
|
||||
pub use crate::db::types::observation_source_kind::KbObservationSourceKind;
|
||||
pub use crate::db::types::observed_token_status::KbObservedTokenStatus;
|
||||
pub use crate::db::types::pool_kind::KbPoolKind;
|
||||
pub use crate::db::types::pool_status::KbPoolStatus;
|
||||
pub use crate::db::types::pool_token_role::KbPoolTokenRole;
|
||||
pub use crate::db::types::runtime_event_level::KbDbRuntimeEventLevel;
|
||||
|
||||
49
kb_lib/src/db/types/pool_kind.rs
Normal file
49
kb_lib/src/db/types/pool_kind.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
// file: kb_lib/src/db/types/pool_kind.rs
|
||||
|
||||
//! Pool kind.
|
||||
|
||||
/// Normalized pool kind.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum KbPoolKind {
|
||||
/// Unknown pool kind.
|
||||
Unknown,
|
||||
/// Constant-product AMM.
|
||||
Amm,
|
||||
/// Concentrated liquidity market maker.
|
||||
Clmm,
|
||||
/// Bonding curve pool.
|
||||
BondingCurve,
|
||||
/// Order book market.
|
||||
OrderBook,
|
||||
}
|
||||
|
||||
impl KbPoolKind {
|
||||
/// Converts the kind to its stable integer representation.
|
||||
pub fn to_i16(self) -> i16 {
|
||||
match self {
|
||||
Self::Unknown => 0,
|
||||
Self::Amm => 1,
|
||||
Self::Clmm => 2,
|
||||
Self::BondingCurve => 3,
|
||||
Self::OrderBook => 4,
|
||||
}
|
||||
}
|
||||
|
||||
/// Restores a kind from its stable integer representation.
|
||||
pub fn from_i16(
|
||||
value: i16,
|
||||
) -> Result<Self, crate::KbError> {
|
||||
match value {
|
||||
0 => Ok(Self::Unknown),
|
||||
1 => Ok(Self::Amm),
|
||||
2 => Ok(Self::Clmm),
|
||||
3 => Ok(Self::BondingCurve),
|
||||
4 => Ok(Self::OrderBook),
|
||||
_ => Err(crate::KbError::Db(format!(
|
||||
"invalid KbPoolKind value: {}",
|
||||
value
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
46
kb_lib/src/db/types/pool_status.rs
Normal file
46
kb_lib/src/db/types/pool_status.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
// file: kb_lib/src/db/types/pool_status.rs
|
||||
|
||||
//! Pool status.
|
||||
|
||||
/// Normalized pool status.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum KbPoolStatus {
|
||||
/// Unknown status.
|
||||
Unknown,
|
||||
/// Pool detected but not yet fully active.
|
||||
Pending,
|
||||
/// Pool active.
|
||||
Active,
|
||||
/// Pool inactive.
|
||||
Inactive,
|
||||
/// Pool closed.
|
||||
Closed,
|
||||
}
|
||||
|
||||
impl KbPoolStatus {
|
||||
/// Converts the status to its stable integer representation.
|
||||
pub fn to_i16(self) -> i16 {
|
||||
match self {
|
||||
Self::Unknown => 0,
|
||||
Self::Pending => 1,
|
||||
Self::Active => 2,
|
||||
Self::Inactive => 3,
|
||||
Self::Closed => 4,
|
||||
}
|
||||
}
|
||||
|
||||
/// Restores a status from its stable integer representation.
|
||||
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
|
||||
match value {
|
||||
0 => Ok(Self::Unknown),
|
||||
1 => Ok(Self::Pending),
|
||||
2 => Ok(Self::Active),
|
||||
3 => Ok(Self::Inactive),
|
||||
4 => Ok(Self::Closed),
|
||||
_ => Err(crate::KbError::Db(format!(
|
||||
"invalid KbPoolStatus value: {}",
|
||||
value
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
46
kb_lib/src/db/types/pool_token_role.rs
Normal file
46
kb_lib/src/db/types/pool_token_role.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
// file: kb_lib/src/db/types/pool_token_role.rs
|
||||
|
||||
//! Pool token role.
|
||||
|
||||
/// Role of one token inside a normalized pool.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum KbPoolTokenRole {
|
||||
/// Base token.
|
||||
Base,
|
||||
/// Quote token.
|
||||
Quote,
|
||||
/// LP mint token.
|
||||
LpMint,
|
||||
/// Reserve token or reserve-side tracked token.
|
||||
Reserve,
|
||||
/// Other role.
|
||||
Other,
|
||||
}
|
||||
|
||||
impl KbPoolTokenRole {
|
||||
/// Converts the role to its stable integer representation.
|
||||
pub fn to_i16(self) -> i16 {
|
||||
match self {
|
||||
Self::Base => 0,
|
||||
Self::Quote => 1,
|
||||
Self::LpMint => 2,
|
||||
Self::Reserve => 3,
|
||||
Self::Other => 4,
|
||||
}
|
||||
}
|
||||
|
||||
/// Restores a role from its stable integer representation.
|
||||
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
|
||||
match value {
|
||||
0 => Ok(Self::Base),
|
||||
1 => Ok(Self::Quote),
|
||||
2 => Ok(Self::LpMint),
|
||||
3 => Ok(Self::Reserve),
|
||||
4 => Ok(Self::Other),
|
||||
_ => Err(crate::KbError::Db(format!(
|
||||
"invalid KbPoolTokenRole value: {}",
|
||||
value
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,29 @@ pub use crate::db::KbAnalysisSignalSeverity;
|
||||
pub use crate::db::KbObservationSourceKind;
|
||||
pub use crate::db::KbOnchainObservationDto;
|
||||
pub use crate::db::KbOnchainObservationEntity;
|
||||
pub use crate::db::KbDexDto;
|
||||
pub use crate::db::KbDexEntity;
|
||||
pub use crate::db::KbPairDto;
|
||||
pub use crate::db::KbPairEntity;
|
||||
pub use crate::db::KbPoolDto;
|
||||
pub use crate::db::KbPoolEntity;
|
||||
pub use crate::db::KbPoolKind;
|
||||
pub use crate::db::KbPoolListingDto;
|
||||
pub use crate::db::KbPoolListingEntity;
|
||||
pub use crate::db::KbPoolStatus;
|
||||
pub use crate::db::KbPoolTokenDto;
|
||||
pub use crate::db::KbPoolTokenEntity;
|
||||
pub use crate::db::KbPoolTokenRole;
|
||||
pub use crate::db::KbTokenDto;
|
||||
pub use crate::db::KbTokenEntity;
|
||||
pub use crate::db::get_token_by_mint;
|
||||
pub use crate::db::list_dexes;
|
||||
pub use crate::db::upsert_dex;
|
||||
pub use crate::db::upsert_pair;
|
||||
pub use crate::db::upsert_pool;
|
||||
pub use crate::db::upsert_pool_listing;
|
||||
pub use crate::db::upsert_pool_token;
|
||||
pub use crate::db::upsert_token;
|
||||
pub use crate::db::insert_analysis_signal;
|
||||
pub use crate::db::insert_onchain_observation;
|
||||
pub use crate::db::list_recent_analysis_signals;
|
||||
@@ -102,4 +125,3 @@ pub use crate::db::list_known_ws_endpoints;
|
||||
pub use crate::db::list_recent_db_runtime_events;
|
||||
pub use crate::db::upsert_known_http_endpoint;
|
||||
pub use crate::db::upsert_known_ws_endpoint;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user