0.6.3
This commit is contained in:
@@ -26,3 +26,4 @@
|
|||||||
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
|
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
|
||||||
0.6.1 - Ajout du bridge de détection Solana WS : notifications JSON-RPC persistées en observations, avec détection initiale des mints SPL / Token-2022 depuis programNotification
|
0.6.1 - Ajout du bridge de détection Solana WS : notifications JSON-RPC persistées en observations, avec détection initiale des mints SPL / Token-2022 depuis programNotification
|
||||||
0.6.2 - Branchement de WsClient vers le pipeline de détection via un relais asynchrone de notifications JSON-RPC WebSocket
|
0.6.2 - Branchement de WsClient vers le pipeline de détection via un relais asynchrone de notifications JSON-RPC WebSocket
|
||||||
|
0.6.3 - Enrichissement des notifications WebSocket utiles : extraction améliorée de pubkey, signature, owner, parsed account type et slot pour account/logs/signature notifications
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.6.2"
|
version = "0.6.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
|
||||||
|
|||||||
100
ROADMAP.md
100
ROADMAP.md
@@ -140,7 +140,7 @@ Le tracing est centralisé dans `kb_lib`.
|
|||||||
|
|
||||||
## 6. Phasage par versions
|
## 6. Phasage par versions
|
||||||
|
|
||||||
### 6.1. Version `0.0.2` — Socle conforme
|
### 6.001. Version `0.0.2` — Socle conforme
|
||||||
Objectif : corriger le squelette et poser la base de travail.
|
Objectif : corriger le squelette et poser la base de travail.
|
||||||
|
|
||||||
Réalisé :
|
Réalisé :
|
||||||
@@ -155,7 +155,7 @@ Réalisé :
|
|||||||
- documentation de `kb_app/src/splash.rs`,
|
- documentation de `kb_app/src/splash.rs`,
|
||||||
- UI Tauri minimale.
|
- UI Tauri minimale.
|
||||||
|
|
||||||
### 6.2. Version `0.1.x` — Transport WebSocket générique
|
### 6.002. Version `0.1.x` — Transport WebSocket générique
|
||||||
Objectif : construire un vrai `WsClient` asynchrone clonable.
|
Objectif : construire un vrai `WsClient` asynchrone clonable.
|
||||||
|
|
||||||
Réalisé :
|
Réalisé :
|
||||||
@@ -169,7 +169,7 @@ Réalisé :
|
|||||||
- fermeture avec timeout,
|
- fermeture avec timeout,
|
||||||
- tests offline avec serveur mock.
|
- tests offline avec serveur mock.
|
||||||
|
|
||||||
### 6.3. Version `0.1.1` — Intégration Tauri minimale du `WsClient`
|
### 6.003. Version `0.1.1` — Intégration Tauri minimale du `WsClient`
|
||||||
Objectif : valider le transport via l’application desktop.
|
Objectif : valider le transport via l’application desktop.
|
||||||
|
|
||||||
Réalisé :
|
Réalisé :
|
||||||
@@ -179,7 +179,7 @@ Réalisé :
|
|||||||
- zone de logs,
|
- zone de logs,
|
||||||
- validation du flux `frontend -> tauri -> kb_lib -> frontend`.
|
- validation du flux `frontend -> tauri -> kb_lib -> frontend`.
|
||||||
|
|
||||||
### 6.4. Version `0.2.0` — Couche JSON-RPC WS Solana
|
### 6.004. Version `0.2.0` — Couche JSON-RPC WS Solana
|
||||||
Objectif : séparer clairement transport, réponses RPC et notifications.
|
Objectif : séparer clairement transport, réponses RPC et notifications.
|
||||||
|
|
||||||
Réalisé :
|
Réalisé :
|
||||||
@@ -190,7 +190,7 @@ Réalisé :
|
|||||||
- parsing des notifications,
|
- parsing des notifications,
|
||||||
- premiers helpers JSON-RPC sur `WsClient`.
|
- premiers helpers JSON-RPC sur `WsClient`.
|
||||||
|
|
||||||
### 6.5. Version `0.3.0` — Registre subscriptions / notifications
|
### 6.005. Version `0.3.0` — Registre subscriptions / notifications
|
||||||
Objectif : fiabiliser la gestion des subscriptions.
|
Objectif : fiabiliser la gestion des subscriptions.
|
||||||
|
|
||||||
Réalisé :
|
Réalisé :
|
||||||
@@ -202,7 +202,7 @@ Réalisé :
|
|||||||
- purge locale si nécessaire,
|
- purge locale si nécessaire,
|
||||||
- routage séparé des notifications.
|
- routage séparé des notifications.
|
||||||
|
|
||||||
### 6.6. Version `0.3.1` — Helpers subscribe/unsubscribe WebSocket
|
### 6.006. Version `0.3.1` — Helpers subscribe/unsubscribe WebSocket
|
||||||
Objectif : ajouter les helpers haut niveau correspondant aux principales méthodes PubSub Solana.
|
Objectif : ajouter les helpers haut niveau correspondant aux principales méthodes PubSub Solana.
|
||||||
|
|
||||||
Réalisé :
|
Réalisé :
|
||||||
@@ -211,7 +211,7 @@ Réalisé :
|
|||||||
- helpers d’unsubscribe correspondants,
|
- helpers d’unsubscribe correspondants,
|
||||||
- premiers tests de validation des noms de méthodes.
|
- premiers tests de validation des noms de méthodes.
|
||||||
|
|
||||||
### 6.7. Version `0.3.2` — Helpers typed et notifications typed
|
### 6.007. Version `0.3.2` — Helpers typed et notifications typed
|
||||||
Objectif : s’appuyer principalement sur `solana-rpc-client-api` pour typer les subscribe et les notifications.
|
Objectif : s’appuyer principalement sur `solana-rpc-client-api` pour typer les subscribe et les notifications.
|
||||||
|
|
||||||
Réalisé :
|
Réalisé :
|
||||||
@@ -220,7 +220,7 @@ Réalisé :
|
|||||||
- parsing typed des notifications,
|
- parsing typed des notifications,
|
||||||
- base de travail pour réduire l’usage direct de `serde_json::Value`.
|
- base de travail pour réduire l’usage direct de `serde_json::Value`.
|
||||||
|
|
||||||
### 6.8. Version `0.3.3` — Distinction API typed / raw
|
### 6.008. Version `0.3.3` — Distinction API typed / raw
|
||||||
Objectif : clarifier l’API publique de `WsClient`.
|
Objectif : clarifier l’API publique de `WsClient`.
|
||||||
|
|
||||||
Réalisé :
|
Réalisé :
|
||||||
@@ -229,7 +229,7 @@ Réalisé :
|
|||||||
- conservation des helpers typed comme interface plus propre,
|
- conservation des helpers typed comme interface plus propre,
|
||||||
- préparation d’une hiérarchie API plus explicite.
|
- préparation d’une hiérarchie API plus explicite.
|
||||||
|
|
||||||
### 6.9. Version `0.3.4` — Fenêtre `Demo Ws` dans `kb_app`
|
### 6.009. Version `0.3.4` — Fenêtre `Demo Ws` dans `kb_app`
|
||||||
Objectif : tester manuellement les souscriptions live dans une fenêtre dédiée.
|
Objectif : tester manuellement les souscriptions live dans une fenêtre dédiée.
|
||||||
|
|
||||||
Réalisé :
|
Réalisé :
|
||||||
@@ -241,7 +241,7 @@ Réalisé :
|
|||||||
- affichage des événements raw et typed,
|
- affichage des événements raw et typed,
|
||||||
- premiers tests réels sur `wss://api.mainnet.solana.com`.
|
- premiers tests réels sur `wss://api.mainnet.solana.com`.
|
||||||
|
|
||||||
### 6.10. Version `0.3.5` — Stabilisation de `Demo Ws`
|
### 6.010. Version `0.3.5` — Stabilisation de `Demo Ws`
|
||||||
Objectif : rendre la fenêtre de démonstration robuste sous flux élevé et cohérente avec la configuration.
|
Objectif : rendre la fenêtre de démonstration robuste sous flux élevé et cohérente avec la configuration.
|
||||||
|
|
||||||
Réalisé :
|
Réalisé :
|
||||||
@@ -255,11 +255,11 @@ Réalisé :
|
|||||||
- conserver des compteurs et états UI exploitables,
|
- conserver des compteurs et états UI exploitables,
|
||||||
- mieux gérer les fermetures/ralentissements d’endpoints publics.
|
- mieux gérer les fermetures/ralentissements d’endpoints publics.
|
||||||
|
|
||||||
### 6.11. Version `0.4.x` — Transport HTTP générique et helpers RPC
|
### 6.011. Version `0.4.x` — Transport HTTP générique et helpers RPC
|
||||||
|
|
||||||
Objectif : construire un `HttpClient` clonable, limité et extensible, puis ajouter les premiers helpers HTTP Solana.
|
Objectif : construire un `HttpClient` clonable, limité et extensible, puis ajouter les premiers helpers HTTP Solana.
|
||||||
|
|
||||||
### 6.12. Version `0.4.0` — Socle `HttpClient`
|
### 6.012. Version `0.4.0` — Socle `HttpClient`
|
||||||
Réalisé :
|
Réalisé :
|
||||||
|
|
||||||
- client `reqwest` asynchrone clonable,
|
- client `reqwest` asynchrone clonable,
|
||||||
@@ -280,7 +280,7 @@ Livrables :
|
|||||||
- `getVersion`
|
- `getVersion`
|
||||||
- `getSlot`
|
- `getSlot`
|
||||||
|
|
||||||
### 6.13. Version `0.4.1` — Helpers HTTP Solana
|
### 6.013. Version `0.4.1` — Helpers HTTP Solana
|
||||||
Réalisé :
|
Réalisé :
|
||||||
|
|
||||||
- ajouter des helpers HTTP haut niveau comme pour le client WS,
|
- ajouter des helpers HTTP haut niveau comme pour le client WS,
|
||||||
@@ -288,7 +288,7 @@ Réalisé :
|
|||||||
- couvrir les premières méthodes utiles du RPC HTTP Solana,
|
- couvrir les premières méthodes utiles du RPC HTTP Solana,
|
||||||
- conserver `HttpClient` comme couche générique réutilisable.
|
- conserver `HttpClient` comme couche générique réutilisable.
|
||||||
|
|
||||||
### 6.14. Version `0.4.2` — Politique HTTP avancée
|
### 6.014. Version `0.4.2` — Politique HTTP avancée
|
||||||
Réalisé :
|
Réalisé :
|
||||||
|
|
||||||
- préparer un état de pause avant envoi pour un endpoint HTTP,
|
- préparer un état de pause avant envoi pour un endpoint HTTP,
|
||||||
@@ -296,7 +296,7 @@ Réalisé :
|
|||||||
- distinguer quota RPC général et quota `sendTransaction`,
|
- distinguer quota RPC général et quota `sendTransaction`,
|
||||||
- préparer un futur pool d’endpoints HTTP et l’arbitrage entre eux.
|
- préparer un futur pool d’endpoints HTTP et l’arbitrage entre eux.
|
||||||
|
|
||||||
### 6.15. Version `0.4.3` — Pool d’endpoints HTTP
|
### 6.015. Version `0.4.3` — Pool d’endpoints HTTP
|
||||||
Réalisé :
|
Réalisé :
|
||||||
|
|
||||||
- ajouter un pool d’`HttpClient`,
|
- ajouter un pool d’`HttpClient`,
|
||||||
@@ -306,7 +306,7 @@ Réalisé :
|
|||||||
- prendre en compte la classe de méthode HTTP,
|
- prendre en compte la classe de méthode HTTP,
|
||||||
- préparer le routage multi-RPC et la limitation de concurrence par endpoint.
|
- préparer le routage multi-RPC et la limitation de concurrence par endpoint.
|
||||||
|
|
||||||
### 6.16. Version `0.4.4` — Démo HTTP dans `kb_app`
|
### 6.016. Version `0.4.4` — Démo HTTP dans `kb_app`
|
||||||
Réalisé :
|
Réalisé :
|
||||||
|
|
||||||
- ajout d’une fenêtre `Demo Http`,
|
- ajout d’une fenêtre `Demo Http`,
|
||||||
@@ -317,10 +317,10 @@ Réalisé :
|
|||||||
- alignement visuel de la fenêtre sur le gabarit `Demo Ws`,
|
- alignement visuel de la fenêtre sur le gabarit `Demo Ws`,
|
||||||
- amélioration des presets UI, copie de réponse et bascule pretty/raw.
|
- amélioration des presets UI, copie de réponse et bascule pretty/raw.
|
||||||
|
|
||||||
### 6.17. Version `0.5.x` — Base de données SQLite
|
### 6.017. Version `0.5.x` — Base de données SQLite
|
||||||
Objectif : poser la persistance locale avec une organisation préparée dès le départ à une future évolution vers PostgreSQL ou un autre backend.
|
Objectif : poser la persistance locale avec une organisation préparée dès le départ à une future évolution vers PostgreSQL ou un autre backend.
|
||||||
|
|
||||||
### 6.18. Version `0.5.0` — Socle SQLite
|
### 6.018. Version `0.5.0` — Socle SQLite
|
||||||
Réalisé :
|
Réalisé :
|
||||||
|
|
||||||
- configuration DB dans `config.json`,
|
- configuration DB dans `config.json`,
|
||||||
@@ -330,7 +330,7 @@ Réalisé :
|
|||||||
- table `kb_db_metadata`,
|
- table `kb_db_metadata`,
|
||||||
- séparation `db/entities`, `db/dtos`, `db/queries`, `db/types`.
|
- séparation `db/entities`, `db/dtos`, `db/queries`, `db/types`.
|
||||||
|
|
||||||
### 6.19. Version `0.5.1` — Premières tables métier de stockage local
|
### 6.019. Version `0.5.1` — Premières tables métier de stockage local
|
||||||
Réalisé :
|
Réalisé :
|
||||||
|
|
||||||
- ajout des tables de référence pour les endpoints connus HTTP/WS,
|
- ajout des tables de référence pour les endpoints connus HTTP/WS,
|
||||||
@@ -338,7 +338,7 @@ Réalisé :
|
|||||||
- mise en place des `entities`, `dtos`, `queries` et `types` associés,
|
- mise en place des `entities`, `dtos`, `queries` et `types` associés,
|
||||||
- préparation du stockage local des endpoints HTTP/WS connus et de leur état utile.
|
- préparation du stockage local des endpoints HTTP/WS connus et de leur état utile.
|
||||||
|
|
||||||
### 6.20. Version `0.5.2` — Stockage des tokens observés
|
### 6.020. Version `0.5.2` — Stockage des tokens observés
|
||||||
Réalisé :
|
Réalisé :
|
||||||
|
|
||||||
- ajout de la table `kb_observed_tokens`,
|
- ajout de la table `kb_observed_tokens`,
|
||||||
@@ -347,7 +347,7 @@ Réalisé :
|
|||||||
- préparation des relations futures avec pools, paires et événements on-chain,
|
- préparation des relations futures avec pools, paires et événements on-chain,
|
||||||
- conservation d’unicité locale par mint sans duplication par endpoint.
|
- conservation d’unicité locale par mint sans duplication par endpoint.
|
||||||
|
|
||||||
### 6.21. Version `0.5.3` — Événements et signaux locaux
|
### 6.021. Version `0.5.3` — Événements et signaux locaux
|
||||||
Réalisé :
|
Réalisé :
|
||||||
|
|
||||||
- conservation des événements runtime techniques via `kb_db_runtime_events`,
|
- conservation des événements runtime techniques via `kb_db_runtime_events`,
|
||||||
@@ -356,7 +356,7 @@ Réalisé :
|
|||||||
- distinction explicite entre événements runtime, observations on-chain et événements métier,
|
- distinction explicite entre événements runtime, observations on-chain et événements métier,
|
||||||
- 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.
|
- 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
|
### 6.022. Version `0.5.4` — Modèle métier normalisé initial
|
||||||
Réalisé :
|
Réalisé :
|
||||||
|
|
||||||
- ajouter les tables de référence métier pour les DEX, tokens, pools et paires,
|
- ajouter les tables de référence métier pour les DEX, tokens, pools et paires,
|
||||||
@@ -364,7 +364,7 @@ Réalisé :
|
|||||||
- préparer les relations entre tokens, pools, paires et listings,
|
- préparer les relations entre tokens, pools, paires et listings,
|
||||||
- éviter que la détection technique `0.6.x` écrive directement dans des tables trop brutes ou ambiguës.
|
- éviter que la détection technique `0.6.x` écrive directement dans des tables trop brutes ou ambiguës.
|
||||||
|
|
||||||
### 6.23. Version `0.5.5` — Activité métier normalisée
|
### 6.023. Version `0.5.5` — Activité métier normalisée
|
||||||
Réalisé :
|
Réalisé :
|
||||||
|
|
||||||
- ajout des tables de swaps,
|
- ajout des tables de swaps,
|
||||||
@@ -372,7 +372,7 @@ Réalisé :
|
|||||||
- ajout des événements de mint et burn utiles au suivi des tokens,
|
- ajout des événements de mint et burn utiles au suivi des tokens,
|
||||||
- préparation de l’historique métier nécessaire avant l’arrivée des connecteurs DEX complets.
|
- préparation de l’historique métier nécessaire avant l’arrivée des connecteurs DEX complets.
|
||||||
|
|
||||||
### 6.24. Version `0.5.6` — Consolidation de la couche stockage
|
### 6.024. Version `0.5.6` — Consolidation de la couche stockage
|
||||||
Objectif : stabiliser le schéma avant la détection technique réelle.
|
Objectif : stabiliser le schéma avant la détection technique réelle.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
@@ -383,7 +383,7 @@ Objectif : stabiliser le schéma avant la détection technique réelle.
|
|||||||
- durcir les relations, contraintes et index utiles,
|
- durcir les relations, contraintes et index utiles,
|
||||||
- préparer une future compatibilité PostgreSQL sans casser l’organisation générale.
|
- préparer une future compatibilité PostgreSQL sans casser l’organisation générale.
|
||||||
|
|
||||||
### 6.25. Version `0.6.0` — Pipeline de détection technique
|
### 6.025. Version `0.6.0` — Pipeline de détection technique
|
||||||
Objectif : relier les connecteurs RPC à la couche de stockage technique et métier.
|
Objectif : relier les connecteurs RPC à la couche de stockage technique et métier.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
@@ -393,7 +393,7 @@ Objectif : relier les connecteurs RPC à la couche de stockage technique et mét
|
|||||||
- éviter que les futurs watchers RPC écrivent directement dans la DB sans couche intermédiaire,
|
- é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.
|
- préparer les prochaines étapes de détection technique on-chain / RPC.
|
||||||
|
|
||||||
### 6.26. Version `0.6.1` — Détection technique RPC
|
### 6.026. Version `0.6.1` — Détection technique RPC
|
||||||
Réalisé :
|
Réalisé :
|
||||||
|
|
||||||
- ajout d’un bridge `Solana WS notification -> pipeline de détection`,
|
- ajout d’un bridge `Solana WS notification -> pipeline de détection`,
|
||||||
@@ -401,27 +401,23 @@ Réalisé :
|
|||||||
- génération d’un candidat token quand une `programNotification` expose un mint SPL / Token-2022 en JSON parsé,
|
- génération d’un candidat token quand une `programNotification` expose un mint SPL / Token-2022 en JSON parsé,
|
||||||
- préparation du branchement futur des watchers et règles RPC réelles sur une façade de détection unique.
|
- préparation du branchement futur des watchers et règles RPC réelles sur une façade de détection unique.
|
||||||
|
|
||||||
### 6.27. Version `0.6.2` — Branchement `WsClient` vers la détection
|
### 6.027. Version `0.6.2` — Branchement `WsClient` vers la détection
|
||||||
Objectif : relier directement les notifications WS issues de `WsClient` au pipeline de détection.
|
Réalisé :
|
||||||
|
|
||||||
À faire :
|
|
||||||
|
|
||||||
- ajouter un relais interne de notifications WS vers la couche de détection,
|
- ajouter un relais interne de notifications WS vers la couche de détection,
|
||||||
- permettre à `WsClient` de forwarder les `JsonRpcWsNotification` vers un worker dédié,
|
- permettre à `WsClient` de forwarder les `JsonRpcWsNotification` vers un worker dédié,
|
||||||
- conserver le découplage entre transport WS et logique de détection,
|
- conserver le découplage entre transport WS et logique de détection,
|
||||||
- éviter de bloquer la boucle de lecture WS si la détection est lente.
|
- éviter de bloquer la boucle de lecture WS si la détection est lente.
|
||||||
|
|
||||||
### 6.28. Version `0.6.3` — Enrichissement des notifications WS utiles
|
### 6.028. Version `0.6.3` — Enrichissement des notifications WS utiles
|
||||||
Objectif : améliorer la couverture technique des notifications WS.
|
Réalisé :
|
||||||
|
|
||||||
À faire :
|
- enrichir `accountNotification`, `logsNotification` et `signatureNotification`,
|
||||||
|
- mieux extraire slot, pubkey, signature, owner, parsed account type et clés pertinentes,
|
||||||
- enrichir `accountNotification`, `logsNotification`, `signatureNotification`,
|
|
||||||
- mieux extraire slot, pubkey, signature, owner, parsed account type,
|
|
||||||
- produire des observations plus précises et plus homogènes,
|
- produire des observations plus précises et plus homogènes,
|
||||||
- préparer les règles de détection techniques réelles.
|
- préparer les règles de détection techniques réelles.
|
||||||
|
|
||||||
### 6.29. Version `0.6.4` — Premières règles de détection technique
|
### 6.029. Version `0.6.4` — Premières règles de détection technique
|
||||||
Objectif : commencer la détection technique on-chain utile avant les connecteurs DEX dédiés.
|
Objectif : commencer la détection technique on-chain utile avant les connecteurs DEX dédiés.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
@@ -431,7 +427,17 @@ Objectif : commencer la détection technique on-chain utile avant les connecteur
|
|||||||
- préparer l’alimentation des tables métier normalisées,
|
- préparer l’alimentation des tables métier normalisées,
|
||||||
- garder la logique encore indépendante des connecteurs DEX `0.7.x`.
|
- garder la logique encore indépendante des connecteurs DEX `0.7.x`.
|
||||||
|
|
||||||
### 6.30. Version `0.7.x` — DEX connectors v1
|
### 6.030. Version `0.6.5` — Orchestration multi-clients WebSocket
|
||||||
|
Objectif : préparer la gestion coordonnée de plusieurs `WsClient`.
|
||||||
|
|
||||||
|
À faire :
|
||||||
|
|
||||||
|
- introduire une abstraction `ws_pool.rs` ou `ws_manager.rs`,
|
||||||
|
- piloter plusieurs clients WS selon les rôles d’endpoints configurés,
|
||||||
|
- centraliser le démarrage, l’arrêt, l’état et les relais de notifications WS,
|
||||||
|
- préparer les futures politiques de répartition, supervision et reconnexion.
|
||||||
|
|
||||||
|
### 6.031. Version `0.7.x` — DEX connectors v1
|
||||||
Objectif : structurer les connecteurs par protocole.
|
Objectif : structurer les connecteurs par protocole.
|
||||||
|
|
||||||
Cibles initiales possibles :
|
Cibles initiales possibles :
|
||||||
@@ -450,7 +456,7 @@ Cibles initiales possibles :
|
|||||||
- création de types métiers propres,
|
- création de types métiers propres,
|
||||||
- enrichissement des métadonnées token/pool/pair.
|
- enrichissement des métadonnées token/pool/pair.
|
||||||
|
|
||||||
### 6.31. Version `0.8.x` — Analyse et filtrage
|
### 6.032. Version `0.8.x` — Analyse et filtrage
|
||||||
Objectif : transformer les événements bruts en signaux exploitables.
|
Objectif : transformer les événements bruts en signaux exploitables.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
@@ -461,7 +467,7 @@ Objectif : transformer les événements bruts en signaux exploitables.
|
|||||||
- statistiques de comportement,
|
- statistiques de comportement,
|
||||||
- premiers patterns.
|
- premiers patterns.
|
||||||
|
|
||||||
### 6.32. Version `1.x.y` — Wallets et swap préparatoire
|
### 6.033. Version `1.x.y` — Wallets et swap préparatoire
|
||||||
Objectif : préparer la couche d’action.
|
Objectif : préparer la couche d’action.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
@@ -472,7 +478,7 @@ Objectif : préparer la couche d’action.
|
|||||||
- préparation d’ordres et de swaps,
|
- préparation d’ordres et de swaps,
|
||||||
- simulation et garde-fous.
|
- simulation et garde-fous.
|
||||||
|
|
||||||
### 6.33. Version `2.x.y` — Trading semi-automatisé
|
### 6.034. Version `2.x.y` — Trading semi-automatisé
|
||||||
Objectif : brancher l’analyse à l’action tout en gardant des garde-fous explicites.
|
Objectif : brancher l’analyse à l’action tout en gardant des garde-fous explicites.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
@@ -483,7 +489,7 @@ Objectif : brancher l’analyse à l’action tout en gardant des garde-fous exp
|
|||||||
- confirmations explicites ou semi-automatiques,
|
- confirmations explicites ou semi-automatiques,
|
||||||
- journaux d’exécution.
|
- journaux d’exécution.
|
||||||
|
|
||||||
### 6.34. Version `3.x.y` — Yellowstone gRPC
|
### 6.035. Version `3.x.y` — Yellowstone gRPC
|
||||||
Objectif : ajouter le connecteur gRPC dédié.
|
Objectif : ajouter le connecteur gRPC dédié.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
@@ -568,9 +574,9 @@ Le projet doit maintenir au minimum :
|
|||||||
## 12. Priorité immédiate
|
## 12. Priorité immédiate
|
||||||
La priorité immédiate est désormais la suivante :
|
La priorité immédiate est désormais la suivante :
|
||||||
|
|
||||||
1. démarrer la version `0.6.2` avec le branchement `WsClient` vers la détection,
|
1. démarrer la version `0.6.3` avec l’enrichissement des notifications WS utiles,
|
||||||
2. ajouter un relais asynchrone entre notifications WS et worker de détection,
|
2. améliorer l’extraction des métadonnées utiles depuis `accountNotification`, `logsNotification` et `signatureNotification`,
|
||||||
3. éviter de bloquer la boucle de lecture du client WS,
|
3. produire des observations on-chain plus précises et homogènes,
|
||||||
4. préparer ensuite la version `0.6.3` pour enrichir les notifications WS utiles,
|
4. préparer ensuite la version `0.6.4` pour les premières règles de détection technique,
|
||||||
5. conserver le découplage entre transport, détection et stockage,
|
5. conserver le découplage entre transport, détection et stockage,
|
||||||
6. préparer enfin `0.6.4` pour les premières règles de détection technique.
|
6. planifier enfin `0.6.5` pour l’orchestration multi-clients WebSocket via `ws_pool.rs` ou `ws_manager.rs`.
|
||||||
|
|||||||
@@ -73,10 +73,10 @@ impl KbSolanaWsDetectionService {
|
|||||||
let observation_input = crate::KbDetectionObservationInput::new(
|
let observation_input = crate::KbDetectionObservationInput::new(
|
||||||
observation_kind,
|
observation_kind,
|
||||||
crate::KbObservationSourceKind::WsRpc,
|
crate::KbObservationSourceKind::WsRpc,
|
||||||
endpoint_name,
|
endpoint_name.clone(),
|
||||||
object_key,
|
object_key.clone(),
|
||||||
slot,
|
slot,
|
||||||
payload,
|
payload.clone(),
|
||||||
);
|
);
|
||||||
let observation_id_result = self
|
let observation_id_result = self
|
||||||
.persistence
|
.persistence
|
||||||
@@ -86,6 +86,32 @@ impl KbSolanaWsDetectionService {
|
|||||||
Ok(observation_id) => observation_id,
|
Ok(observation_id) => observation_id,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
};
|
};
|
||||||
|
let should_emit_signal = match notification.method.as_str() {
|
||||||
|
"accountNotification" => true,
|
||||||
|
"logsNotification" => true,
|
||||||
|
"signatureNotification" => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if should_emit_signal {
|
||||||
|
let signal_input = crate::KbDetectionSignalInput::new(
|
||||||
|
build_signal_kind_for_notification(
|
||||||
|
notification.method.as_str(),
|
||||||
|
¬ification.params.result,
|
||||||
|
),
|
||||||
|
build_signal_severity_for_notification(
|
||||||
|
notification.method.as_str(),
|
||||||
|
¬ification.params.result,
|
||||||
|
),
|
||||||
|
object_key,
|
||||||
|
Some(observation_id),
|
||||||
|
None,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
let signal_result = self.persistence.record_signal(&signal_input).await;
|
||||||
|
if let Err(error) = signal_result {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id })
|
Ok(crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,9 +140,6 @@ impl KbSolanaWsDetectionService {
|
|||||||
Some(parsed_type) => parsed_type,
|
Some(parsed_type) => parsed_type,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
if parsed_type.as_str() != "mint" {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
let token_program_option = extract_account_owner(account_value);
|
let token_program_option = extract_account_owner(account_value);
|
||||||
let token_program = match token_program_option {
|
let token_program = match token_program_option {
|
||||||
Some(token_program) => token_program,
|
Some(token_program) => token_program,
|
||||||
@@ -127,13 +150,30 @@ impl KbSolanaWsDetectionService {
|
|||||||
{
|
{
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let decimals = extract_decimals_from_account_value(account_value);
|
let decimals = if parsed_type.as_str() == "mint" {
|
||||||
|
extract_decimals_from_account_value(account_value)
|
||||||
|
} else if parsed_type.as_str() == "account" {
|
||||||
|
extract_token_account_decimals_from_account_value(account_value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let mint = if parsed_type.as_str() == "mint" {
|
||||||
|
pubkey.clone()
|
||||||
|
} else if parsed_type.as_str() == "account" {
|
||||||
|
let mint_option = extract_parsed_account_mint(account_value);
|
||||||
|
match mint_option {
|
||||||
|
Some(mint) => mint,
|
||||||
|
None => return Ok(None),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
let slot =
|
let slot =
|
||||||
extract_slot_from_result(notification.method.as_str(), ¬ification.params.result);
|
extract_slot_from_result(notification.method.as_str(), ¬ification.params.result);
|
||||||
let payload = build_notification_payload(notification);
|
let payload = build_notification_payload(notification);
|
||||||
let is_quote_token = pubkey == crate::WSOL_MINT_ID.to_string();
|
let is_quote_token = mint == crate::WSOL_MINT_ID.to_string();
|
||||||
let input = crate::KbDetectionTokenCandidateInput::new(
|
let input = crate::KbDetectionTokenCandidateInput::new(
|
||||||
pubkey,
|
mint,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
decimals,
|
decimals,
|
||||||
@@ -144,7 +184,11 @@ impl KbSolanaWsDetectionService {
|
|||||||
slot,
|
slot,
|
||||||
"ws.program_notification".to_string(),
|
"ws.program_notification".to_string(),
|
||||||
payload.clone(),
|
payload.clone(),
|
||||||
"signal.token_mint_account_detected".to_string(),
|
if parsed_type.as_str() == "mint" {
|
||||||
|
"signal.token_mint_account_detected".to_string()
|
||||||
|
} else {
|
||||||
|
"signal.token_account_detected".to_string()
|
||||||
|
},
|
||||||
crate::KbAnalysisSignalSeverity::Medium,
|
crate::KbAnalysisSignalSeverity::Medium,
|
||||||
None,
|
None,
|
||||||
Some(payload),
|
Some(payload),
|
||||||
@@ -342,6 +386,235 @@ fn extract_decimals_from_account_value(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extracts the owner program id from one program notification result.
|
||||||
|
fn extract_program_notification_owner(
|
||||||
|
result: &serde_json::Value,
|
||||||
|
) -> std::option::Option<std::string::String> {
|
||||||
|
let account_value_option = extract_account_value_from_result(result);
|
||||||
|
let account_value = match account_value_option {
|
||||||
|
Some(account_value) => account_value,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
extract_account_owner(account_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the parsed token amount decimals from one parsed token account notification.
|
||||||
|
fn extract_token_account_decimals_from_account_value(
|
||||||
|
account_value: &serde_json::Value,
|
||||||
|
) -> std::option::Option<u8> {
|
||||||
|
let data_option = account_value.get("data");
|
||||||
|
let data = match data_option {
|
||||||
|
Some(data) => data,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
let parsed_option = data.get("parsed");
|
||||||
|
let parsed = match parsed_option {
|
||||||
|
Some(parsed) => parsed,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
let info_option = parsed.get("info");
|
||||||
|
let info = match info_option {
|
||||||
|
Some(info) => info,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
let token_amount_option = info.get("tokenAmount");
|
||||||
|
let token_amount = match token_amount_option {
|
||||||
|
Some(token_amount) => token_amount,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
let decimals_option = token_amount
|
||||||
|
.get("decimals")
|
||||||
|
.and_then(serde_json::Value::as_u64);
|
||||||
|
let decimals = match decimals_option {
|
||||||
|
Some(decimals) => decimals,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
let convert_result = u8::try_from(decimals);
|
||||||
|
match convert_result {
|
||||||
|
Ok(decimals) => Some(decimals),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts a parsed account mint from one account-like JSON object.
|
||||||
|
fn extract_parsed_account_mint(
|
||||||
|
account_value: &serde_json::Value,
|
||||||
|
) -> std::option::Option<std::string::String> {
|
||||||
|
let data_option = account_value.get("data");
|
||||||
|
let data = match data_option {
|
||||||
|
Some(data) => data,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
let parsed_option = data.get("parsed");
|
||||||
|
let parsed = match parsed_option {
|
||||||
|
Some(parsed) => parsed,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
let info_option = parsed.get("info");
|
||||||
|
let info = match info_option {
|
||||||
|
Some(info) => info,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
let mint_option = info.get("mint").and_then(serde_json::Value::as_str);
|
||||||
|
match mint_option {
|
||||||
|
Some(mint) => Some(mint.to_string()),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts log lines from one logs notification result.
|
||||||
|
fn extract_logs_lines(result: &serde_json::Value) -> std::vec::Vec<std::string::String> {
|
||||||
|
let mut lines = std::vec::Vec::new();
|
||||||
|
let value_option = result.get("value");
|
||||||
|
let value = match value_option {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return lines,
|
||||||
|
};
|
||||||
|
let logs_option = value.get("logs");
|
||||||
|
let logs = match logs_option {
|
||||||
|
Some(logs) => logs,
|
||||||
|
None => return lines,
|
||||||
|
};
|
||||||
|
let array_option = logs.as_array();
|
||||||
|
let array = match array_option {
|
||||||
|
Some(array) => array,
|
||||||
|
None => return lines,
|
||||||
|
};
|
||||||
|
for item in array {
|
||||||
|
let line_option = item.as_str();
|
||||||
|
if let Some(line) = line_option {
|
||||||
|
lines.push(line.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the error field from a signature notification result.
|
||||||
|
fn extract_signature_notification_err(
|
||||||
|
result: &serde_json::Value,
|
||||||
|
) -> std::option::Option<serde_json::Value> {
|
||||||
|
let value_option = result.get("value");
|
||||||
|
let value = match value_option {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
match value.get("err") {
|
||||||
|
Some(err) => Some(err.clone()),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a more specific signal kind for one WebSocket notification.
|
||||||
|
fn build_signal_kind_for_notification(
|
||||||
|
method: &str,
|
||||||
|
result: &serde_json::Value,
|
||||||
|
) -> std::string::String {
|
||||||
|
match method {
|
||||||
|
"accountNotification" => {
|
||||||
|
let account_value_option = extract_account_value_from_result(result);
|
||||||
|
if let Some(account_value) = account_value_option {
|
||||||
|
let parsed_type_option = extract_parsed_account_type(account_value);
|
||||||
|
if let Some(parsed_type) = parsed_type_option {
|
||||||
|
return format!("signal.account_notification.{parsed_type}");
|
||||||
|
}
|
||||||
|
let owner_option = extract_account_owner(account_value);
|
||||||
|
if let Some(owner) = owner_option {
|
||||||
|
if owner == crate::SPL_TOKEN_PROGRAM_ID.to_string() {
|
||||||
|
return "signal.account_notification.spl_token".to_string();
|
||||||
|
}
|
||||||
|
if owner == crate::SPL_TOKEN_2022_PROGRAM_ID.to_string() {
|
||||||
|
return "signal.account_notification.spl_token_2022".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"signal.account_notification.generic".to_string()
|
||||||
|
}
|
||||||
|
"logsNotification" => {
|
||||||
|
let lines = extract_logs_lines(result);
|
||||||
|
for line in &lines {
|
||||||
|
if line.contains("Instruction: InitializeMint") {
|
||||||
|
return "signal.logs_notification.initialize_mint".to_string();
|
||||||
|
}
|
||||||
|
if line.contains("Instruction: MintTo") {
|
||||||
|
return "signal.logs_notification.mint_to".to_string();
|
||||||
|
}
|
||||||
|
if line.contains("Instruction: Burn") {
|
||||||
|
return "signal.logs_notification.burn".to_string();
|
||||||
|
}
|
||||||
|
if line.contains("Instruction: InitializeAccount") {
|
||||||
|
return "signal.logs_notification.initialize_account".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"signal.logs_notification.generic".to_string()
|
||||||
|
}
|
||||||
|
"signatureNotification" => {
|
||||||
|
let err_option = extract_signature_notification_err(result);
|
||||||
|
match err_option {
|
||||||
|
Some(err) => {
|
||||||
|
if err.is_null() {
|
||||||
|
"signal.signature_notification.confirmed".to_string()
|
||||||
|
} else {
|
||||||
|
"signal.signature_notification.failed".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => "signal.signature_notification.generic".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"programNotification" => {
|
||||||
|
let owner_option = extract_program_notification_owner(result);
|
||||||
|
let owner = match owner_option {
|
||||||
|
Some(owner) => owner,
|
||||||
|
None => return "signal.program_notification.generic".to_string(),
|
||||||
|
};
|
||||||
|
if owner == crate::SPL_TOKEN_PROGRAM_ID.to_string() {
|
||||||
|
return "signal.program_notification.spl_token".to_string();
|
||||||
|
}
|
||||||
|
if owner == crate::SPL_TOKEN_2022_PROGRAM_ID.to_string() {
|
||||||
|
return "signal.program_notification.spl_token_2022".to_string();
|
||||||
|
}
|
||||||
|
"signal.program_notification.generic".to_string()
|
||||||
|
}
|
||||||
|
_ => format!(
|
||||||
|
"signal.{}",
|
||||||
|
method.replace("Notification", "").to_lowercase()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a more specific severity for one WebSocket notification.
|
||||||
|
fn build_signal_severity_for_notification(
|
||||||
|
method: &str,
|
||||||
|
result: &serde_json::Value,
|
||||||
|
) -> crate::KbAnalysisSignalSeverity {
|
||||||
|
match method {
|
||||||
|
"programNotification" => crate::KbAnalysisSignalSeverity::Medium,
|
||||||
|
"accountNotification" => crate::KbAnalysisSignalSeverity::Low,
|
||||||
|
"logsNotification" => {
|
||||||
|
let lines = extract_logs_lines(result);
|
||||||
|
for line in &lines {
|
||||||
|
if line.contains("Instruction: InitializeMint") {
|
||||||
|
return crate::KbAnalysisSignalSeverity::Medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crate::KbAnalysisSignalSeverity::Low
|
||||||
|
}
|
||||||
|
"signatureNotification" => {
|
||||||
|
let err_option = extract_signature_notification_err(result);
|
||||||
|
match err_option {
|
||||||
|
Some(err) => {
|
||||||
|
if err.is_null() {
|
||||||
|
crate::KbAnalysisSignalSeverity::Low
|
||||||
|
} else {
|
||||||
|
crate::KbAnalysisSignalSeverity::Medium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => crate::KbAnalysisSignalSeverity::Low,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => crate::KbAnalysisSignalSeverity::Low,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
async fn create_database() -> crate::KbDatabase {
|
async fn create_database() -> crate::KbDatabase {
|
||||||
@@ -409,6 +682,46 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_logs_notification() -> crate::KbJsonRpcWsNotification {
|
||||||
|
crate::KbJsonRpcWsNotification {
|
||||||
|
jsonrpc: "2.0".to_string(),
|
||||||
|
method: "logsNotification".to_string(),
|
||||||
|
params: crate::KbJsonRpcWsNotificationParams {
|
||||||
|
result: serde_json::json!({
|
||||||
|
"context": {
|
||||||
|
"slot": 888888_u64
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"signature": "Sig111111111111111111111111111111111111111111111111",
|
||||||
|
"err": null,
|
||||||
|
"logs": [
|
||||||
|
"Program log: Instruction: InitializeMint"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
subscription: 3001_u64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_signature_notification() -> crate::KbJsonRpcWsNotification {
|
||||||
|
crate::KbJsonRpcWsNotification {
|
||||||
|
jsonrpc: "2.0".to_string(),
|
||||||
|
method: "signatureNotification".to_string(),
|
||||||
|
params: crate::KbJsonRpcWsNotificationParams {
|
||||||
|
result: serde_json::json!({
|
||||||
|
"context": {
|
||||||
|
"slot": 999999_u64
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"err": null
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
subscription: 4001_u64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn slot_notification_records_observation() {
|
async fn slot_notification_records_observation() {
|
||||||
let database = create_database().await;
|
let database = create_database().await;
|
||||||
@@ -500,4 +813,90 @@ mod tests {
|
|||||||
"Mint111111111111111111111111111111111111111"
|
"Mint111111111111111111111111111111111111111"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn logs_notification_records_observation_and_signal() {
|
||||||
|
let database = create_database().await;
|
||||||
|
let persistence = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||||
|
let detector = crate::KbSolanaWsDetectionService::new(persistence);
|
||||||
|
let outcome_result = detector
|
||||||
|
.process_notification(
|
||||||
|
Some("helius_primary_ws_programs".to_string()),
|
||||||
|
&build_logs_notification(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let outcome = match outcome_result {
|
||||||
|
Ok(outcome) => outcome,
|
||||||
|
Err(error) => panic!("process_notification failed: {error}"),
|
||||||
|
};
|
||||||
|
match outcome {
|
||||||
|
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
|
||||||
|
assert!(observation_id > 0);
|
||||||
|
}
|
||||||
|
_ => panic!("unexpected detection outcome"),
|
||||||
|
}
|
||||||
|
let observations_result =
|
||||||
|
crate::list_recent_onchain_observations(detector.persistence().database().as_ref(), 10)
|
||||||
|
.await;
|
||||||
|
let observations = match observations_result {
|
||||||
|
Ok(observations) => observations,
|
||||||
|
Err(error) => panic!("list_recent_onchain_observations failed: {error}"),
|
||||||
|
};
|
||||||
|
let signals_result =
|
||||||
|
crate::list_recent_analysis_signals(detector.persistence().database().as_ref(), 10)
|
||||||
|
.await;
|
||||||
|
let signals = match signals_result {
|
||||||
|
Ok(signals) => signals,
|
||||||
|
Err(error) => panic!("list_recent_analysis_signals failed: {error}"),
|
||||||
|
};
|
||||||
|
assert_eq!(observations.len(), 1);
|
||||||
|
assert_eq!(signals.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
signals[0].signal_kind,
|
||||||
|
"signal.logs_notification.initialize_mint"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn signature_notification_records_observation_and_signal() {
|
||||||
|
let database = create_database().await;
|
||||||
|
let persistence = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||||
|
let detector = crate::KbSolanaWsDetectionService::new(persistence);
|
||||||
|
let outcome_result = detector
|
||||||
|
.process_notification(
|
||||||
|
Some("mainnet_public_ws_slots".to_string()),
|
||||||
|
&build_signature_notification(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let outcome = match outcome_result {
|
||||||
|
Ok(outcome) => outcome,
|
||||||
|
Err(error) => panic!("process_notification failed: {error}"),
|
||||||
|
};
|
||||||
|
match outcome {
|
||||||
|
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
|
||||||
|
assert!(observation_id > 0);
|
||||||
|
}
|
||||||
|
_ => panic!("unexpected detection outcome"),
|
||||||
|
}
|
||||||
|
let observations_result =
|
||||||
|
crate::list_recent_onchain_observations(detector.persistence().database().as_ref(), 10)
|
||||||
|
.await;
|
||||||
|
let observations = match observations_result {
|
||||||
|
Ok(observations) => observations,
|
||||||
|
Err(error) => panic!("list_recent_onchain_observations failed: {error}"),
|
||||||
|
};
|
||||||
|
let signals_result =
|
||||||
|
crate::list_recent_analysis_signals(detector.persistence().database().as_ref(), 10)
|
||||||
|
.await;
|
||||||
|
let signals = match signals_result {
|
||||||
|
Ok(signals) => signals,
|
||||||
|
Err(error) => panic!("list_recent_analysis_signals failed: {error}"),
|
||||||
|
};
|
||||||
|
assert_eq!(observations.len(), 1);
|
||||||
|
assert_eq!(signals.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
signals[0].signal_kind,
|
||||||
|
"signal.signature_notification.confirmed"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user