This commit is contained in:
2026-05-14 17:44:01 +02:00
parent 403f271083
commit 3f6d2e9f7f
21 changed files with 775 additions and 88 deletions

View File

@@ -68,3 +68,4 @@
0.7.35 - Ajout du profil `0.7.35_non_trade_fee_reward_admin`, matérialisation des événements non-trade fees/rewards/admin, raccordement aux diagnostics locaux et maintien strict de linvariant : aucun fee/reward/admin ne peut produire de trade, metric ou candle.
0.7.36 - Consolidation de la famille Meteora : corpus mixte `meteora_damm_v1`, `meteora_damm_v2`, `meteora_dbc` et `meteora_dlmm`, correction des discriminants DAMM v2 / DBC, validation du profil `0.7.36_meteora_family_consolidation`, et reclassement explicite des swaps DAMM v2 / DBC sans payload montant/prix en `non_actionable_trade` afin déviter tout trade/candle artificiel.
0.7.37 - Première tranche metadata/catalog : ajout du profil `0.7.37_token_metadata_catalog_enrichment`, exposition des compteurs metadata dans diagnostics/validation et raccordement UI Demo Pipeline 2 sans rendre les metadata manquantes bloquantes.
0.7.38 - Priorisation des metadata manquantes : ajout du profil `0.7.38_token_metadata_gap_prioritization`, samples `tokenMetadataGapSamples`, priorités tradable/quote/catalog, raccordement UI Demo Pipeline 2 et maintien du caractère non bloquant des metadata incomplètes.

View File

@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
version = "0.7.37"
version = "0.7.38"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"

View File

@@ -4,7 +4,7 @@
`khadhroony-bobobot` est un workspace Rust destiné à la détection, au décodage, à lanalyse et, à terme, au trading semi-automatisé de tokens Solana.
Le README précédent décrivait surtout létat `0.3.1`. Ce fichier reflète létat de reprise autour de `0.7.37-A` : le socle transport HTTP/WS, la résolution transactionnelle, le modèle SQLite, plusieurs connecteurs DEX, les candles, les signaux analytiques, la validation locale et une matrice DEX commune existent déjà.
Le README précédent décrivait surtout létat `0.3.1`. Ce fichier reflète létat de clôture `0.7.38` et la reprise prévue en `0.7.39` : le socle transport HTTP/WS, la résolution transactionnelle, le modèle SQLite, plusieurs connecteurs DEX, les candles, les signaux analytiques, la validation locale, la matrice DEX commune et les diagnostics de metadata prioritaires existent déjà.
## 1. Objectif
@@ -31,7 +31,7 @@ Le workspace contient deux crates principales.
La logique métier doit rester dans `kb_lib`. `kb_demo_app` doit rester une façade UI/Tauri et ne doit pas récupérer de logique Solana ou DEX profonde.
## 3. État actuel autour de `0.7.37-A`
## 3. État actuel après `0.7.38`
### 3.1. Socle stabilisé à ne pas refactorer maintenant
@@ -232,20 +232,18 @@ Les tests peuvent rester plus souples lorsque cela clarifie le test.
## 8. Priorité immédiate
La phase actuelle est `0.7.37_token_metadata_catalog_enrichment`.
La phase `0.7.38_token_metadata_gap_prioritization` est considérée comme close. La clôture `0.7.38-B` ajoute un registre local minimal de tokens connus : `WSOL`, `USDC`, `USDT`, `JUP`, `RAY`, `BONK`. `USDC` et `USDT` restent les seuls ajouts stable-quote ; `JUP`, `RAY` et `BONK` enrichissent seulement laffichage metadata sans changer la classification de quote. La prochaine étape est `0.7.39_launch_surfaces`.
Objectif : rendre le catalogue local exploitable visuellement et analytiquement sans toucher aux invariants de décodage/trade validés en `0.7.36`.
Préconditions validées avant de reprendre le codage :
À faire en priorité :
1. les invariants locaux restent sains : `blockingIssueCount = 0`, `actionableMissingTradeEventCount = 0`, `missingTradeEventCount = 0` ;
2. les profils `0.7.36_meteora_family_consolidation`, `0.7.37_token_metadata_catalog_enrichment` et `0.7.38_token_metadata_gap_prioritization` existent et passent sur le corpus local fourni ;
3. les metadata manquantes restent non bloquantes ;
4. les `tokenMetadataGapSamples` exposent maintenant une liste de travail priorisée ;
5. le registre local minimal contient `SOL`, `WSOL`, `USDC` et `USDT` ;
6. le backfill metadata peut traiter les tokens déjà présents en base et rafraîchir les `pair_symbol` sans recréer les objets de marché.
1. ajouter ou compléter un registre local des mints connus : `SOL`, `WSOL`, `USDC`, `USDT`, puis autres mints fréquents si vérifiés ;
2. améliorer le service de backfill metadata pour traiter les tokens déjà présents en base ;
3. exposer un résumé de metadata manquantes par asset class, protocole dorigine, DEX et quote asset ;
4. rafraîchir automatiquement ou explicitement les `pair_symbol` après mise à jour des tokens ;
5. ajouter une commande UI ou clarifier la commande existante pour relancer le metadata backfill ;
6. vérifier lidempotence : relancer le backfill metadata ne doit pas recréer tokens/pools/pairs/trades ;
7. conserver les compteurs DEX propres : `blockingIssueCount = 0`, `actionableMissingTradeEventCount = 0`, `missingTradeEventCount = 0` ;
8. préparer ensuite les launch surfaces, qui deviennent létape suivante de la roadmap.
Objectif de `0.7.39` : détecter et rattacher les surfaces de lancement sans inventer de program ids, sans produire de faux trades/candles et sans confondre `launch_origin`, `pool_origin`, `dex_effective` et `migration_target`.
## 9. Fichiers utiles pour reprendre dans une nouvelle session

View File

@@ -751,19 +751,17 @@ Réalisé :
### 6.059. Version `0.7.27` — Validation multi-DEX des connecteurs déjà branchés
Objectif : verrouiller la non-régression du pipeline actuel avant dajouter de nouveaux DEX ou douvrir la phase danalyse `0.8.x`.
À faire :
Réalisé / validé :
- rejouer des bases neuves de test pour `pump_fun`, `pump_swap`, `raydium_cpmm` et `raydium_clmm`,
- ne pas ajouter de nouveau DEX dans cette version ; cette version sert uniquement à valider les connecteurs déjà branchés,
- vérifier pour chaque DEX le triptyque `decoded_event_count / trade_event_count / pair_candle_count`,
- vérifier que les compteurs `diagnosticsClean`, `blockingIssueCount`, `actionableMissingTradeEventCount`, `duplicateDecodedEventTradeCount` et `duplicateCandleBucketCount` restent cohérents après replay,
- garantir que les événements non pricés ou non candle ne produisent pas de trade event invalide,
- conserver lenrichissement `eventCategory`, `tradeCandidate`, `candleCandidate`, `liquidityCandidate`, `feeCandidate`, `rewardCandidate`, `adminCandidate` et `poolLifecycleCandidate` dans `payload_json`,
- documenter les familles dévénements utilisées pour les candles et celles conservées seulement pour lanalyse, la liquidité, les frais, les rewards, ladministration ou la traçabilité,
- ajouter ou compléter les tests unitaires sur `dex_decode`, `dex_detect`, `trade_aggregation`, `pair_candle_aggregation`, `pair_analytic_signal`, `local_pipeline_replay` et `local_pipeline_diagnostics`,
- ajouter des requêtes SQL de diagnostic de référence pour contrôler rapidement les tables clés après backfill ou replay local,
- conserver la tolérance aux événements DEX partiels tout en refusant les trades sans montant ou prix exploitable,
- valider que les transactions échouées restent traçables dans les événements décodés sans produire de `k_sol_trade_events`.
- replay local et bases neuves de test utilisés pour stabiliser `pump_fun`, `pump_swap`, `raydium_cpmm` et `raydium_clmm` ;
- aucun nouveau DEX ajouté dans cette étape : la version a bien servi de verrou de non-régression ;
- vérification du triptyque `decoded_event_count / trade_event_count / pair_candle_count` par DEX ;
- garde-fous maintenus sur `diagnosticsClean`, `blockingIssueCount`, `actionableMissingTradeEventCount`, `duplicateDecodedEventTradeCount` et `duplicateCandleBucketCount` ;
- refus des trades sans montant ou prix exploitable ;
- conservation des transactions échouées comme decoded events traçables sans produire de `k_sol_trade_events` ;
- maintien des champs denrichissement dans `payload_json` : `eventCategory`, `tradeCandidate`, `candleCandidate`, `liquidityCandidate`, `feeCandidate`, `rewardCandidate`, `adminCandidate`, `poolLifecycleCandidate` ;
- couverture testée dans les zones critiques : `dex_decode`, `dex_detect`, `trade_aggregation`, `pair_candle_aggregation`, `pair_analytic_signal`, `local_pipeline_replay`, `local_pipeline_diagnostics` ;
- requêtes SQL de diagnostic conservées comme contrôle manuel après backfill ou replay local.
### 6.060. Version `0.7.28` — Refactor DEX commun et préparation extension
Réalisé :
@@ -888,16 +886,17 @@ Réalisé :
### 6.067. Version `0.7.35` — Événements non-trade v2 : fees, rewards et administration
Objectif : conserver les événements utiles au risque, au scoring, à léconomie du pool et à la traçabilité opérationnelle.
À faire :
Réalisé :
- ajouter `k_sol_fee_events`,
- ajouter `k_sol_reward_events`,
- ajouter `k_sol_pool_admin_events`,
- matérialiser les événements `collect_protocol_fee`, `collect_fund_fee`, `collect_creator_fee`, `collect_fee` et assimilés,
- matérialiser les événements `set_reward_params`, `initialize_reward`, `collect_reward`, `update_reward_infos` et assimilés,
- matérialiser les événements `set_config`, `update_config`, `set_authority`, `set_fee_rate`, `pause`, `resume` et assimilés,
- rattacher ces événements aux transactions, decoded events, pools, paires et wallets observés lorsque les comptes le permettent,
- documenter clairement que ces événements ne sont ni des trades ni des candles.
- ajout des tables `k_sol_fee_events`, `k_sol_reward_events` et `k_sol_pool_admin_events` ;
- ajout des DTO, entities, requêtes, index et re-exports associés ;
- matérialisation contrôlée des événements fees/rewards/admin lorsque la classification et les comptes le permettent ;
- rattachement aux transactions, decoded events, pools et paires lorsque les données disponibles sont fiables ;
- raccordement aux diagnostics locaux via `feeEventCount`, `rewardEventCount` et `poolAdminEventCount` ;
- ajout du profil `0.7.35_non_trade_fee_reward_admin` ;
- invariant maintenu : aucun fee/reward/admin ne produit de trade, metric ou candle.
Limite connue : le corpus local `0.7.38` ne contient pas encore dévénements fee/reward/admin matérialisés ; les compteurs peuvent donc rester à zéro sans bloquer la validation.
### 6.068. Version `0.7.36` — Meteora : DBC / DAMM v1 / DAMM v2 / DLMM
Réalisé :
@@ -915,26 +914,37 @@ Réalisé :
### 6.069. Version `0.7.37` — Token metadata et catalogue local
Objectif : rendre le catalogue local exploitable et lisible avant dajouter davantage de launch surfaces.
Réalisé en `0.7.37-A` :
Réalisé :
- ajout du profil `0.7.37_token_metadata_catalog_enrichment` ;
- exposition des compteurs metadata/catalog dans les diagnostics et le rapport de validation ;
- raccordement UI Demo Pipeline 2 au profil `0.7.37` ;
- maintien volontaire du caractère non bloquant des metadata manquantes.
- exposition des compteurs metadata/catalog dans les diagnostics et le rapport de validation : `tokenCount`, `tokenMetadataMissingCount`, `tradableTokenMetadataMissingCount`, `quoteTokenMetadataMissingCount`, `pairSymbolFallbackCount`, `pairSymbolResolvedCount`, `wsolQuotePairCount`, `stableQuotePairCount` ;
- raccordement UI Demo Pipeline 2 au profil `0.7.37` puis au profil `0.7.38` ;
- maintien volontaire du caractère non bloquant des metadata manquantes ;
- backfill metadata des tokens déjà présents dans `k_sol_tokens` sans nécessiter un nouveau backfill transactionnel ;
- enrichissement depuis les sources disponibles : registre local, payloads Pump.fun, comptes SPL/Token-2022, Metaplex lorsque le service dispose dun `HttpEndpointPool` ;
- rafraîchissement des `pair_symbol` après enrichissement des tokens ;
- commande UI disponible via `Demo Pipeline 2 > Replay local > Refresh missing token metadata` avec limite dédiée ;
- idempotence attendue : le backfill metadata met à jour tokens et symboles de paires sans recréer pools, paires, trades, candles ou origins ;
- registre local minimal consolidé : `SOL`, `WSOL`, `USDC`, `USDT`.
À faire :
Limite non bloquante : les diagnostics détaillés par origine de découverte restent une amélioration de confort ; létape `0.7.38` fournit déjà une liste priorisée exploitable via `tokenMetadataGapSamples`.
- ajouter ou consolider un registre local des mints connus et stables : `SOL`, `WSOL`, `USDC`, `USDT`, puis autres mints seulement si vérifiés ;
- améliorer le backfill metadata pour traiter les tokens déjà présents dans `k_sol_tokens` sans nécessiter un nouveau backfill transactionnel ;
- enrichir les tokens depuis les sources disponibles : registre local, payloads DEX, comptes Token-2022, Metaplex, transactions déjà persistées ;
- rafraîchir les `pair_symbol` après mise à jour des metadata de tokens ;
- exposer des diagnostics précis : `tokenMetadataMissingCount` par `dexCode`, `quoteAssetClass`, mint connu/inconnu et origine de découverte ;
- ajouter ou clarifier une commande UI dans `kb_demo_app` pour lancer le backfill metadata et rafraîchir le catalogue ;
- garantir lidempotence : relancer lenrichissement metadata ne doit pas recréer tokens, pools, paires, trades, candles ou origins ;
- conserver linvariant de validation : le manque de metadata nest pas un diagnostic bloquant tant que les trades/candles actionnables sont sains ;
- ajouter le profil de validation `0.7.37_token_metadata_catalog_enrichment`.
### 6.070. Version `0.7.38` — Priorisation des metadata manquantes
Objectif : transformer les compteurs metadata/catalog de `0.7.37` en liste daction priorisée sans rendre les metadata manquantes bloquantes.
### 6.070. Version `0.7.38` — Launch surfaces : LaunchLab, LetsBonk, Bags, Moonshot/Moonit, Boop.fun, Believe
Réalisé :
- ajout du profil `0.7.38_token_metadata_gap_prioritization` ;
- exposition de `tokenMetadataGapSamples` dans les diagnostics locaux, la validation et les bindings Demo Pipeline 2 ;
- priorisation des tokens manquants par usage : `tradable_quote_missing_metadata`, `tradable_token_missing_metadata`, `quote_token_missing_metadata`, puis `catalog_token_missing_metadata` ;
- raccordement Demo Pipeline 2 au nouveau profil par défaut ;
- conservation de linvariant : les metadata manquantes ne créent pas de blocking issue tant que les trades/candles actionnables restent sains ;
- validation locale confirmée avec `validationPassed = true`, `blockingIssueCount = 0`, `missingTradeEventCount = 0`, `decodedTradeCandidateWithoutTradeEventCount = 0` ;
- les samples permettent de sélectionner les prochains mints à enrichir via registre local, payloads DEX, Token-2022, Metaplex ou backfill HTTP.
Décision : `0.7.38` est clos. La clôture `0.7.38-B` conserve la logique de stable quotes limitée à `USDC`/`USDT` et ajoute un registre metadata local pour `JUP`, `RAY` et `BONK` sans les classer automatiquement comme quotes. La suite de développement commence à `0.7.39` avec les launch surfaces.
### 6.071. Version `0.7.39` — Launch surfaces : LaunchLab, LetsBonk, Bags, Moonshot/Moonit, Boop.fun, Believe
Objectif : détecter la première source de mint/lancement des tokens même lorsque le swap final se fait ailleurs.
À faire :
@@ -949,7 +959,7 @@ Objectif : détecter la première source de mint/lancement des tokens même lors
- rattacher les launch origins aux pools et paires lorsque les comptes permettent un matching fiable,
- exposer les origins dans les diagnostics et lUI dinspection.
### 6.071. Version `0.7.39` — Heaven : corpus, launch et AMM
### 6.072. Version `0.7.39` — Heaven : corpus, launch et AMM
Objectif : ajouter Heaven sans le classer trop tôt comme simple DEX ou simple launchpad.
À faire :
@@ -961,7 +971,7 @@ Objectif : ajouter Heaven sans le classer trop tôt comme simple DEX ou simple l
- documenter les limites si le corpus ne permet pas encore de matérialiser tous les événements,
- vérifier que Heaven ne crée pas de candles invalides en cas dévénement de launch non pricé.
### 6.072. Version `0.7.40` — Orca / FluxBeam / DexLab : corpus et validation ciblée
### 6.073. Version `0.7.40` — Orca / FluxBeam / DexLab : corpus et validation ciblée
Objectif : consolider les connecteurs déjà présents à partir de corpus locaux vérifiables.
À faire :
@@ -973,7 +983,7 @@ Objectif : consolider les connecteurs déjà présents à partir de corpus locau
- marquer explicitement les variantes partiellement supportées ou heuristiques,
- rejouer les corpus plusieurs fois pour vérifier lidempotence et labsence de trades/candles invalides.
### 6.073. Version `0.7.41` — Raydium AMM v4 legacy : corpus et validation ciblée
### 6.074. Version `0.7.41` — Raydium AMM v4 legacy : corpus et validation ciblée
Objectif : traiter le vrai Raydium AMM v4 historique après les autres Raydium, afin de lisoler de `raydium_cpmm`, `raydium_clmm` et des labels Raydium génériques.
À faire :
@@ -986,7 +996,7 @@ Objectif : traiter le vrai Raydium AMM v4 historique après les autres Raydium,
- renommer/stabiliser les fonctions internes autour de `raydium_amm_v4` pour éviter lambiguïté avec `raydium_cpmm` et `raydium_clmm`,
- documenter les limites connues si le corpus AMM v4 reste faible.
### 6.074. Version `0.7.42` — Validation DEX v1 consolidée
### 6.075. Version `0.7.42` — Validation DEX v1 consolidée
Objectif : rejouer tous les DEX et launch surfaces supportés et valider les invariants du pipeline complet.
À faire :
@@ -999,7 +1009,7 @@ Objectif : rejouer tous les DEX et launch surfaces supportés et valider les inv
- conserver une matrice de support par DEX, variante, instruction et type dévénement,
- verrouiller les invariants avant douvrir lanalyse `0.8.x`.
### 6.075. Version `0.7.43` — `kb_demo_app` : overlays analytiques
### 6.076. Version `0.7.43` — `kb_demo_app` : overlays analytiques
Objectif : rendre visibles les signaux analytiques directement sur les graphes et vues de marché.
À faire :
@@ -1010,7 +1020,7 @@ Objectif : rendre visibles les signaux analytiques directement sur les graphes e
- afficher un panneau latéral listant les signaux liés à une paire et à un timeframe,
- préparer lextension future vers Ichimoku, Kumo, projections ABCD et égalités temps/prix sans les mélanger au pipeline de décodage DEX.
### 6.076. Version `0.7.44` — `kb_demo_app` : vues consolidées token / pair / pool
### 6.077. Version `0.7.44` — `kb_demo_app` : vues consolidées token / pair / pool
Objectif : fournir une lecture métier plus confortable du modèle `0.7.x`.
À faire :
@@ -1022,7 +1032,7 @@ Objectif : fournir une lecture métier plus confortable du modèle `0.7.x`.
- préparer une navigation transversale entre objets techniques et objets métier,
- rendre explicites les cas `tradeCount = null`, `lastPriceQuotePerBase = null`, tokens non enrichis et événements conservés uniquement pour analyse.
### 6.077. Version `0.7.45` — Finition UI `0.7.x`
### 6.078. Version `0.7.45` — Finition UI `0.7.x`
Objectif : stabiliser la couche desktop de validation avant louverture de `0.8.x`.
À faire :
@@ -1033,7 +1043,7 @@ Objectif : stabiliser la couche desktop de validation avant louverture de `0.
- préparer une base UI suffisamment stable pour la future phase danalyse et filtrage `0.8.x`,
- vérifier que les commandes Tauri restent de simples façades vers `kb_lib`.
### 6.078. Version `0.7.x` — Couverture DEX v1
### 6.079. Version `0.7.x` — Couverture DEX v1
Objectif : structurer les connecteurs DEX autour dun pipeline complet de résolution, décodage, normalisation métier et classification des événements non-trade.
Protocoles et surfaces cibles :
@@ -1076,7 +1086,7 @@ Résultat attendu :
- préparation dune détection temps réel hybride et dun backfill ciblé compatible avec les mêmes objets métier,
- préparation dagrégats DEX plus riches, de candles/OHLCV et dune UI dinspection du pipeline `0.7.x`.
### 6.079. Version `0.8.x` — Analyse et filtrage
### 6.080. Version `0.8.x` — Analyse et filtrage
Objectif : transformer les événements bruts en signaux exploitables.
À faire :
@@ -1091,7 +1101,7 @@ Objectif : transformer les événements bruts en signaux exploitables.
- outils de sélection manuelle de points ABC et projection dun point D selon des règles temps/prix explicites,
- séparation stricte entre signaux analytiques observés, projections hypothétiques et décisions de trading.
### 6.080. Version `1.x.y` — Wallets et swap préparatoire
### 6.081. Version `1.x.y` — Wallets et swap préparatoire
Objectif : préparer la couche daction.
À faire :
@@ -1102,7 +1112,7 @@ Objectif : préparer la couche daction.
- préparation dordres et de swaps,
- simulation et garde-fous.
### 6.081. Version `2.x.y` — Trading semi-automatisé
### 6.082. Version `2.x.y` — Trading semi-automatisé
Objectif : brancher lanalyse à laction tout en gardant des garde-fous explicites.
À faire :
@@ -1113,7 +1123,7 @@ Objectif : brancher lanalyse à laction tout en gardant des garde-fous exp
- confirmations explicites ou semi-automatiques,
- journaux dexécution.
### 6.082. Version `3.x.y` — Yellowstone gRPC
### 6.083. Version `3.x.y` — Yellowstone gRPC
Objectif : ajouter le connecteur gRPC dédié.
À faire :
@@ -1243,17 +1253,21 @@ Le projet doit maintenir au minimum :
## 12. Priorité immédiate
La priorité immédiate reste de terminer `0.7.37_token_metadata_catalog_enrichment` par le backfill metadata effectif et le rafraîchissement des symboles de paires.
La priorité immédiate est `0.7.39_launch_surfaces` : détecter et rattacher les origines de lancement sans casser les invariants `0.7.36` à `0.7.38`.
Ordre de travail recommandé :
Préconditions validées avant `0.7.39` :
1. conserver la validation acquise `0.7.36` : Meteora consolidé, transactions failed traçables mais non actionnables, swaps sans amounts classés `non_actionable_trade`, aucun diagnostic bloquant masqué ;
2. améliorer le catalogue local : metadata de tokens, symboles, noms, asset classes et `pair_symbol` lisibles ;
3. ajouter un registre local des mints connus et stables : `SOL`, `WSOL`, `USDC`, `USDT`, puis autres mints seulement après vérification ;
4. permettre un backfill metadata idempotent sur les tokens déjà présents sans relancer un backfill transactionnel complet ;
5. exposer les compteurs de metadata manquantes par DEX, asset class, quote asset et origine de découverte ;
6. ajouter ou clarifier la commande UI permettant de relancer le backfill metadata et le refresh du catalogue ;
7. vérifier que lenrichissement metadata ne modifie pas les invariants DEX : pas de faux trades, pas de fausses candles, pas de recréation de pools/paires ;
8. déplacer ensuite les launch surfaces vers `0.7.38` : Raydium LaunchLab/Launchpad, LetsBonk/Bonk.fun, Boop.fun, Moonshot/Moonit, Believe et Bags ;
9. traiter Heaven en `0.7.39`, puis Orca/FluxBeam/DexLab, puis Raydium AMM v4 legacy ;
10. effectuer une validation DEX v1 consolidée avant douvrir réellement `0.8.x` pour lanalyse, les filtres, les patterns et les projections graphiques.
1. validation `0.7.36` acquise : Meteora consolidé, transactions failed traçables mais non actionnables, swaps sans amounts classés `non_actionable_trade`, aucun diagnostic bloquant masqué ;
2. validation `0.7.37` acquise : compteurs metadata/catalog exposés, backfill metadata idempotent, `pair_symbol` rafraîchissables, metadata manquantes non bloquantes ;
3. validation `0.7.38` acquise : `tokenMetadataGapSamples` priorisés, Demo Pipeline 2 raccordé, `validationPassed = true`, `blockingIssueCount = 0`, registre local `WSOL`/`USDC`/`USDT`/`JUP`/`RAY`/`BONK` disponible ;
4. registre local minimal disponible : `SOL`, `WSOL`, `USDC`, `USDT` ;
5. les diagnostics locaux restent loutil de vérité pour décider si une surface peut passer de `planned` à `partial` ou `supported`.
Ordre de travail recommandé pour `0.7.39` :
1. scanner `launch_origin.rs`, `pool_origin.rs`, `protocol_candidate_recording.rs`, `dex_support_matrix.rs`, `transaction_classification.rs`, `dex_decode.rs` et `dex_detect.rs` ;
2. identifier les surfaces candidates déjà présentes dans la matrice : Raydium LaunchLab/Launchpad, LetsBonk/Bonk.fun, Bags, Moonshot/Moonit, Boop.fun et Believe ;
3. ne promouvoir une surface que si le corpus prouve le program id, les comptes/authorities et la migration vers le DEX effectif ;
4. distinguer explicitement `launch_origin`, `pool_origin`, `dex_effective` et `migration_target` ;
5. exposer les origins dans les diagnostics et lUI dinspection ;
6. conserver les garde-fous : pas de faux trade, pas de fausse candle, pas de program id inventé, pas de metadata manquante bloquante.

View File

@@ -166,7 +166,8 @@
<div class="mb-3">
<label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label>
<select id="demoPipeline2ValidationProfileSelect" class="form-select">
<option value="0.7.37_token_metadata_catalog_enrichment" selected>0.7.37 — token metadata/catalog enrichment</option>
<option value="0.7.38_token_metadata_gap_prioritization" selected>0.7.38 — token metadata gap prioritization</option>
<option value="0.7.37_token_metadata_catalog_enrichment">0.7.37 — token metadata/catalog enrichment</option>
<option value="0.7.36_meteora_family_consolidation">0.7.36 — Meteora family consolidation</option>
<option value="0.7.35_non_trade_fee_reward_admin">0.7.35 — non-trade fee/reward admin</option>
<option value="0.7.34_non_trade_liquidity_lifecycle">0.7.34 — non-trade liquidity/lifecycle</option>

View File

@@ -11,6 +11,7 @@ import type { DemoPipeline2LocalPairActionabilityDiagnosticSummary } from "./Dem
import type { DemoPipeline2LocalPairDiagnosticSummary } from "./DemoPipeline2LocalPairDiagnosticSummary";
import type { DemoPipeline2LocalPairGapDiagnosticSample } from "./DemoPipeline2LocalPairGapDiagnosticSample";
import type { DemoPipeline2LocalPairTradingReadinessDiagnosticSummary } from "./DemoPipeline2LocalPairTradingReadinessDiagnosticSummary";
import type { DemoPipeline2LocalTokenMetadataGapDiagnosticSample } from "./DemoPipeline2LocalTokenMetadataGapDiagnosticSample";
/**
* Local pipeline diagnostics summary for the UI.
@@ -60,6 +61,18 @@ liquidityEventCount: number,
* Total persisted pool lifecycle events.
*/
poolLifecycleEventCount: number,
/**
* Total persisted fee events.
*/
feeEventCount: number,
/**
* Total persisted reward events.
*/
rewardEventCount: number,
/**
* Total persisted pool administration events.
*/
poolAdminEventCount: number,
/**
* Whether the local persisted pipeline has no blocking diagnostic issue.
*/
@@ -125,6 +138,30 @@ tokenCount: number,
* Total tokens missing symbol or name.
*/
tokenMetadataMissingCount: number,
/**
* Total tokens used by trade-materialized pairs that still miss symbol or name.
*/
tradableTokenMetadataMissingCount: number,
/**
* Total quote-side tokens used by pairs that still miss symbol or name.
*/
quoteTokenMetadataMissingCount: number,
/**
* Total pairs whose display symbol is missing or still falls back to raw mints.
*/
pairSymbolFallbackCount: number,
/**
* Total pairs whose display symbol is present and does not include raw mints.
*/
pairSymbolResolvedCount: number,
/**
* Total pairs whose quote token is WSOL.
*/
wsolQuotePairCount: number,
/**
* Total pairs whose quote token is a known stable quote.
*/
stableQuotePairCount: number,
/**
* Total known pools.
*/
@@ -209,6 +246,10 @@ eventClassificationSummaries: Array<DemoPipeline2LocalEventClassificationDiagnos
* Missing trade events grouped by diagnostic reason.
*/
missingTradeEventReasonSummaries: Array<DemoPipeline2LocalMissingTradeEventReasonSummary>,
/**
* Prioritized samples of tokens whose display metadata is still incomplete.
*/
tokenMetadataGapSamples: Array<DemoPipeline2LocalTokenMetadataGapDiagnosticSample>,
/**
* Total pairs with only non-actionable missing trade events.
*/

View File

@@ -50,6 +50,50 @@ liquidityEventCount: number,
* Total persisted pool lifecycle events.
*/
poolLifecycleEventCount: number,
/**
* Total persisted fee events.
*/
feeEventCount: number,
/**
* Total persisted reward events.
*/
rewardEventCount: number,
/**
* Total persisted pool administration events.
*/
poolAdminEventCount: number,
/**
* Total known tokens.
*/
tokenCount: number,
/**
* Total tokens missing symbol or name.
*/
tokenMetadataMissingCount: number,
/**
* Total tokens used by trade-materialized pairs that still miss symbol or name.
*/
tradableTokenMetadataMissingCount: number,
/**
* Total quote-side tokens used by pairs that still miss symbol or name.
*/
quoteTokenMetadataMissingCount: number,
/**
* Total pairs whose display symbol is missing or still falls back to raw mints.
*/
pairSymbolFallbackCount: number,
/**
* Total pairs whose display symbol is present and does not include raw mints.
*/
pairSymbolResolvedCount: number,
/**
* Total pairs whose quote token is WSOL.
*/
wsolQuotePairCount: number,
/**
* Total pairs whose quote token is a known stable quote.
*/
stableQuotePairCount: number,
/**
* Number of entries currently exposed by the DEX support matrix.
*/

View File

@@ -0,0 +1,54 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* Local token metadata gap sample for the UI.
*/
export type DemoPipeline2LocalTokenMetadataGapDiagnosticSample = {
/**
* Token id.
*/
tokenId: number,
/**
* Mint address.
*/
mint: string,
/**
* Current symbol, when present.
*/
symbol: string | null,
/**
* Current name, when present.
*/
name: string | null,
/**
* Current mint decimals, when known.
*/
decimals: number | null,
/**
* Token program id.
*/
tokenProgram: string,
/**
* Whether this token row is flagged as quote token.
*/
isQuoteToken: boolean,
/**
* Whether this token appears in at least one trade-materialized pair.
*/
usedByTradeMaterializedPair: boolean,
/**
* Whether this token appears on the quote side of at least one pair.
*/
usedAsQuoteToken: boolean,
/**
* Number of trade-materialized pairs using this token.
*/
tradeMaterializedPairCount: number,
/**
* Number of catalog pairs using this token.
*/
totalPairCount: number,
/**
* Prioritization bucket.
*/
priority: string, };

View File

@@ -1,7 +1,7 @@
{
"name": "kb-demo-app",
"private": true,
"version": "0.7.37",
"version": "0.7.38",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -451,6 +451,9 @@ pub(crate) struct DemoPipeline2LocalPipelineDiagnosticSummary {
/// Missing trade events grouped by diagnostic reason.
pub missing_trade_event_reason_summaries:
std::vec::Vec<DemoPipeline2LocalMissingTradeEventReasonSummary>,
/// Prioritized samples of tokens whose display metadata is still incomplete.
pub token_metadata_gap_samples:
std::vec::Vec<DemoPipeline2LocalTokenMetadataGapDiagnosticSample>,
/// Total pairs with only non-actionable missing trade events.
#[ts(type = "number")]
pub non_actionable_pair_count: i64,
@@ -893,6 +896,44 @@ pub(crate) struct DemoPipeline2LocalPairGapDiagnosticSample {
pub pair_candle_count: i64,
}
/// Local token metadata gap sample for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/DemoPipeline2LocalTokenMetadataGapDiagnosticSample.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalTokenMetadataGapDiagnosticSample {
/// Token id.
#[ts(type = "number")]
pub token_id: i64,
/// Mint address.
pub mint: std::string::String,
/// Current symbol, when present.
pub symbol: std::option::Option<std::string::String>,
/// Current name, when present.
pub name: std::option::Option<std::string::String>,
/// Current mint decimals, when known.
#[ts(type = "number | null")]
pub decimals: std::option::Option<i64>,
/// Token program id.
pub token_program: std::string::String,
/// Whether this token row is flagged as quote token.
pub is_quote_token: bool,
/// Whether this token appears in at least one trade-materialized pair.
pub used_by_trade_materialized_pair: bool,
/// Whether this token appears on the quote side of at least one pair.
pub used_as_quote_token: bool,
/// Number of trade-materialized pairs using this token.
#[ts(type = "number")]
pub trade_materialized_pair_count: i64,
/// Number of catalog pairs using this token.
#[ts(type = "number")]
pub total_pair_count: i64,
/// Prioritization bucket.
pub priority: std::string::String,
}
/// One token item for the local catalog.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/DemoPipeline2TokenItem.ts")]
@@ -1147,7 +1188,7 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
let service = kb_lib::LocalPipelineValidationService::new(database.clone());
let profile_code = match request {
Some(request) => request.profile_code,
None => "0.7.37_token_metadata_catalog_enrichment".to_string(),
None => "0.7.38_token_metadata_gap_prioritization".to_string(),
};
let run_result = match profile_code.as_str() {
"0.7.27" | "0.7.27_dexes_non_regression" => {
@@ -1183,6 +1224,9 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
"0.7.37" | "0.7.37_token_metadata_catalog_enrichment" => {
service.validate_v0_7_37_current_database().await
},
"0.7.38" | "0.7.38_token_metadata_gap_prioritization" => {
service.validate_v0_7_38_current_database().await
},
other => Err(kb_lib::Error::InvalidState(format!(
"unsupported local pipeline validation profile: {other}"
))),
@@ -1733,6 +1777,10 @@ fn demo_pipeline2_map_local_diagnostics_summary(
multi_trade_signature_pair_samples
.push(demo_pipeline2_map_multi_trade_signature_pair_sample(sample));
}
let mut token_metadata_gap_samples = std::vec::Vec::new();
for sample in summary.token_metadata_gap_samples {
token_metadata_gap_samples.push(demo_pipeline2_map_token_metadata_gap_sample(sample));
}
let mut pair_without_trade_samples = std::vec::Vec::new();
for sample in summary.pair_without_trade_samples {
pair_without_trade_samples.push(demo_pipeline2_map_pair_gap_sample(sample));
@@ -1805,6 +1853,7 @@ fn demo_pipeline2_map_local_diagnostics_summary(
decoded_event_summaries,
event_classification_summaries,
missing_trade_event_reason_summaries,
token_metadata_gap_samples,
non_actionable_pair_count: summary.non_actionable_pair_count,
non_actionable_pair_summaries,
missing_trade_event_samples,
@@ -2003,6 +2052,25 @@ fn demo_pipeline2_map_multi_trade_signature_pair_sample(
};
}
fn demo_pipeline2_map_token_metadata_gap_sample(
sample: kb_lib::LocalTokenMetadataGapDiagnosticSampleDto,
) -> DemoPipeline2LocalTokenMetadataGapDiagnosticSample {
return DemoPipeline2LocalTokenMetadataGapDiagnosticSample {
token_id: sample.token_id,
mint: sample.mint,
symbol: sample.symbol,
name: sample.name,
decimals: sample.decimals,
token_program: sample.token_program,
is_quote_token: sample.is_quote_token,
used_by_trade_materialized_pair: sample.used_by_trade_materialized_pair,
used_as_quote_token: sample.used_as_quote_token,
trade_materialized_pair_count: sample.trade_materialized_pair_count,
total_pair_count: sample.total_pair_count,
priority: sample.priority,
};
}
fn demo_pipeline2_map_pair_gap_sample(
sample: kb_lib::LocalPairGapDiagnosticSampleDto,
) -> DemoPipeline2LocalPairGapDiagnosticSample {

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "kb-demo-app",
"version": "0.7.37",
"version": "0.7.38",
"identifier": "com.sasedev.kb-demo-app",
"build": {
"beforeDevCommand": "npm run dev",

View File

@@ -143,6 +143,21 @@ pub const ZK_ELGAMAL_PROOF_PROGRAM_ID: &str = "ZkE1Gama1Proof1111111111111111111
/// @see solana_sdk::pubkey::Pubkey = spl_token_interface::native_mint::ID
pub const WSOL_MINT_ID: &str = "So11111111111111111111111111111111111111112";
/// Canonical Solana USDC mint identifier.
pub const USDC_MINT_ID: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
/// Canonical Solana USDT mint identifier.
pub const USDT_MINT_ID: &str = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB";
/// Canonical Jupiter governance token mint identifier.
pub const JUP_MINT_ID: &str = "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN";
/// Canonical Raydium token mint identifier.
pub const RAY_MINT_ID: &str = "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R";
/// Canonical Bonk token mint identifier.
pub const BONK_MINT_ID: &str = "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263";
/// DexLab Swap/Pool program id. ("DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N").
pub const DEXLAB_PROGRAM_ID: &str = "DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N";

View File

@@ -44,6 +44,7 @@ pub use dtos::LocalPairGapDiagnosticSampleDto;
pub use dtos::LocalPairTradingReadinessDiagnosticSummaryDto;
pub use dtos::LocalPipelineDiagnosticCountersDto;
pub use dtos::LocalPipelineDiagnosticSummaryDto;
pub use dtos::LocalTokenMetadataGapDiagnosticSampleDto;
pub use dtos::ObservedTokenDto;
pub use dtos::OnchainObservationDto;
pub use dtos::PairAnalyticSignalDto;
@@ -170,6 +171,7 @@ pub use queries::query_local_pair_without_candle_diagnostic_list_samples;
pub use queries::query_local_pair_without_trade_diagnostic_list_samples;
pub use queries::query_local_pipeline_diagnostic_get_counters;
pub use queries::query_local_pipeline_diagnostic_list_summaries;
pub use queries::query_local_token_metadata_gap_diagnostic_list_samples;
pub use queries::query_observed_tokens_get_by_mint;
pub use queries::query_observed_tokens_list;
pub use queries::query_observed_tokens_upsert;

View File

@@ -58,6 +58,7 @@ pub(crate) use local_pipeline_diagnostics::LocalPairDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPairGapDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalPairTradingReadinessDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPipelineDiagnosticCountersRow;
pub(crate) use local_pipeline_diagnostics::LocalTokenMetadataGapDiagnosticSampleRow;
pub use analysis_signal::AnalysisSignalDto;
pub use chain_instruction::ChainInstructionDto;
@@ -88,6 +89,7 @@ pub use local_pipeline_diagnostics::LocalPairGapDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalPairTradingReadinessDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticCountersDto;
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalTokenMetadataGapDiagnosticSampleDto;
pub use observed_token::ObservedTokenDto;
pub use onchain_observation::OnchainObservationDto;
pub use pair::PairDto;

View File

@@ -138,6 +138,8 @@ pub struct LocalPipelineDiagnosticSummaryDto {
/// Missing trade events grouped by diagnostic reason.
pub missing_trade_event_reason_summaries:
std::vec::Vec<crate::LocalMissingTradeEventReasonSummaryDto>,
/// Prioritized samples of tokens whose display metadata is still incomplete.
pub token_metadata_gap_samples: std::vec::Vec<crate::LocalTokenMetadataGapDiagnosticSampleDto>,
/// Total pairs with only non-actionable missing trade events.
pub non_actionable_pair_count: i64,
/// Pair summaries for non-actionable missing trade events.
@@ -712,6 +714,35 @@ pub struct LocalPairGapDiagnosticSampleDto {
pub pair_candle_count: i64,
}
/// Prioritized sample of an incomplete token metadata row.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalTokenMetadataGapDiagnosticSampleDto {
/// Token id.
pub token_id: i64,
/// Mint address.
pub mint: std::string::String,
/// Current symbol, when present.
pub symbol: std::option::Option<std::string::String>,
/// Current name, when present.
pub name: std::option::Option<std::string::String>,
/// Current mint decimals, when known.
pub decimals: std::option::Option<i64>,
/// Token program id.
pub token_program: std::string::String,
/// Whether this token row is flagged as quote token.
pub is_quote_token: bool,
/// Whether the token appears in at least one pair with materialized trades.
pub used_by_trade_materialized_pair: bool,
/// Whether the token appears on the quote side of at least one pair.
pub used_as_quote_token: bool,
/// Number of trade-materialized pairs using this token.
pub trade_materialized_pair_count: i64,
/// Number of catalog pairs using this token.
pub total_pair_count: i64,
/// Human-readable prioritization bucket.
pub priority: std::string::String,
}
/// SQL row for missing trade event reason summaries.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalMissingTradeEventReasonSummaryRow {
@@ -803,3 +834,20 @@ pub(crate) struct LocalPairGapDiagnosticSampleRow {
pub(crate) trade_event_count: i64,
pub(crate) pair_candle_count: i64,
}
/// SQL row for incomplete token metadata samples.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalTokenMetadataGapDiagnosticSampleRow {
pub(crate) token_id: i64,
pub(crate) mint: std::string::String,
pub(crate) symbol: std::option::Option<std::string::String>,
pub(crate) name: std::option::Option<std::string::String>,
pub(crate) decimals: std::option::Option<i64>,
pub(crate) token_program: std::string::String,
pub(crate) is_quote_token: i64,
pub(crate) used_by_trade_materialized_pair: i64,
pub(crate) used_as_quote_token: i64,
pub(crate) trade_materialized_pair_count: i64,
pub(crate) total_pair_count: i64,
pub(crate) priority: std::string::String,
}

View File

@@ -102,6 +102,7 @@ pub use local_pipeline_diagnostics::query_local_pair_without_candle_diagnostic_l
pub use local_pipeline_diagnostics::query_local_pair_without_trade_diagnostic_list_samples;
pub use local_pipeline_diagnostics::query_local_pipeline_diagnostic_get_counters;
pub use local_pipeline_diagnostics::query_local_pipeline_diagnostic_list_summaries;
pub use local_pipeline_diagnostics::query_local_token_metadata_gap_diagnostic_list_samples;
pub use observed_token::query_observed_tokens_get_by_mint;
pub use observed_token::query_observed_tokens_list;
pub use observed_token::query_observed_tokens_upsert;

View File

@@ -1678,6 +1678,104 @@ LIMIT ?
}
}
/// Lists prioritized samples of tokens whose metadata is still incomplete.
pub async fn query_local_token_metadata_gap_diagnostic_list_samples(
database: &crate::Database,
limit: i64,
) -> Result<std::vec::Vec<crate::LocalTokenMetadataGapDiagnosticSampleDto>, crate::Error> {
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let rows_result = sqlx::query_as::<
sqlx::Sqlite,
crate::db::dtos::LocalTokenMetadataGapDiagnosticSampleRow,
>(
r#"
WITH token_pair_usage AS (
SELECT
token.id AS token_id,
COUNT(DISTINCT pair.id) AS total_pair_count,
COUNT(DISTINCT CASE WHEN te.id IS NOT NULL THEN pair.id END) AS trade_materialized_pair_count,
COUNT(DISTINCT CASE WHEN pair.quote_token_id = token.id THEN pair.id END) AS quote_pair_count
FROM k_sol_tokens token
LEFT JOIN k_sol_pairs pair
ON pair.base_token_id = token.id
OR pair.quote_token_id = token.id
LEFT JOIN k_sol_trade_events te ON te.pair_id = pair.id
GROUP BY token.id
)
SELECT
token.id AS token_id,
token.mint AS mint,
token.symbol AS symbol,
token.name AS name,
token.decimals AS decimals,
token.token_program AS token_program,
token.is_quote_token AS is_quote_token,
CASE WHEN usage.trade_materialized_pair_count > 0 THEN 1 ELSE 0 END AS used_by_trade_materialized_pair,
CASE WHEN usage.quote_pair_count > 0 THEN 1 ELSE 0 END AS used_as_quote_token,
COALESCE(usage.trade_materialized_pair_count, 0) AS trade_materialized_pair_count,
COALESCE(usage.total_pair_count, 0) AS total_pair_count,
CASE
WHEN usage.trade_materialized_pair_count > 0 AND usage.quote_pair_count > 0 THEN 'tradable_quote_missing_metadata'
WHEN usage.trade_materialized_pair_count > 0 THEN 'tradable_token_missing_metadata'
WHEN usage.quote_pair_count > 0 THEN 'quote_token_missing_metadata'
ELSE 'catalog_token_missing_metadata'
END AS priority
FROM k_sol_tokens token
LEFT JOIN token_pair_usage usage ON usage.token_id = token.id
WHERE token.symbol IS NULL
OR TRIM(token.symbol) = ''
OR token.name IS NULL
OR TRIM(token.name) = ''
ORDER BY
CASE
WHEN usage.trade_materialized_pair_count > 0 AND usage.quote_pair_count > 0 THEN 1
WHEN usage.trade_materialized_pair_count > 0 THEN 2
WHEN usage.quote_pair_count > 0 THEN 3
ELSE 4
END,
usage.trade_materialized_pair_count DESC,
usage.total_pair_count DESC,
token.mint
LIMIT ?
"#,
)
.bind(limit)
.fetch_all(pool)
.await;
let rows = match rows_result {
Ok(rows) => rows,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list token metadata gap diagnostic samples on sqlite: {}",
error
)));
},
};
let mut samples = std::vec::Vec::new();
for row in rows {
samples.push(crate::LocalTokenMetadataGapDiagnosticSampleDto {
token_id: row.token_id,
mint: row.mint,
symbol: row.symbol,
name: row.name,
decimals: row.decimals,
token_program: row.token_program,
is_quote_token: sqlite_i64_to_bool(row.is_quote_token),
used_by_trade_materialized_pair: sqlite_i64_to_bool(
row.used_by_trade_materialized_pair,
),
used_as_quote_token: sqlite_i64_to_bool(row.used_as_quote_token),
trade_materialized_pair_count: row.trade_materialized_pair_count,
total_pair_count: row.total_pair_count,
priority: row.priority,
});
}
return Ok(samples);
},
}
}
/// Lists samples of pairs without trade events.
pub async fn query_local_pair_without_trade_diagnostic_list_samples(
database: &crate::Database,

View File

@@ -266,6 +266,16 @@ pub use constants::SYSVAR_STAKE_HISTORY_PROGRAM_ID;
/// Vote program identifier. ("Vote111111111111111111111111111111111111111").
/// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::vote::ID
pub use constants::VOTE_PROGRAM_ID;
/// Canonical Bonk token mint identifier.
pub use constants::BONK_MINT_ID;
/// Canonical Jupiter governance token mint identifier.
pub use constants::JUP_MINT_ID;
/// Canonical Raydium token mint identifier.
pub use constants::RAY_MINT_ID;
/// Canonical Solana USDC mint identifier.
pub use constants::USDC_MINT_ID;
/// Canonical Solana USDT mint identifier.
pub use constants::USDT_MINT_ID;
/// Wrapped SOL mint identifier. ("So11111111111111111111111111111111111111112").
/// @see solana_sdk::pubkey::Pubkey = spl_token_interface::native_mint::ID
pub use constants::WSOL_MINT_ID;
@@ -375,6 +385,8 @@ pub use db::LocalPairTradingReadinessDiagnosticSummaryDto;
pub use db::LocalPipelineDiagnosticCountersDto;
/// Local pipeline diagnostics summary.
pub use db::LocalPipelineDiagnosticSummaryDto;
/// Prioritized sample of an incomplete token metadata row.
pub use db::LocalTokenMetadataGapDiagnosticSampleDto;
/// Source family for one on-chain observation.
pub use db::ObservationSourceKind;
/// Application-facing observed token DTO.
@@ -613,6 +625,8 @@ pub use db::query_local_pair_without_trade_diagnostic_list_samples;
pub use db::query_local_pipeline_diagnostic_get_counters;
/// Lists local DEX diagnostic summaries.
pub use db::query_local_pipeline_diagnostic_list_summaries;
/// Lists prioritized token metadata gap diagnostic samples.
pub use db::query_local_token_metadata_gap_diagnostic_list_samples;
/// Reads one observed token by mint.
pub use db::query_observed_tokens_get_by_mint;
/// Lists observed tokens ordered by newest first.

View File

@@ -75,6 +75,16 @@ impl LocalPipelineDiagnosticsService {
Ok(summaries) => summaries,
Err(error) => return Err(error),
};
let token_metadata_gap_samples_result =
crate::query_local_token_metadata_gap_diagnostic_list_samples(
self.database.as_ref(),
sample_limit,
)
.await;
let token_metadata_gap_samples = match token_metadata_gap_samples_result {
Ok(samples) => samples,
Err(error) => return Err(error),
};
let non_actionable_pair_summaries_result =
crate::query_local_non_actionable_pair_diagnostic_list_summaries(
self.database.as_ref(),
@@ -206,6 +216,7 @@ impl LocalPipelineDiagnosticsService {
decoded_event_summaries,
event_classification_summaries,
missing_trade_event_reason_summaries,
token_metadata_gap_samples,
non_actionable_pair_count: counters.non_actionable_pair_count,
non_actionable_pair_summaries,
missing_trade_event_samples,

View File

@@ -266,6 +266,17 @@ impl LocalPipelineValidationConfig {
config.profile_code = "0.7.37_token_metadata_catalog_enrichment".to_string();
return config;
}
/// Builds the `0.7.38` token metadata gap prioritization validation config.
///
/// This profile keeps the `0.7.37` metadata counters and exposes
/// prioritized token metadata gap samples so the next backfill targets are
/// visible without making missing metadata a blocking validation issue.
pub fn v0_7_38_token_metadata_gap_prioritization() -> Self {
let mut config = Self::v0_7_37_token_metadata_catalog_enrichment();
config.profile_code = "0.7.38_token_metadata_gap_prioritization".to_string();
return config;
}
}
/// A single local pipeline validation issue.
@@ -485,6 +496,15 @@ impl LocalPipelineValidationService {
crate::LocalPipelineValidationConfig::v0_7_37_token_metadata_catalog_enrichment();
return self.validate_current_database(&config).await;
}
/// Diagnoses the current database with the `0.7.38` metadata-gap profile.
pub async fn validate_v0_7_38_current_database(
&self,
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
let config =
crate::LocalPipelineValidationConfig::v0_7_38_token_metadata_gap_prioritization();
return self.validate_current_database(&config).await;
}
}
/// Validates a diagnostics summary without performing database access.
@@ -606,7 +626,8 @@ pub fn validate_local_pipeline_diagnostics_summary(
== "0.7.34_non_trade_liquidity_lifecycle"
|| config.profile_code == "0.7.35_non_trade_fee_reward_admin"
|| config.profile_code == "0.7.36_meteora_family_consolidation"
|| config.profile_code == "0.7.37_token_metadata_catalog_enrichment";
|| config.profile_code == "0.7.37_token_metadata_catalog_enrichment"
|| config.profile_code == "0.7.38_token_metadata_gap_prioritization";
if config.require_all_expected_dexes || missing_expected_dex_is_warning {
for expected_dex_code in &expected_dex_codes {
if !observed_dex_codes.contains(expected_dex_code) {
@@ -1040,6 +1061,7 @@ mod tests {
decoded_event_summaries: vec![],
event_classification_summaries: vec![],
missing_trade_event_reason_summaries: vec![],
token_metadata_gap_samples: vec![],
non_actionable_pair_summaries: vec![],
missing_trade_event_samples: vec![],
duplicate_decoded_event_trade_samples: vec![],
@@ -1300,6 +1322,39 @@ mod tests {
assert_eq!(report.stable_quote_pair_count, 2);
}
#[test]
fn validation_accepts_0_7_38_metadata_gap_samples() {
let mut summary = make_0_7_28_summary_with_meteora();
summary.token_count = 108;
summary.token_metadata_missing_count = 102;
summary.tradable_token_metadata_missing_count = 6;
summary.quote_token_metadata_missing_count = 5;
summary
.token_metadata_gap_samples
.push(crate::LocalTokenMetadataGapDiagnosticSampleDto {
token_id: 42,
mint: "MissingMetaMint111".to_string(),
symbol: None,
name: None,
decimals: Some(6),
token_program: crate::SPL_TOKEN_PROGRAM_ID.to_string(),
is_quote_token: false,
used_by_trade_materialized_pair: true,
used_as_quote_token: false,
trade_materialized_pair_count: 2,
total_pair_count: 3,
priority: "tradable_token_missing_metadata".to_string(),
});
let config =
crate::LocalPipelineValidationConfig::v0_7_38_token_metadata_gap_prioritization();
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
assert!(report.validation_passed);
assert_eq!(report.validation_profile_code, "0.7.38_token_metadata_gap_prioritization");
assert_eq!(report.blocking_issue_count, 0);
assert_eq!(report.token_metadata_missing_count, 102);
assert_eq!(summary.token_metadata_gap_samples.len(), 1);
}
#[test]
fn validation_rejects_0_7_33_pair_trading_readiness_mismatch() {
let mut summary = make_0_7_28_summary_with_meteora();

View File

@@ -13,6 +13,67 @@ const WRAPPED_SOL_SYMBOL: &str = "WSOL";
const WRAPPED_SOL_NAME: &str = "Wrapped SOL";
const METAPLEX_TOKEN_METADATA_PROGRAM_ID: &str = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
#[derive(Debug, Clone, Copy)]
struct KnownLocalTokenMetadata {
mint: &'static str,
symbol: &'static str,
name: &'static str,
decimals: u8,
token_program: &'static str,
is_quote_token: std::option::Option<bool>,
}
const KNOWN_LOCAL_TOKEN_METADATA: &[KnownLocalTokenMetadata] = &[
KnownLocalTokenMetadata {
mint: crate::WSOL_MINT_ID,
symbol: WRAPPED_SOL_SYMBOL,
name: WRAPPED_SOL_NAME,
decimals: 9,
token_program: crate::SPL_TOKEN_PROGRAM_ID,
is_quote_token: Some(true),
},
KnownLocalTokenMetadata {
mint: crate::USDC_MINT_ID,
symbol: "USDC",
name: "USD Coin",
decimals: 6,
token_program: crate::SPL_TOKEN_PROGRAM_ID,
is_quote_token: Some(true),
},
KnownLocalTokenMetadata {
mint: crate::USDT_MINT_ID,
symbol: "USDT",
name: "Tether USD",
decimals: 6,
token_program: crate::SPL_TOKEN_PROGRAM_ID,
is_quote_token: Some(true),
},
KnownLocalTokenMetadata {
mint: crate::JUP_MINT_ID,
symbol: "JUP",
name: "Jupiter",
decimals: 6,
token_program: crate::SPL_TOKEN_PROGRAM_ID,
is_quote_token: None,
},
KnownLocalTokenMetadata {
mint: crate::RAY_MINT_ID,
symbol: "RAY",
name: "Raydium",
decimals: 6,
token_program: crate::SPL_TOKEN_PROGRAM_ID,
is_quote_token: None,
},
KnownLocalTokenMetadata {
mint: crate::BONK_MINT_ID,
symbol: "BONK",
name: "Bonk",
decimals: 5,
token_program: crate::SPL_TOKEN_PROGRAM_ID,
is_quote_token: None,
},
];
/// Summary produced by a token metadata backfill pass.
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -321,15 +382,16 @@ fn resolve_local_token_metadata(mint: &str) -> std::option::Option<ResolvedToken
is_quote_token: Some(true),
});
}
let wsol_mint = crate::WSOL_MINT_ID.to_string();
if mint == wsol_mint {
return Some(ResolvedTokenMetadata {
symbol: Some(WRAPPED_SOL_SYMBOL.to_string()),
name: Some(WRAPPED_SOL_NAME.to_string()),
decimals: Some(9),
token_program: Some(crate::SPL_TOKEN_PROGRAM_ID.to_string()),
is_quote_token: Some(true),
});
for known_token in KNOWN_LOCAL_TOKEN_METADATA {
if mint == known_token.mint {
return Some(ResolvedTokenMetadata {
symbol: Some(known_token.symbol.to_string()),
name: Some(known_token.name.to_string()),
decimals: Some(known_token.decimals),
token_program: Some(known_token.token_program.to_string()),
is_quote_token: known_token.is_quote_token,
});
}
}
return None;
}
@@ -820,6 +882,26 @@ mod tests {
assert_eq!(metadata.is_quote_token, Some(true));
}
#[test]
fn local_metadata_resolves_known_ecosystem_tokens_without_forcing_quote_flag() {
let cases = [
(crate::JUP_MINT_ID, "JUP", "Jupiter", 6_u8),
(crate::RAY_MINT_ID, "RAY", "Raydium", 6_u8),
(crate::BONK_MINT_ID, "BONK", "Bonk", 5_u8),
];
for (mint, expected_symbol, expected_name, expected_decimals) in cases {
let metadata_option = super::resolve_local_token_metadata(mint);
let metadata = match metadata_option {
Some(metadata) => metadata,
None => panic!("known ecosystem token metadata must resolve"),
};
assert_eq!(metadata.symbol.as_deref(), Some(expected_symbol));
assert_eq!(metadata.name.as_deref(), Some(expected_name));
assert_eq!(metadata.decimals, Some(expected_decimals));
assert_eq!(metadata.is_quote_token, None);
}
}
#[test]
fn pump_fun_payload_metadata_extracts_name_and_symbol() {
let payload = serde_json::json!({
@@ -932,6 +1014,144 @@ mod tests {
assert!(fetched.is_quote_token);
}
#[tokio::test]
async fn local_backfill_updates_stable_quotes_without_http() {
let database = make_database().await;
let usdc = crate::TokenDto::new(
crate::USDC_MINT_ID.to_string(),
None,
None,
None,
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
false,
);
let usdt = crate::TokenDto::new(
crate::USDT_MINT_ID.to_string(),
None,
None,
None,
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
false,
);
let usdc_upsert_result = crate::query_tokens_upsert(database.as_ref(), &usdc).await;
if let Err(error) = usdc_upsert_result {
panic!("usdc token upsert must succeed: {}", error);
}
let usdt_upsert_result = crate::query_tokens_upsert(database.as_ref(), &usdt).await;
if let Err(error) = usdt_upsert_result {
panic!("usdt token upsert must succeed: {}", error);
}
let service = crate::TokenMetadataBackfillService::new_local(database.clone());
let result = service.backfill_missing_token_metadata(Some(10)).await;
let result = match result {
Ok(result) => result,
Err(error) => panic!("metadata backfill must succeed: {}", error),
};
assert_eq!(result.updated_token_count, 2);
let usdc_result =
crate::query_tokens_get_by_mint(database.as_ref(), crate::USDC_MINT_ID).await;
let usdc_option = match usdc_result {
Ok(usdc_option) => usdc_option,
Err(error) => panic!("usdc token fetch must succeed: {}", error),
};
let usdc = match usdc_option {
Some(usdc) => usdc,
None => panic!("usdc token must exist"),
};
assert_eq!(usdc.symbol.as_deref(), Some("USDC"));
assert_eq!(usdc.name.as_deref(), Some("USD Coin"));
assert_eq!(usdc.decimals, Some(6));
assert!(usdc.is_quote_token);
let usdt_result =
crate::query_tokens_get_by_mint(database.as_ref(), crate::USDT_MINT_ID).await;
let usdt_option = match usdt_result {
Ok(usdt_option) => usdt_option,
Err(error) => panic!("usdt token fetch must succeed: {}", error),
};
let usdt = match usdt_option {
Some(usdt) => usdt,
None => panic!("usdt token must exist"),
};
assert_eq!(usdt.symbol.as_deref(), Some("USDT"));
assert_eq!(usdt.name.as_deref(), Some("Tether USD"));
assert_eq!(usdt.decimals, Some(6));
assert!(usdt.is_quote_token);
}
#[tokio::test]
async fn local_backfill_updates_known_ecosystem_tokens_without_http() {
let database = make_database().await;
let token_mints = [crate::JUP_MINT_ID, crate::RAY_MINT_ID, crate::BONK_MINT_ID];
for mint in token_mints {
let token = crate::TokenDto::new(
mint.to_string(),
None,
None,
None,
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
false,
);
let upsert_result = crate::query_tokens_upsert(database.as_ref(), &token).await;
if let Err(error) = upsert_result {
panic!("known token upsert must succeed: {}", error);
}
}
let service = crate::TokenMetadataBackfillService::new_local(database.clone());
let result = service.backfill_missing_token_metadata(Some(10)).await;
let result = match result {
Ok(result) => result,
Err(error) => panic!("metadata backfill must succeed: {}", error),
};
assert_eq!(result.updated_token_count, 3);
let jup_result =
crate::query_tokens_get_by_mint(database.as_ref(), crate::JUP_MINT_ID).await;
let jup_option = match jup_result {
Ok(jup_option) => jup_option,
Err(error) => panic!("jup token fetch must succeed: {}", error),
};
let jup = match jup_option {
Some(jup) => jup,
None => panic!("jup token must exist"),
};
assert_eq!(jup.symbol.as_deref(), Some("JUP"));
assert_eq!(jup.name.as_deref(), Some("Jupiter"));
assert_eq!(jup.decimals, Some(6));
assert!(!jup.is_quote_token);
let ray_result =
crate::query_tokens_get_by_mint(database.as_ref(), crate::RAY_MINT_ID).await;
let ray_option = match ray_result {
Ok(ray_option) => ray_option,
Err(error) => panic!("ray token fetch must succeed: {}", error),
};
let ray = match ray_option {
Some(ray) => ray,
None => panic!("ray token must exist"),
};
assert_eq!(ray.symbol.as_deref(), Some("RAY"));
assert_eq!(ray.name.as_deref(), Some("Raydium"));
assert_eq!(ray.decimals, Some(6));
assert!(!ray.is_quote_token);
let bonk_result =
crate::query_tokens_get_by_mint(database.as_ref(), crate::BONK_MINT_ID).await;
let bonk_option = match bonk_result {
Ok(bonk_option) => bonk_option,
Err(error) => panic!("bonk token fetch must succeed: {}", error),
};
let bonk = match bonk_option {
Some(bonk) => bonk,
None => panic!("bonk token must exist"),
};
assert_eq!(bonk.symbol.as_deref(), Some("BONK"));
assert_eq!(bonk.name.as_deref(), Some("Bonk"));
assert_eq!(bonk.decimals, Some(5));
assert!(!bonk.is_quote_token);
}
#[tokio::test]
async fn local_backfill_does_not_overwrite_existing_display_metadata() {
let database = make_database().await;