0.6.0
This commit is contained in:
@@ -21,6 +21,6 @@
|
||||
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
|
||||
0.5.5 - Ajout des événements métier normalisés pour les swaps, liquidités, mints et burns de tokens
|
||||
0.5.6 - Consolidation de la couche stockage : activation des foreign keys SQLite, lectures ciblées sur le modèle métier normalisé, index supplémentaires et tests unitaires dédiés
|
||||
|
||||
0.5.5 - Ajout des événements métier normalisés pour les swaps, liquidités, mints et burns de tokens
|
||||
0.5.6 - Consolidation de la couche stockage : activation des foreign keys SQLite, lectures ciblées sur le modèle métier normalisé, index supplémentaires et tests unitaires dédiés
|
||||
0.6.0 - Ajout du pipeline de détection technique : façade de persistance pour observations on-chain, signaux d’analyse et candidats tokens depuis les connecteurs RPC
|
||||
|
||||
@@ -8,7 +8,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.5.6"
|
||||
version = "0.6.0"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
|
||||
|
||||
44
ROADMAP.md
44
ROADMAP.md
@@ -383,17 +383,27 @@ Objectif : stabiliser le schéma avant la détection technique réelle.
|
||||
- durcir les relations, contraintes et index utiles,
|
||||
- préparer une future compatibilité PostgreSQL sans casser l’organisation générale.
|
||||
|
||||
### 6.25. Version `0.6.x` — Détection technique on-chain / RPC
|
||||
Objectif : commencer la détection utile pour l’application.
|
||||
### 6.25. Version `0.6.0` — Pipeline de détection technique
|
||||
Objectif : relier les connecteurs RPC à la couche de stockage technique et métier.
|
||||
|
||||
À faire :
|
||||
|
||||
- réception de notifications ciblées,
|
||||
- détection de créations de comptes/programmes d’intérêt,
|
||||
- débuts de normalisation d’événements,
|
||||
- premiers connecteurs DEX.
|
||||
- ajouter une façade de persistance pour les observations et signaux issus des connecteurs,
|
||||
- préparer l’enregistrement des candidats tokens détectés depuis les sources RPC,
|
||||
- éviter que les futurs watchers RPC écrivent directement dans la DB sans couche intermédiaire,
|
||||
- préparer les prochaines étapes de détection technique on-chain / RPC.
|
||||
|
||||
### 6.26. Version `0.7.x` — DEX connectors v1
|
||||
### 6.26. Version `0.6.1` — Détection technique RPC
|
||||
Objectif : brancher les premiers watchers et règles techniques sur la façade de détection.
|
||||
|
||||
À faire :
|
||||
|
||||
- relier les notifications WS / RPC au pipeline de détection,
|
||||
- produire des observations on-chain normalisées,
|
||||
- générer les premiers signaux techniques exploitables,
|
||||
- préparer la découverte effective des tokens et pools avant les connecteurs DEX dédiés.
|
||||
|
||||
### 6.27. Version `0.7.x` — DEX connectors v1
|
||||
Objectif : structurer les connecteurs par protocole.
|
||||
|
||||
Cibles initiales possibles :
|
||||
@@ -412,7 +422,7 @@ Cibles initiales possibles :
|
||||
- création de types métiers propres,
|
||||
- enrichissement des métadonnées token/pool/pair.
|
||||
|
||||
### 6.27. Version `0.8.x` — Analyse et filtrage
|
||||
### 6.28. Version `0.8.x` — Analyse et filtrage
|
||||
Objectif : transformer les événements bruts en signaux exploitables.
|
||||
|
||||
À faire :
|
||||
@@ -423,7 +433,7 @@ Objectif : transformer les événements bruts en signaux exploitables.
|
||||
- statistiques de comportement,
|
||||
- premiers patterns.
|
||||
|
||||
### 6.28. Version `1.x.y` — Wallets et swap préparatoire
|
||||
### 6.29. Version `1.x.y` — Wallets et swap préparatoire
|
||||
Objectif : préparer la couche d’action.
|
||||
|
||||
À faire :
|
||||
@@ -434,7 +444,7 @@ Objectif : préparer la couche d’action.
|
||||
- préparation d’ordres et de swaps,
|
||||
- simulation et garde-fous.
|
||||
|
||||
### 6.29. Version `2.x.y` — Trading semi-automatisé
|
||||
### 6.30. Version `2.x.y` — Trading semi-automatisé
|
||||
Objectif : brancher l’analyse à l’action tout en gardant des garde-fous explicites.
|
||||
|
||||
À faire :
|
||||
@@ -445,7 +455,7 @@ Objectif : brancher l’analyse à l’action tout en gardant des garde-fous exp
|
||||
- confirmations explicites ou semi-automatiques,
|
||||
- journaux d’exécution.
|
||||
|
||||
### 6.30. Version `3.x.y` — Yellowstone gRPC
|
||||
### 6.31. Version `3.x.y` — Yellowstone gRPC
|
||||
Objectif : ajouter le connecteur gRPC dédié.
|
||||
|
||||
À faire :
|
||||
@@ -530,9 +540,9 @@ Le projet doit maintenir au minimum :
|
||||
## 12. Priorité immédiate
|
||||
La priorité immédiate est désormais la suivante :
|
||||
|
||||
1. démarrer la version `0.5.4` avec le modèle métier normalisé initial,
|
||||
2. poser les tables de référence pour les DEX, tokens, pools, paires et listings,
|
||||
3. séparer clairement les objets métier des observations techniques brutes,
|
||||
4. préparer les relations nécessaires avant la détection technique `0.6.x`,
|
||||
5. conserver l’abstraction du backend dès cette phase SQLite,
|
||||
6. reporter la couche analytique agrégée après la fin de `0.6.x`.
|
||||
1. démarrer la version `0.6.0` avec le pipeline de détection technique,
|
||||
2. ajouter une façade unique entre les connecteurs RPC et la base de données,
|
||||
3. préparer l’enregistrement des observations on-chain, des signaux et des candidats tokens,
|
||||
4. éviter que les watchers futurs accèdent directement à la DB sans couche intermédiaire,
|
||||
5. conserver l’abstraction du backend et la séparation entre stockage brut et modèle métier,
|
||||
6. préparer ensuite la version `0.6.1` pour brancher les premières règles de détection technique RPC.
|
||||
|
||||
16
kb_lib/src/detect.rs
Normal file
16
kb_lib/src/detect.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
// file: kb_lib/src/detect.rs
|
||||
|
||||
//! Detection pipeline facade.
|
||||
//!
|
||||
//! This module sits between transport/connectors and persistence.
|
||||
//! It centralizes how technical observations, analysis signals and
|
||||
//! candidate tokens are persisted before richer detection logic is added.
|
||||
|
||||
mod service;
|
||||
mod types;
|
||||
|
||||
pub use crate::detect::service::KbDetectionPersistenceService;
|
||||
pub use crate::detect::types::KbDetectionObservationInput;
|
||||
pub use crate::detect::types::KbDetectionSignalInput;
|
||||
pub use crate::detect::types::KbDetectionTokenCandidateInput;
|
||||
pub use crate::detect::types::KbDetectionTokenCandidateResult;
|
||||
250
kb_lib/src/detect/service.rs
Normal file
250
kb_lib/src/detect/service.rs
Normal file
@@ -0,0 +1,250 @@
|
||||
// file: kb_lib/src/detect/service.rs
|
||||
|
||||
//! Detection persistence service.
|
||||
|
||||
/// Persistence façade between technical detection and database storage.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KbDetectionPersistenceService {
|
||||
/// Shared database handle.
|
||||
database: std::sync::Arc<crate::KbDatabase>,
|
||||
}
|
||||
|
||||
impl KbDetectionPersistenceService {
|
||||
/// Creates a new detection persistence service.
|
||||
pub fn new(database: std::sync::Arc<crate::KbDatabase>) -> Self {
|
||||
Self { database }
|
||||
}
|
||||
|
||||
/// Returns the shared database handle.
|
||||
pub fn database(&self) -> &std::sync::Arc<crate::KbDatabase> {
|
||||
&self.database
|
||||
}
|
||||
|
||||
/// Persists one on-chain observation.
|
||||
pub async fn record_observation(
|
||||
&self,
|
||||
input: &crate::KbDetectionObservationInput,
|
||||
) -> Result<i64, crate::KbError> {
|
||||
let dto = crate::KbOnchainObservationDto::new(
|
||||
input.observation_kind.clone(),
|
||||
input.source_kind,
|
||||
input.endpoint_name.clone(),
|
||||
input.object_key.clone(),
|
||||
input.slot,
|
||||
input.payload.clone(),
|
||||
);
|
||||
crate::insert_onchain_observation(self.database.as_ref(), &dto).await
|
||||
}
|
||||
|
||||
/// Persists one analysis signal.
|
||||
pub async fn record_signal(
|
||||
&self,
|
||||
input: &crate::KbDetectionSignalInput,
|
||||
) -> Result<i64, crate::KbError> {
|
||||
let dto = crate::KbAnalysisSignalDto::new(
|
||||
input.signal_kind.clone(),
|
||||
input.severity,
|
||||
input.object_key.clone(),
|
||||
input.related_observation_id,
|
||||
input.score,
|
||||
input.payload.clone(),
|
||||
);
|
||||
crate::insert_analysis_signal(self.database.as_ref(), &dto).await
|
||||
}
|
||||
|
||||
/// Registers one token candidate from a technical source.
|
||||
///
|
||||
/// This method:
|
||||
/// - upserts the normalized token entry,
|
||||
/// - stores the underlying technical observation,
|
||||
/// - stores the derived signal linked to that observation.
|
||||
pub async fn register_token_candidate(
|
||||
&self,
|
||||
input: &crate::KbDetectionTokenCandidateInput,
|
||||
) -> Result<crate::KbDetectionTokenCandidateResult, crate::KbError> {
|
||||
let token_dto = crate::KbTokenDto::new(
|
||||
input.mint.clone(),
|
||||
input.symbol.clone(),
|
||||
input.name.clone(),
|
||||
input.decimals,
|
||||
input.token_program.clone(),
|
||||
input.is_quote_token,
|
||||
);
|
||||
let token_id_result = crate::upsert_token(self.database.as_ref(), &token_dto).await;
|
||||
let token_id = match token_id_result {
|
||||
Ok(token_id) => token_id,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let observation_input = crate::KbDetectionObservationInput::new(
|
||||
input.observation_kind.clone(),
|
||||
input.source_kind,
|
||||
input.endpoint_name.clone(),
|
||||
input.mint.clone(),
|
||||
input.slot,
|
||||
input.observation_payload.clone(),
|
||||
);
|
||||
let observation_id_result = self.record_observation(&observation_input).await;
|
||||
let observation_id = match observation_id_result {
|
||||
Ok(observation_id) => observation_id,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let signal_payload = match &input.signal_payload {
|
||||
Some(signal_payload) => signal_payload.clone(),
|
||||
None => input.observation_payload.clone(),
|
||||
};
|
||||
let signal_input = crate::KbDetectionSignalInput::new(
|
||||
input.signal_kind.clone(),
|
||||
input.signal_severity,
|
||||
input.mint.clone(),
|
||||
Some(observation_id),
|
||||
input.signal_score,
|
||||
signal_payload,
|
||||
);
|
||||
let signal_id_result = self.record_signal(&signal_input).await;
|
||||
let signal_id = match signal_id_result {
|
||||
Ok(signal_id) => signal_id,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
Ok(crate::KbDetectionTokenCandidateResult {
|
||||
token_id,
|
||||
observation_id,
|
||||
signal_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
async fn create_database() -> crate::KbDatabase {
|
||||
let tempdir = tempfile::tempdir().expect("tempdir must succeed");
|
||||
let database_path = tempdir.path().join("detect_pipeline.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,
|
||||
},
|
||||
};
|
||||
crate::KbDatabase::connect_and_initialize(&config)
|
||||
.await
|
||||
.expect("database init must succeed")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn record_observation_and_signal_work() {
|
||||
let database = create_database().await;
|
||||
let service = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let observation_id = service
|
||||
.record_observation(&crate::KbDetectionObservationInput::new(
|
||||
"rpc.program_notification".to_string(),
|
||||
crate::KbObservationSourceKind::WsRpc,
|
||||
Some("mainnet_public_ws_slots".to_string()),
|
||||
"So11111111111111111111111111111111111111112".to_string(),
|
||||
Some(123456),
|
||||
serde_json::json!({
|
||||
"mint": "So11111111111111111111111111111111111111112"
|
||||
}),
|
||||
))
|
||||
.await
|
||||
.expect("record observation must succeed");
|
||||
assert!(observation_id > 0);
|
||||
let signal_id = service
|
||||
.record_signal(&crate::KbDetectionSignalInput::new(
|
||||
"rpc.token_candidate".to_string(),
|
||||
crate::KbAnalysisSignalSeverity::Low,
|
||||
"So11111111111111111111111111111111111111112".to_string(),
|
||||
Some(observation_id),
|
||||
Some(0.25),
|
||||
serde_json::json!({
|
||||
"reason": "test"
|
||||
}),
|
||||
))
|
||||
.await
|
||||
.expect("record signal must succeed");
|
||||
assert!(signal_id > 0);
|
||||
let observations = crate::list_recent_onchain_observations(service.database().as_ref(), 10)
|
||||
.await
|
||||
.expect("list observations must succeed");
|
||||
let signals = crate::list_recent_analysis_signals(service.database().as_ref(), 10)
|
||||
.await
|
||||
.expect("list signals must succeed");
|
||||
assert_eq!(observations.len(), 1);
|
||||
assert_eq!(signals.len(), 1);
|
||||
assert_eq!(
|
||||
observations[0].object_key,
|
||||
"So11111111111111111111111111111111111111112"
|
||||
);
|
||||
assert_eq!(
|
||||
signals[0].object_key,
|
||||
"So11111111111111111111111111111111111111112"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn register_token_candidate_persists_token_observation_and_signal() {
|
||||
let database = create_database().await;
|
||||
let service = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let result = service
|
||||
.register_token_candidate(&crate::KbDetectionTokenCandidateInput::new(
|
||||
"Mint111111111111111111111111111111111111111".to_string(),
|
||||
Some("TEST".to_string()),
|
||||
Some("Test Token".to_string()),
|
||||
Some(6),
|
||||
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
|
||||
false,
|
||||
crate::KbObservationSourceKind::WsRpc,
|
||||
Some("helius_primary_ws_programs".to_string()),
|
||||
Some(777777),
|
||||
"rpc.token_candidate".to_string(),
|
||||
serde_json::json!({
|
||||
"mint": "Mint111111111111111111111111111111111111111",
|
||||
"source": "ws"
|
||||
}),
|
||||
"signal.token_candidate".to_string(),
|
||||
crate::KbAnalysisSignalSeverity::Medium,
|
||||
Some(0.70),
|
||||
None,
|
||||
))
|
||||
.await
|
||||
.expect("register token candidate must succeed");
|
||||
assert!(result.token_id > 0);
|
||||
assert!(result.observation_id > 0);
|
||||
assert!(result.signal_id > 0);
|
||||
let token = crate::get_token_by_mint(
|
||||
service.database().as_ref(),
|
||||
"Mint111111111111111111111111111111111111111",
|
||||
)
|
||||
.await
|
||||
.expect("get token must succeed");
|
||||
assert!(token.is_some());
|
||||
assert_eq!(
|
||||
token.expect("token must exist").symbol.as_deref(),
|
||||
Some("TEST")
|
||||
);
|
||||
let observations = crate::list_recent_onchain_observations(service.database().as_ref(), 10)
|
||||
.await
|
||||
.expect("list observations must succeed");
|
||||
let signals = crate::list_recent_analysis_signals(service.database().as_ref(), 10)
|
||||
.await
|
||||
.expect("list signals must succeed");
|
||||
assert_eq!(observations.len(), 1);
|
||||
assert_eq!(signals.len(), 1);
|
||||
assert_eq!(
|
||||
observations[0].object_key,
|
||||
"Mint111111111111111111111111111111111111111"
|
||||
);
|
||||
assert_eq!(
|
||||
signals[0].object_key,
|
||||
"Mint111111111111111111111111111111111111111"
|
||||
);
|
||||
assert_eq!(
|
||||
signals[0].related_observation_id,
|
||||
Some(result.observation_id)
|
||||
);
|
||||
}
|
||||
}
|
||||
164
kb_lib/src/detect/types.rs
Normal file
164
kb_lib/src/detect/types.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
// file: kb_lib/src/detect/types.rs
|
||||
|
||||
//! Detection pipeline input and output types.
|
||||
|
||||
/// One normalized observation ready to be persisted.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbDetectionObservationInput {
|
||||
/// Observation kind.
|
||||
pub observation_kind: std::string::String,
|
||||
/// Observation source family.
|
||||
pub source_kind: crate::KbObservationSourceKind,
|
||||
/// Optional logical source endpoint name.
|
||||
pub endpoint_name: std::option::Option<std::string::String>,
|
||||
/// Logical object key, for example a mint, signature or pool address.
|
||||
pub object_key: std::string::String,
|
||||
/// Optional slot number.
|
||||
pub slot: std::option::Option<u64>,
|
||||
/// JSON payload.
|
||||
pub payload: serde_json::Value,
|
||||
}
|
||||
|
||||
impl KbDetectionObservationInput {
|
||||
/// Creates a new detection observation input.
|
||||
pub fn new(
|
||||
observation_kind: std::string::String,
|
||||
source_kind: crate::KbObservationSourceKind,
|
||||
endpoint_name: std::option::Option<std::string::String>,
|
||||
object_key: std::string::String,
|
||||
slot: std::option::Option<u64>,
|
||||
payload: serde_json::Value,
|
||||
) -> Self {
|
||||
Self {
|
||||
observation_kind,
|
||||
source_kind,
|
||||
endpoint_name,
|
||||
object_key,
|
||||
slot,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One normalized signal ready to be persisted.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbDetectionSignalInput {
|
||||
/// Signal kind.
|
||||
pub signal_kind: std::string::String,
|
||||
/// Signal severity.
|
||||
pub severity: crate::KbAnalysisSignalSeverity,
|
||||
/// Logical object key, for example a mint, signature or pool address.
|
||||
pub object_key: std::string::String,
|
||||
/// Optional related observation id.
|
||||
pub related_observation_id: std::option::Option<i64>,
|
||||
/// Optional score.
|
||||
pub score: std::option::Option<f64>,
|
||||
/// JSON payload.
|
||||
pub payload: serde_json::Value,
|
||||
}
|
||||
|
||||
impl KbDetectionSignalInput {
|
||||
/// Creates a new detection signal input.
|
||||
pub fn new(
|
||||
signal_kind: std::string::String,
|
||||
severity: crate::KbAnalysisSignalSeverity,
|
||||
object_key: std::string::String,
|
||||
related_observation_id: std::option::Option<i64>,
|
||||
score: std::option::Option<f64>,
|
||||
payload: serde_json::Value,
|
||||
) -> Self {
|
||||
Self {
|
||||
signal_kind,
|
||||
severity,
|
||||
object_key,
|
||||
related_observation_id,
|
||||
score,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One token candidate detected from a technical source.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbDetectionTokenCandidateInput {
|
||||
/// Token mint address.
|
||||
pub mint: std::string::String,
|
||||
/// Optional token symbol.
|
||||
pub symbol: std::option::Option<std::string::String>,
|
||||
/// Optional token name.
|
||||
pub name: std::option::Option<std::string::String>,
|
||||
/// Optional decimals.
|
||||
pub decimals: std::option::Option<u8>,
|
||||
/// Token program id.
|
||||
pub token_program: std::string::String,
|
||||
/// Whether the token is typically used as quote token.
|
||||
pub is_quote_token: bool,
|
||||
/// Observation source family.
|
||||
pub source_kind: crate::KbObservationSourceKind,
|
||||
/// Optional source endpoint logical name.
|
||||
pub endpoint_name: std::option::Option<std::string::String>,
|
||||
/// Optional slot number.
|
||||
pub slot: std::option::Option<u64>,
|
||||
/// Observation kind.
|
||||
pub observation_kind: std::string::String,
|
||||
/// Observation payload.
|
||||
pub observation_payload: serde_json::Value,
|
||||
/// Signal kind.
|
||||
pub signal_kind: std::string::String,
|
||||
/// Signal severity.
|
||||
pub signal_severity: crate::KbAnalysisSignalSeverity,
|
||||
/// Optional signal score.
|
||||
pub signal_score: std::option::Option<f64>,
|
||||
/// Optional dedicated signal payload.
|
||||
pub signal_payload: std::option::Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl KbDetectionTokenCandidateInput {
|
||||
/// Creates a new token candidate input.
|
||||
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,
|
||||
source_kind: crate::KbObservationSourceKind,
|
||||
endpoint_name: std::option::Option<std::string::String>,
|
||||
slot: std::option::Option<u64>,
|
||||
observation_kind: std::string::String,
|
||||
observation_payload: serde_json::Value,
|
||||
signal_kind: std::string::String,
|
||||
signal_severity: crate::KbAnalysisSignalSeverity,
|
||||
signal_score: std::option::Option<f64>,
|
||||
signal_payload: std::option::Option<serde_json::Value>,
|
||||
) -> Self {
|
||||
Self {
|
||||
mint,
|
||||
symbol,
|
||||
name,
|
||||
decimals,
|
||||
token_program,
|
||||
is_quote_token,
|
||||
source_kind,
|
||||
endpoint_name,
|
||||
slot,
|
||||
observation_kind,
|
||||
observation_payload,
|
||||
signal_kind,
|
||||
signal_severity,
|
||||
signal_score,
|
||||
signal_payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of one token candidate persistence operation.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbDetectionTokenCandidateResult {
|
||||
/// Persisted token id.
|
||||
pub token_id: i64,
|
||||
/// Persisted observation id.
|
||||
pub observation_id: i64,
|
||||
/// Persisted signal id.
|
||||
pub signal_id: i64,
|
||||
}
|
||||
@@ -19,6 +19,7 @@ mod ws_client;
|
||||
mod rpc_ws_solana;
|
||||
mod http_pool;
|
||||
mod db;
|
||||
mod detect;
|
||||
|
||||
pub use crate::config::KbAppConfig;
|
||||
pub use crate::config::KbConfig;
|
||||
@@ -151,3 +152,8 @@ pub use crate::db::list_pairs;
|
||||
pub use crate::db::list_pool_listings;
|
||||
pub use crate::db::list_pool_tokens_by_pool_id;
|
||||
pub use crate::db::list_pools;
|
||||
pub use crate::detect::KbDetectionObservationInput;
|
||||
pub use crate::detect::KbDetectionPersistenceService;
|
||||
pub use crate::detect::KbDetectionSignalInput;
|
||||
pub use crate::detect::KbDetectionTokenCandidateInput;
|
||||
pub use crate::detect::KbDetectionTokenCandidateResult;
|
||||
|
||||
Reference in New Issue
Block a user