This commit is contained in:
2026-05-12 18:53:42 +02:00
parent 4f6a4806e2
commit 75c2b6983d
22 changed files with 1452 additions and 368 deletions

View File

@@ -59,3 +59,4 @@
0.7.26 - Diagnostics locaux du pipeline persisté, correction de lagrégation instruction-scoped des swaps Raydium, clarification des compteurs de replay/upsert, et validation quaucun trade candidate issu dune transaction OK nest perdu
0.7.27 - Validation multi-DEX et non-régression du pipeline sur Pump.fun, PumpSwap, Raydium CPMM et Raydium CLMM, avec corpus de tests, diagnostics de référence et garanties sur les événements non pricés
0.7.28 - Refactor DEX commun et verrouillage des invariants de normalisation : séparation des événements décodés, actionnables, trade candidates et candle candidates ; conservation des transactions failed comme traçables mais non actionnables ; ajout de la règle bloquante empêchant tout trade/candle candidate sans payload de montants exploitable, notamment pour le cas partiel `meteora_damm_v1.swap` sans base/quote amount.
0.7.29 - Ajout dune matrice DEX commune (`dex_support_matrix`) utilisée par le catalogue DEX, la classification transactionnelle et lenregistrement des protocol candidates ; ajout du profil de validation `0.7.29_multi_dex_matrix_baseline` exposant la matrice dans le rapport de validation ; préparation explicite des surfaces planifiées sans inventer de program ids non vérifiés.

View File

@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
version = "0.7.28"
version = "0.7.29"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
@@ -87,3 +87,4 @@ manual_map = "allow"
match_like_matches_macro = "allow"
single_match = "allow"
manual_unwrap_or_default = "allow"
manual_find = "allow"

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.27` : le socle transport HTTP/WS, la résolution transactionnelle, le modèle SQLite, plusieurs connecteurs DEX, les candles, les signaux analytiques et lapplication de démonstration existent déjà.
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.29` : 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à.
## 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.27`
## 3. État actuel autour de `0.7.29`
### 3.1. Socle stabilisé à ne pas refactorer maintenant
@@ -63,12 +63,14 @@ Le pipeline `0.7.x` couvre déjà les étapes suivantes :
### 3.3. Connecteurs validés manuellement via lapplication de démo
Les connecteurs suivants ont déjà été testés via lapplication de démonstration et doivent être verrouillés par corpus/replay avant dajouter de nouveaux DEX :
Les connecteurs suivants sont les surfaces prioritaires à verrouiller avant extension massive :
- `pump_fun` ;
- `pump_swap` ;
- `pump_fun` comme surface de launch / mint initial ;
- `pump_swap` pour les swaps post-migration Pump ;
- `raydium_cpmm` ;
- `raydium_clmm`.
- `raydium_clmm` ;
- `meteora_dlmm` ;
- `meteora_damm_v1`, actuellement partiel pour les swaps sans payload montant/prix exploitable.
### 3.4. Connecteurs déjà présents mais à consolider par corpus
@@ -93,28 +95,33 @@ La distinction importante est la suivante :
### 4.1. Matrice de travail
| Code cible | Type | Statut actuel | Prochaine action |
Depuis `0.7.29`, la matrice de support DEX est portée par `kb_lib/src/dex_support_matrix.rs`. Elle centralise le code interne, la famille, la version, le type de surface, les program ids vérifiés localement, le statut de support, les capacités actuelles et les raisons de skip.
| Code cible | Type | Statut `0.7.29` | Prochaine action |
|---|---:|---|---|
| `pump_fun` | Launch + bonding curve | testé via démo | verrouiller corpus, invariants et documentation |
| `pump_swap` | AMM / swap | testé via démo | verrouiller corpus, invariants et candles |
| `raydium_cpmm` | AMM | testé via démo | verrouiller corpus, swaps et candles |
| `raydium_clmm` | CLMM | testé via démo | verrouiller corpus, swaps et candles |
| `raydium_launchlab` / `raydium_launchpad` | Launch surface + migration | manquant | ajouter comme origine de mint/lancement et migration vers Raydium |
| `raydium_amm_v4` | AMM legacy | présent, à isoler | traiter après les autres Raydium avec corpus dédié |
| `meteora_dbc` | Launch / bonding curve | présent, à consolider | corpus, lifecycle, migration et swaps exploitables |
| `meteora_damm_v1` | AMM legacy | présent, à consolider | corpus et séparation swaps/liquidité/events |
| `meteora_damm_v2` | AMM | présent, à consolider | corpus et séparation swaps/liquidité/events |
| `meteora_dlmm` | DLMM | manquant | ajouter à la matrice, puis corpus avant décodage |
| `orca_whirlpools` | CLMM | présent, à consolider | corpus fiable et validation des instructions utiles |
| `fluxbeam` | DEX | présent, à consolider | corpus fiable avant validation |
| `dexlab` | DEX | présent, à consolider | corpus fiable avant validation |
| `bags` | Launch surface / attribution | amorcé | conserver comme origine de lancement, relier à Meteora si prouvé |
| `letsbonk` / `bonk_fun` | Launch surface | manquant | ajouter comme origine LaunchLab/Raydium, pas comme AMM autonome tant que non prouvé |
| `boop_fun` | Launch surface | manquant | ajouter comme origine de mint/lancement et migration |
| `moonshot` / `moonit` | Launch surface | amorcé partiellement | remplacer les heuristiques faibles par corpus et règles prouvées |
| `believe` | Launch surface | manquant | ajouter comme origine associée à Meteora DBC si les comptes lattestent |
| `heaven` | Launch + AMM candidat | manquant | ajouter corpus et déterminer séparation launch/swap |
| `zora_solana` | À vérifier | écarté maintenant | ne pas intégrer avant preuve de programme Solana pertinent |
| `pump_fun` | Launch + bonding curve | partiel | verrouiller le rattachement mint initial -> pools migrés |
| `pump_swap` | AMM / swap | supporté | conserver invariants trade/candle |
| `raydium_cpmm` | AMM | supporté | conserver invariants trade/candle |
| `raydium_clmm` | CLMM | supporté | conserver invariants trade/candle |
| `raydium_launchlab` | Launch surface | planifié, program id local connu | ajouter decoder/materialization dédiée |
| `raydium_launchpad` | Launch surface | à vérifier | ne pas inventer de program id |
| `raydium_amm_v4` | AMM legacy | partiel | traiter après les autres Raydium avec corpus dédié |
| `meteora_dlmm` | DLMM | supporté | verrouiller corpus et non-régression |
| `meteora_damm_v1` | AMM legacy | partiel | conserver le skip explicite des swaps sans montants exploitables |
| `meteora_damm_v2` | AMM | partiel | corpus et séparation swaps/liquidité/events |
| `meteora_dbc` | Launch / bonding curve | partiel | lifecycle, migration et swaps exploitables |
| `meteora_dlc` | À vérifier | à vérifier | confirmer surface/program id avant intégration |
| `orca_whirlpools` | CLMM | partiel | corpus fiable et validation des instructions utiles |
| `fluxbeam` | AMM | partiel | corpus fiable avant validation |
| `dexlab` | AMM | partiel | corpus fiable avant validation |
| `bags` | Launch surface | planifié | conserver comme origine de lancement si corpus le prouve |
| `letsbonk` / `bonk` | Launch surface | planifié | ajouter comme origine de mint/lancement sans supposer un AMM autonome |
| `okx_dex` | Aggregator/router | planifié | classifier sans matérialiser en trade direct avant preuve |
| `boop_fun` | Launch surface | planifié | ajouter comme origine de mint/lancement et migration |
| `moonshot` / `moonit` | Launch surface | planifié | remplacer les heuristiques faibles par corpus et règles prouvées |
| `believe` | Launch surface | planifié | confirmer comptes, migration et rattachement |
| `heaven` | Launch + AMM candidat | planifié | ajouter corpus et déterminer séparation launch/swap |
| `zora` | À vérifier | à vérifier | ne pas intégrer avant preuve de programme Solana pertinent |
## 5. Base de données
@@ -147,9 +154,9 @@ Le modèle actuel contient déjà notamment :
- metrics et analytic signals ;
- diagnostics locaux.
### 5.2. Tables futures prioritaires
### 5.2. Tables existantes à stabiliser pour les cas non-trade et inconnus
Avant détendre trop agressivement les DEX, le modèle doit prévoir les cas non directement tradables :
Avant détendre trop agressivement les DEX, ces tables doivent être stabilisées et raccordées progressivement aux diagnostics et matérialisations :
| Table cible | Rôle |
|---|---|
@@ -206,18 +213,16 @@ Les tests peuvent rester plus souples lorsque cela clarifie le test.
La reprise doit suivre cet ordre :
1. finir/verrouiller `0.7.27` sur `pump_fun`, `pump_swap`, `raydium_cpmm`, `raydium_clmm` ;
2. démarrer `0.7.28` par un refactor commun DEX sans toucher au transport ;
3. ajouter la matrice DEX documentée et les corpus de validation ;
4. ajouter les tables de classification des transactions inconnues et protocol candidates ;
5. matérialiser les événements non-trade : lifecycle, liquidité, fees, rewards, admin ;
6. consolider Meteora, y compris `meteora_dlmm` dans la matrice ;
7. ajouter les launch surfaces manquantes comme origines de mint : LaunchLab/Launchpad, LetsBonk/Bonk.fun, Boop.fun, Moonshot/Moonit, Believe ;
8. traiter Heaven ;
9. consolider Orca/FluxBeam/DexLab ;
10. isoler Raydium AMM v4 legacy ;
11. effectuer une validation DEX v1 consolidée ;
12. reprendre ensuite lUI analytique et les vues token/pair/pool.
1. conserver la non-régression `0.7.28` : transactions failed traçables mais non actionnables, aucun trade/candle candidate sans montant exploitable ;
2. utiliser la matrice `0.7.29` comme source commune pour le catalogue, la classification et les protocol candidates ;
3. relier progressivement les événements non-trade aux tables existantes : lifecycle, liquidité, fees, rewards, admin ;
4. consolider Meteora, surtout `meteora_dlmm` et le cas partiel `meteora_damm_v1` ;
5. ajouter les launch surfaces manquantes comme origines de mint : LaunchLab/Launchpad, LetsBonk/Bonk.fun, Boop.fun, Moonshot/Moonit, Believe, Bags ;
6. traiter Heaven ;
7. consolider Orca/FluxBeam/DexLab ;
8. isoler Raydium AMM v4 legacy ;
9. effectuer une validation DEX v1 consolidée ;
10. reprendre ensuite lUI analytique et les vues token/pair/pool.
## 9. Fichiers utiles pour reprendre dans une nouvelle session

View File

@@ -766,9 +766,7 @@ Objectif : verrouiller la non-régression du pipeline actuel avant dajouter d
- valider que les transactions échouées restent traçables dans les événements décodés sans produire de `k_sol_trade_events`.
### 6.060. Version `0.7.28` — Refactor DEX commun et préparation extension
Objectif : nettoyer la couche DEX avant dajouter de nouveaux protocoles, sans modifier le transport HTTP/WS déjà stabilisé.
À faire :
Réalisé :
- ne pas toucher à `ws_client.rs`, `ws_manager.rs`, `http_client.rs`, `http_pool.rs` ni aux couches JSON-RPC déjà stabilisées,
- extraire depuis `dex_decode.rs` les catégories communes dévénements : trade, candle candidate, liquidity candidate, fee candidate, reward candidate, admin candidate, pool lifecycle candidate,
@@ -787,52 +785,54 @@ Contraintes :
- aucun changement de comportement métier volontaire dans cette version,
- aucun événement non price-action ne doit devenir un trade ou une candle.
### 6.061. Version `0.7.29` — Matrice DEX, corpus et documentation de support
Objectif : rendre explicite ce qui est validé, présent, partiel, manquant ou volontairement ignoré.
### 6.061. Version `0.7.29` — Matrice DEX commune et validation baseline
Réalisé :
À faire :
- ajouter et maintenir une matrice DEX dans `README.md` et `ROADMAP.md`,
- distinguer clairement `DEX effectif`, `launch surface`, `pool origin`, `launch origin` et `migration target`,
- documenter que les launch surfaces sont importantes comme première source de mint/lancement même lorsquun token migre ensuite vers un autre DEX,
- constituer une liste de corpus par DEX/surface : signatures, pools, token mints, résultat attendu,
- indiquer pour chaque protocole : statut, source de preuve, type dévénements couverts, tables alimentées, limites connues,
- retirer `zora_solana` du phasage actif tant quaucun programme Solana pertinent nest prouvé,
- ajouter `meteora_dlmm` à la matrice comme variante Meteora manquante à couvrir plus tard,
- préparer les diagnostics SQL de référence par protocole et par table métier.
- ajouter `kb_lib/src/dex_support_matrix.rs` comme source commune de metadata DEX/surfaces ;
- exposer pour chaque entrée : code interne, famille, version, type de surface, program id connu ou à vérifier, support actuel, statut, confiance, raisons de skip et activation catalogue ;
- raccorder `dex_catalog`, `transaction_classification` et `protocol_candidate_recording` à cette matrice ;
- ajouter le profil `0.7.29_multi_dex_matrix_baseline` ;
- exposer la matrice dans le rapport de validation local ;
- conserver explicitement le comportement `0.7.28` : transactions failed traçables mais non actionnables, et `meteora_damm_v1.swap` sans payload montant/prix non candidat trade/candle.
Matrice cible initiale :
| Code cible | Type | Statut | Objectif immédiat |
| Code cible | Type | Statut `0.7.29` | Objectif immédiat |
|---|---:|---|---|
| `pump_fun` | launch + bonding curve | testé via démo | verrouiller corpus et invariants |
| `pump_swap` | AMM / swap | testé via démo | verrouiller trades/candles |
| `raydium_cpmm` | AMM | testé via démo | verrouiller trades/candles |
| `raydium_clmm` | CLMM | testé via démo | verrouiller trades/candles |
| `raydium_launchlab` / `raydium_launchpad` | launch surface | manquant | détecter mint, launch, migration |
| `raydium_amm_v4` | AMM legacy | présent, à isoler | corpus dédié après autres DEX |
| `meteora_dbc` | launch / bonding curve | présent, à consolider | lifecycle, migration, swaps utiles |
| `meteora_damm_v1` | AMM legacy | présent, à consolider | corpus et séparation events |
| `meteora_damm_v2` | AMM | présent, à consolider | corpus et séparation events |
| `meteora_dlmm` | DLMM | manquant | ajouter corpus avant décodeur |
| `orca_whirlpools` | CLMM | présent, à consolider | validation par corpus |
| `fluxbeam` | DEX | présent, à consolider | validation par corpus |
| `dexlab` | DEX | présent, à consolider | validation par corpus |
| `bags` | launch surface | amorcé | attribution fiable, migration si prouvée |
| `letsbonk` / `bonk_fun` | launch surface | manquant | origine LaunchLab/Raydium |
| `boop_fun` | launch surface | manquant | origine mint/lancement/migration |
| `moonshot` / `moonit` | launch surface | amorcé partiellement | corpus, éviter heuristique faible |
| `believe` | launch surface | manquant | origine Meteora DBC si comptes probants |
| `heaven` | launch + AMM candidat | manquant | corpus et séparation launch/swap |
| `pump_fun` | launch + bonding curve | partiel | rattacher mint initial, bonding curve et migration |
| `pump_swap` | AMM / swap | supporté | conserver trades/candles |
| `raydium_cpmm` | AMM | supporté | conserver trades/candles |
| `raydium_clmm` | CLMM | supporté | conserver trades/candles |
| `raydium_launchlab` | launch surface | planifié, program id local connu | ajouter decoder/materialization dédiée |
| `raydium_launchpad` | launch surface | à vérifier | ne pas inventer de program id |
| `raydium_amm_v4` | AMM legacy | partiel | corpus dédié après autres Raydium |
| `raydium_router` | router | partiel | ne pas matérialiser en trade direct avant preuve |
| `raydium_stable_swap` | AMM legacy | planifié | traiter seulement si corpus pertinent |
| `meteora_dlmm` | DLMM | supporté | verrouiller corpus et non-régression |
| `meteora_damm_v1` | AMM legacy | partiel | garder skip explicite sans payload montant/prix |
| `meteora_damm_v2` | AMM | partiel | corpus et séparation events |
| `meteora_dbc` | launch / bonding curve | partiel | lifecycle, migration, swaps utiles |
| `meteora_dlc` | à vérifier | à vérifier | confirmer surface/program id avant intégration |
| `orca_whirlpools` | CLMM | partiel | validation par corpus |
| `fluxbeam` | AMM | partiel | validation par corpus |
| `dexlab` | AMM | partiel | validation par corpus |
| `bags` | launch surface | planifié | attribution fiable, migration si prouvée |
| `letsbonk` / `bonk` | launch surface | planifié | origine mint/lancement, sans supposer un AMM autonome |
| `okx_dex` | aggregator/router | planifié | classifier sans trade direct avant preuve |
| `boop_fun` | launch surface | planifié | origine mint/lancement/migration |
| `moonshot` / `moonit` | launch surface | planifié | corpus, éviter heuristique faible |
| `believe` | launch surface | planifié | confirmer comptes et migrations |
| `heaven` | launch + AMM candidat | planifié | corpus et séparation launch/swap |
| `zora` | à vérifier | à vérifier | hors phasage actif avant preuve Solana |
### 6.062. Version `0.7.30` — Transactions inconnues et protocol candidates
Objectif : ne plus perdre les transactions utiles qui ne correspondent pas encore à un DEX connu.
À faire :
- ajouter `k_sol_transaction_classifications`,
- ajouter `k_sol_protocol_candidates`,
- classifier les transactions résolues en catégories : known dex, known launch surface, unknown program, non-dex, failed transaction, partial decode, ignored technical transaction,
- consolider `k_sol_transaction_classifications`, déjà présente, avec les catégories utiles au suivi DEX,
- consolider `k_sol_protocol_candidates`, déjà présente, pour prioriser les programmes inconnus ou partiellement reconnus,
- classifier les transactions résolues en catégories : known supported, known partial, known non-trade, unknown program, unknown protocol candidate, unknown event kind, failed transaction, non-actionable trade,
- conserver les `program_id`, comptes, signatures, préfixes de `data`, logs et indices dinstructions utiles à lanalyse,
- créer des requêtes de diagnostic pour repérer les programmes inconnus fréquents,
- permettre de promouvoir plus tard un protocol candidate vers un vrai DEX/surface sans perdre lhistorique,
@@ -1161,6 +1161,19 @@ Plus tard, ce comportement pourra devenir configurable dans `config.json` et pil
6. laisser se terminer le flux de lecture,
7. journaliser clairement les cas dégradés.
## 10.5. Jalons `0.7.29`
Réalisé / à maintenir :
- matrice DEX commune dans `kb_lib/src/dex_support_matrix.rs` ;
- raccordement minimal du catalogue DEX à cette matrice ;
- raccordement des mappings program id -> protocole utilisés par la classification transactionnelle et les protocol candidates ;
- profil `0.7.29_multi_dex_matrix_baseline` ;
- exposition de la matrice dans le rapport de validation local ;
- aucune modification volontaire du comportement trade/candle validé en `0.7.28`.
À poursuivre en `0.7.30` : matérialisation contrôlée des événements non-trade utiles et des transactions inconnues/partielles, sans alimenter les trades/candles actionnables.
## 11. Documentation et livrables de référence
Le projet doit maintenir au minimum :
@@ -1175,21 +1188,18 @@ Le projet doit maintenir au minimum :
La priorité immédiate est désormais la suivante :
1. terminer la validation `0.7.27` sur `pump_fun`, `pump_swap`, `raydium_cpmm` et `raydium_clmm`, sans ajouter de nouveau DEX dans cette étape,
2. vérifier sur bases neuves et après replay local les invariants bloquants du pipeline : `diagnosticsClean = true`, `blockingIssueCount = 0`, aucun trade candidate exploitable perdu, aucun trade event invalide, aucun doublon réel par `decoded_event_id`, aucune candle dupliquée par bucket,
3. démarrer `0.7.28` par le refactor DEX commun, sans toucher aux clients HTTP/WS ni aux managers réseau stabilisés,
4. ajouter la matrice DEX et launch surfaces, avec statut, corpus, limites et prochaine action pour chaque protocole,
5. ajouter les tables de classification des transactions inconnues et des protocol candidates afin de ne plus perdre les transactions utiles non encore décodables,
6. matérialiser ensuite les événements non-trade : liquidité, cycle de vie des pools, fees, rewards et administration,
7. garantir que ces événements non-trade restent séparés des `k_sol_trade_events` et des candles tout en restant rattachés aux transactions, decoded events, pools, pairs et wallets observés,
8. consolider Meteora, y compris `meteora_dlmm`, après corpus fiable,
9. ajouter les launch surfaces manquantes comme premières sources de mint/lancement : Raydium LaunchLab/Launchpad, LetsBonk/Bonk.fun, Boop.fun, Moonshot/Moonit, Believe et Bags,
10. traiter Heaven avec séparation launch/swap,
11. consolider Orca, FluxBeam et DexLab sur corpus,
12. traiter `raydium_amm_v4` legacy seulement après les autres Raydium, avec corpus dédié prouvant le programme `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8`,
13. effectuer une validation DEX v1 consolidée sur tous les connecteurs supportés avant de considérer la couche DEX `0.7.x` comme stable,
14. ajouter ensuite les overlays des signaux analytiques sur les candles,
15. consolider les vues métier `token / pair / pool` dans `kb_demo_app`, y compris les événements liquidité, lifecycle, fees, rewards et admin,
16. stabiliser lergonomie, les filtres, la pagination et la navigation de lUI dinspection,
17. préparer ensuite louverture de `0.8.x` pour lanalyse, les filtres, les patterns et les projections graphiques,
18. préparer enfin Yellowstone gRPC comme extension de capacité, et non comme remplacement du socle HTTP / WS existant.
1. conserver la validation acquise `0.7.28` : transactions failed traçables mais non actionnables, aucun trade/candle candidate sans payload montant/prix exploitable, aucun diagnostic bloquant masqué,
2. utiliser la matrice `0.7.29` (`kb_lib/src/dex_support_matrix.rs`) comme source commune pour le catalogue DEX, les mappings program id -> protocole, la classification transactionnelle et les protocol candidates,
3. garder les clients HTTP/WS et managers réseau hors du refactor DEX tant quils ne bloquent pas le pipeline,
4. consolider les événements non-trade sans les confondre avec les trades/candles : lifecycle de pool, liquidité, fees, rewards, admin/config, migration et launch/mint,
5. rattacher les launch surfaces aux tokens et aux pools migrés : Raydium LaunchLab/Launchpad, LetsBonk/Bonk.fun, Boop.fun, Moonshot/Moonit, Believe, Bags et Heaven,
6. consolider Meteora avec corpus fiable : `meteora_dlmm`, `meteora_damm_v1`, `meteora_damm_v2`, `meteora_dbc` et `meteora_dlc` si le programme est confirmé,
7. consolider Orca, FluxBeam et DexLab sur corpus,
8. traiter `raydium_amm_v4` legacy seulement après les autres Raydium, avec corpus dédié prouvant le programme `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8`,
9. ajouter une matérialisation dédiée des transactions inconnues ou partiellement décodées pour analyser les DEX manquants sans polluer les trades/candles,
10. effectuer une validation DEX v1 consolidée sur tous les connecteurs supportés avant de considérer la couche DEX `0.7.x` comme stable,
11. ajouter ensuite les overlays des signaux analytiques sur les candles,
12. consolider les vues métier `token / pair / pool` dans `kb_demo_app`, y compris les événements liquidité, lifecycle, fees, rewards et admin,
13. stabiliser lergonomie, les filtres, la pagination et la navigation de lUI dinspection,
14. préparer ensuite louverture de `0.8.x` pour lanalyse, les filtres, les patterns et les projections graphiques,
15. préparer enfin Yellowstone gRPC comme extension de capacité, et non comme remplacement du socle HTTP / WS existant.

View File

@@ -163,6 +163,15 @@
Analyse les données déjà persistées : transactions, decoded events, trades, candles, tokens, pools et pairs.
</p>
<div class="mb-3">
<label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label>
<select id="demoPipeline2ValidationProfileSelect" class="form-select">
<option value="0.7.29_multi_dex_matrix_baseline" selected>0.7.29 — DEX matrix baseline</option>
<option value="0.7.28_multi_dex_non_regression">0.7.28 — multi-DEX non-regression</option>
<option value="0.7.27_dexes_non_regression" selected>0.7.27 — first DEXes non-regression</option>
</select>
</div>
<div class="d-flex gap-2">
<button id="demoPipeline2DiagnoseLocalPipelineButton" type="button" class="btn btn-outline-primary">
Diagnose local pipeline

View File

@@ -0,0 +1,82 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* One DEX support matrix entry for the UI.
*/
export type DemoPipeline2DexSupportMatrixEntry = {
/**
* Stable internal protocol or surface code.
*/
code: string,
/**
* Human-readable protocol or surface name.
*/
displayName: string,
/**
* Protocol family.
*/
family: string,
/**
* Protocol version or `unknown` when not verified locally.
*/
version: string,
/**
* Surface type: launch, bonding curve, AMM, CLMM, DLMM, router, aggregator or unknown.
*/
surfaceType: string,
/**
* Primary Solana program id, when verified in local constants or docs.
*/
programId: string | null,
/**
* Optional router program id, when this entry uses a distinct router.
*/
routerProgramId: string | null,
/**
* Program id confidence: known, to_verify or unknown.
*/
programIdStatus: string,
/**
* Whether this protocol has been observed in the local replay corpus.
*/
observed: boolean,
/**
* Whether the code currently contains a decoder for this protocol.
*/
decoded: boolean,
/**
* Whether decoded events are currently materialized beyond raw decoded rows.
*/
materialized: boolean,
/**
* Whether this protocol can currently produce trade candidates.
*/
tradeCandidate: boolean,
/**
* Whether this protocol can currently produce candle candidates.
*/
candleCandidate: boolean,
/**
* Whether this protocol can currently produce pair candidates.
*/
pairCandidate: boolean,
/**
* Whether this protocol can currently produce pool candidates.
*/
poolCandidate: boolean,
/**
* Operational support status.
*/
status: string,
/**
* Confidence level attached to this matrix entry.
*/
confidence: string,
/**
* Optional explicit skip reason for partial or ignored entries.
*/
skipReason: string | null,
/**
* Whether the entry should be inserted as an enabled DEX in the storage catalog.
*/
catalogEnabled: boolean, };

View File

@@ -1,4 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DemoPipeline2DexSupportMatrixEntry } from "./DemoPipeline2DexSupportMatrixEntry";
import type { DemoPipeline2LocalPipelineValidationIssue } from "./DemoPipeline2LocalPipelineValidationIssue";
/**
@@ -29,6 +30,14 @@ expectedDexCodes: Array<string>,
* Observed DEX codes found in diagnostics.
*/
observedDexCodes: Array<string>,
/**
* Number of entries currently exposed by the DEX support matrix.
*/
dexSupportMatrixEntryCount: number,
/**
* DEX support matrix snapshot exposed with the validation report.
*/
dexSupportMatrix: Array<DemoPipeline2DexSupportMatrixEntry>,
/**
* Issues produced by validation.
*/

View File

@@ -0,0 +1,10 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* Request payload for local pipeline validation.
*/
export type DemoPipeline2LocalValidationRequest = {
/**
* Stable validation profile code to execute.
*/
profileCode: string, };

View File

@@ -14,6 +14,7 @@ import type { DemoPipeline2BackfillPayload } from "./bindings/DemoPipeline2Backf
import type { DemoPipeline2PairCandlesRequest } from "./bindings/DemoPipeline2PairCandlesRequest.ts";
import type { DemoPipeline2PairCandlesPayload } from "./bindings/DemoPipeline2PairCandlesPayload.ts";
import type { DemoPipeline2LocalDiagnosticsPayload } from "./bindings/DemoPipeline2LocalDiagnosticsPayload.ts";
import type { DemoPipeline2LocalValidationRequest } from "./bindings/DemoPipeline2LocalValidationRequest.ts";
import type { DemoPipeline2LocalValidationPayload } from "./bindings/DemoPipeline2LocalValidationPayload.ts";
import type { DemoPipeline2ProgramInstructionDiscriminatorSummaryRequest } from "./bindings/DemoPipeline2ProgramInstructionDiscriminatorSummaryRequest.ts";
import type { DemoPipeline2ProgramInstructionDiscriminatorSummaryPayload } from "./bindings/DemoPipeline2ProgramInstructionDiscriminatorSummaryPayload.ts";
@@ -356,6 +357,7 @@ document.addEventListener("DOMContentLoaded", async () => {
const replayMetadataLimitInput = document.querySelector<HTMLInputElement>("#demoPipeline2ReplayMetadataLimitInput");
const replayLocalPipelineButton = document.querySelector<HTMLButtonElement>("#demoPipeline2ReplayLocalPipelineButton");
const diagnoseLocalPipelineButton = document.querySelector<HTMLButtonElement>("#demoPipeline2DiagnoseLocalPipelineButton");
const validationProfileSelect = document.querySelector<HTMLSelectElement>("#demoPipeline2ValidationProfileSelect");
const validateLocalPipelineButton = document.querySelector<HTMLButtonElement>("#demoPipeline2ValidateLocalPipelineButton");
const discriminatorProgramIdInput = document.querySelector<HTMLInputElement>("#demoPipeline2DiscriminatorProgramIdInput");
@@ -402,6 +404,7 @@ document.addEventListener("DOMContentLoaded", async () => {
!replayMetadataLimitInput ||
!replayLocalPipelineButton ||
!diagnoseLocalPipelineButton ||
!validationProfileSelect ||
!validateLocalPipelineButton ||
!discriminatorProgramIdInput ||
!discriminatorLimitInput ||
@@ -647,11 +650,16 @@ document.addEventListener("DOMContentLoaded", async () => {
});
validateLocalPipelineButton.addEventListener("click", async () => {
appendLogLine(logTextarea, "[ui] validating local pipeline with 0.7.28 profile");
const request: DemoPipeline2LocalValidationRequest = {
profileCode: validationProfileSelect.value,
};
appendLogLine(logTextarea, `[ui] validating local pipeline with ${request.profileCode} profile`);
try {
const payload = await invoke<DemoPipeline2LocalValidationPayload>(
"demo_pipeline2_validate_local_pipeline",
{ request },
);
localValidationTextarea.value = payload.validationJson;
@@ -659,7 +667,7 @@ document.addEventListener("DOMContentLoaded", async () => {
appendLogLine(
logTextarea,
`[ui] local pipeline validation completed: profile='${payload.run.validationProfileCode}' passed='${payload.run.validationPassed ? "yes" : "no"}' blocking='${payload.run.blockingIssueCount.toString()}' warnings='${payload.run.warningCount.toString()}'`,
`[ui] local pipeline validation completed: profile='${payload.run.validationProfileCode}' passed='${payload.run.validationPassed ? "yes" : "no"}' blocking='${payload.run.blockingIssueCount.toString()}' warnings='${payload.run.warningCount.toString()}' dexMatrix='${payload.run.report.dexSupportMatrixEntryCount.toString()}'`,
);
} catch (error) {
appendLogLine(logTextarea, `[ui] local pipeline validation error: ${String(error)}`);

View File

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

View File

@@ -76,6 +76,18 @@ pub(crate) struct DemoPipeline2LocalDiagnosticsPayload {
pub summary: DemoPipeline2LocalPipelineDiagnosticSummary,
}
/// Request payload for local pipeline validation.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/DemoPipeline2LocalValidationRequest.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalValidationRequest {
/// Stable validation profile code to execute.
pub profile_code: std::string::String,
}
/// Local validation payload returned to the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
@@ -140,10 +152,63 @@ pub(crate) struct DemoPipeline2LocalPipelineValidationReport {
pub expected_dex_codes: std::vec::Vec<std::string::String>,
/// Observed DEX codes found in diagnostics.
pub observed_dex_codes: std::vec::Vec<std::string::String>,
/// Number of entries currently exposed by the DEX support matrix.
#[ts(type = "number")]
pub dex_support_matrix_entry_count: i64,
/// DEX support matrix snapshot exposed with the validation report.
pub dex_support_matrix: std::vec::Vec<DemoPipeline2DexSupportMatrixEntry>,
/// Issues produced by validation.
pub issues: std::vec::Vec<DemoPipeline2LocalPipelineValidationIssue>,
}
/// One DEX support matrix entry for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/DemoPipeline2DexSupportMatrixEntry.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2DexSupportMatrixEntry {
/// Stable internal protocol or surface code.
pub code: std::string::String,
/// Human-readable protocol or surface name.
pub display_name: std::string::String,
/// Protocol family.
pub family: std::string::String,
/// Protocol version or `unknown` when not verified locally.
pub version: std::string::String,
/// Surface type: launch, bonding curve, AMM, CLMM, DLMM, router, aggregator or unknown.
pub surface_type: std::string::String,
/// Primary Solana program id, when verified in local constants or docs.
pub program_id: std::option::Option<std::string::String>,
/// Optional router program id, when this entry uses a distinct router.
pub router_program_id: std::option::Option<std::string::String>,
/// Program id confidence: known, to_verify or unknown.
pub program_id_status: std::string::String,
/// Whether this protocol has been observed in the local replay corpus.
pub observed: bool,
/// Whether the code currently contains a decoder for this protocol.
pub decoded: bool,
/// Whether decoded events are currently materialized beyond raw decoded rows.
pub materialized: bool,
/// Whether this protocol can currently produce trade candidates.
pub trade_candidate: bool,
/// Whether this protocol can currently produce candle candidates.
pub candle_candidate: bool,
/// Whether this protocol can currently produce pair candidates.
pub pair_candidate: bool,
/// Whether this protocol can currently produce pool candidates.
pub pool_candidate: bool,
/// Operational support status.
pub status: std::string::String,
/// Confidence level attached to this matrix entry.
pub confidence: std::string::String,
/// Optional explicit skip reason for partial or ignored entries.
pub skip_reason: std::option::Option<std::string::String>,
/// Whether the entry should be inserted as an enabled DEX in the storage catalog.
pub catalog_enabled: bool,
}
/// One local pipeline validation issue for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
@@ -839,14 +904,32 @@ pub(crate) async fn demo_pipeline2_diagnose_local_pipeline(
})
}
/// Validates the local pipeline with the `0.7.28` multi-DEX non-regression profile.
/// Validates the local pipeline with a selected multi-DEX validation profile.
#[tauri::command]
pub(crate) async fn demo_pipeline2_validate_local_pipeline(
state: tauri::State<'_, crate::AppState>,
request: std::option::Option<DemoPipeline2LocalValidationRequest>,
) -> Result<DemoPipeline2LocalValidationPayload, std::string::String> {
let database = state.database.clone();
let service = kb_lib::LocalPipelineValidationService::new(database.clone());
let run_result = service.validate_v0_7_28_current_database().await;
let profile_code = match request {
Some(request) => request.profile_code,
None => "0.7.29_multi_dex_matrix_baseline".to_string(),
};
let run_result = match profile_code.as_str() {
"0.7.27" | "0.7.27_dexes_non_regression" => {
service.validate_v0_7_27_current_database().await
},
"0.7.28" | "0.7.28_multi_dex_non_regression" => {
service.validate_v0_7_28_current_database().await
},
"0.7.29" | "0.7.29_multi_dex_matrix_baseline" => {
service.validate_v0_7_29_current_database().await
},
other => Err(kb_lib::Error::InvalidState(format!(
"unsupported local pipeline validation profile: {other}"
))),
};
let run = match run_result {
Ok(run) => run,
Err(error) => {
@@ -1257,6 +1340,10 @@ fn demo_pipeline2_map_local_validation_report(
for issue in report.issues {
issues.push(demo_pipeline2_map_local_validation_issue(issue));
}
let mut dex_support_matrix = std::vec::Vec::new();
for entry in report.dex_support_matrix {
dex_support_matrix.push(demo_pipeline2_map_dex_support_matrix_entry(entry));
}
return DemoPipeline2LocalPipelineValidationReport {
validation_profile_code: report.validation_profile_code,
validation_passed: report.validation_passed,
@@ -1264,10 +1351,38 @@ fn demo_pipeline2_map_local_validation_report(
warning_count: report.warning_count,
expected_dex_codes: report.expected_dex_codes,
observed_dex_codes: report.observed_dex_codes,
dex_support_matrix_entry_count: report.dex_support_matrix_entry_count,
dex_support_matrix,
issues,
};
}
fn demo_pipeline2_map_dex_support_matrix_entry(
entry: kb_lib::DexSupportMatrixEntryDto,
) -> DemoPipeline2DexSupportMatrixEntry {
return DemoPipeline2DexSupportMatrixEntry {
code: entry.code,
display_name: entry.display_name,
family: entry.family,
version: entry.version,
surface_type: entry.surface_type,
program_id: entry.program_id,
router_program_id: entry.router_program_id,
program_id_status: entry.program_id_status,
observed: entry.observed,
decoded: entry.decoded,
materialized: entry.materialized,
trade_candidate: entry.trade_candidate,
candle_candidate: entry.candle_candidate,
pair_candidate: entry.pair_candidate,
pool_candidate: entry.pool_candidate,
status: entry.status,
confidence: entry.confidence,
skip_reason: entry.skip_reason,
catalog_enabled: entry.catalog_enabled,
};
}
fn demo_pipeline2_map_local_validation_issue(
issue: kb_lib::LocalPipelineValidationIssueDto,
) -> DemoPipeline2LocalPipelineValidationIssue {

View File

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

View File

@@ -199,7 +199,7 @@ fn parse_accounts_json(accounts_json: &str) -> std::vec::Vec<std::string::String
accounts.push(text.to_string());
continue;
}
if let Some(pubkey) = item.get("pubkey").and_then(|value| value.as_str()) {
if let Some(pubkey) = item.get("pubkey").and_then(|value| return value.as_str()) {
accounts.push(pubkey.to_string());
}
}

View File

@@ -177,12 +177,12 @@ fn build_program_instruction_discriminator_summaries(
});
}
summaries.sort_by(|left, right| {
right
return right
.undecoded_occurrence_count
.cmp(&left.undecoded_occurrence_count)
.then(right.transaction_count.cmp(&left.transaction_count))
.then(right.occurrence_count.cmp(&left.occurrence_count))
.then(right.latest_instruction_id.cmp(&left.latest_instruction_id))
.then(right.latest_instruction_id.cmp(&left.latest_instruction_id));
});
return Ok(summaries);
}

View File

@@ -26,178 +26,17 @@ pub(crate) struct DexCatalogItem {
pub(crate) fn dex_catalog_item_by_code(
code: &str,
) -> std::option::Option<crate::dex_catalog::DexCatalogItem> {
match code {
"raydium" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "raydium",
name: "Raydium AMM v4",
program_id: Some(crate::RAYDIUM_AMM_V4_PROGRAM_ID),
router_program_id: None,
is_enabled: true,
});
},
"raydium_cpmm" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "raydium_cpmm",
name: "Raydium CPMM",
program_id: Some(crate::RAYDIUM_CPMM_PROGRAM_ID),
router_program_id: None,
is_enabled: true,
});
},
"raydium_clmm" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "raydium_clmm",
name: "Raydium CLMM",
program_id: Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
router_program_id: None,
is_enabled: true,
});
},
"pump_fun" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "pump_fun",
name: "Pump.fun",
program_id: Some(crate::PUMP_FUN_PROGRAM_ID),
router_program_id: None,
is_enabled: true,
});
},
"pump_swap" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "pump_swap",
name: "PumpSwap",
program_id: Some(crate::PUMP_SWAP_PROGRAM_ID),
router_program_id: None,
is_enabled: true,
});
},
"meteora_dbc" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "meteora_dbc",
name: "Meteora DBC",
program_id: Some(crate::METEORA_DBC_PROGRAM_ID),
router_program_id: None,
is_enabled: true,
});
},
"meteora_dlmm" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "meteora_dlmm",
name: "Meteora DLMM",
program_id: Some(crate::METEORA_DLMM_PROGRAM_ID),
router_program_id: None,
is_enabled: true,
});
},
"meteora_damm_v1" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "meteora_damm_v1",
name: "Meteora DAMM v1",
program_id: Some(crate::METEORA_DAMM_V1_PROGRAM_ID),
router_program_id: None,
is_enabled: true,
});
},
"meteora_damm_v2" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "meteora_damm_v2",
name: "Meteora DAMM v2",
program_id: Some(crate::METEORA_DAMM_V2_PROGRAM_ID),
router_program_id: None,
is_enabled: true,
});
},
"orca_whirlpools" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "orca_whirlpools",
name: "Orca Whirlpools",
program_id: Some(crate::ORCA_WHIRLPOOLS_PROGRAM_ID),
router_program_id: None,
is_enabled: true,
});
},
"fluxbeam" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "fluxbeam",
name: "FluxBeam",
program_id: Some(crate::FLUXBEAM_PROGRAM_ID),
router_program_id: None,
is_enabled: true,
});
},
"dexlab" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "dexlab",
name: "DexLab Swap/Pool",
program_id: Some(crate::DEXLAB_PROGRAM_ID),
router_program_id: None,
is_enabled: true,
});
},
// Planned launch/swap surfaces.
//
// These entries are intentionally present before decoder support so that
// the roadmap can evolve without duplicating DEX metadata later.
//
// Program ids should be filled only after validation against live
// transactions and official or otherwise trustworthy references.
"raydium_launchlab" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "raydium_launchlab",
name: "Raydium LaunchLab",
program_id: None,
router_program_id: None,
is_enabled: false,
});
},
"letsbonk" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "letsbonk",
name: "LetsBonk / Bonk.fun",
program_id: None,
router_program_id: None,
is_enabled: false,
});
},
"boop_fun" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "boop_fun",
name: "Boop.fun",
program_id: None,
router_program_id: None,
is_enabled: false,
});
},
"moonshot" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "moonshot",
name: "Moonshot",
program_id: None,
router_program_id: None,
is_enabled: false,
});
},
"believe" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "believe",
name: "Believe",
program_id: None,
router_program_id: None,
is_enabled: false,
});
},
"heaven" => {
return Some(crate::dex_catalog::DexCatalogItem {
code: "heaven",
name: "Heaven",
program_id: None,
router_program_id: None,
is_enabled: false,
});
},
_ => return None,
}
let matrix_entry = match crate::dex_support_matrix_entry_by_code(code) {
Some(matrix_entry) => matrix_entry,
None => return None,
};
return Some(crate::dex_catalog::DexCatalogItem {
code: matrix_entry.code,
name: matrix_entry.display_name,
program_id: matrix_entry.program_id,
router_program_id: matrix_entry.router_program_id,
is_enabled: matrix_entry.catalog_enabled,
});
}
/// Ensures that one known DEX exists in storage and returns its internal id.
@@ -283,7 +122,15 @@ mod tests {
#[test]
fn planned_launch_surfaces_are_present_but_disabled() {
let codes = ["raydium_launchlab", "letsbonk", "boop_fun", "moonshot", "believe", "heaven"];
let codes = [
"raydium_launchlab",
"raydium_launchpad",
"letsbonk",
"boop_fun",
"moonshot",
"believe",
"heaven",
];
for code in codes {
let item_option = crate::dex_catalog::dex_catalog_item_by_code(code);
let item = match item_option {
@@ -297,6 +144,23 @@ mod tests {
}
}
#[test]
fn catalog_entries_are_derived_from_support_matrix() {
let item = match crate::dex_catalog::dex_catalog_item_by_code("pump_swap") {
Some(item) => item,
None => panic!("expected pump_swap catalog item"),
};
let matrix_entry = match crate::dex_support_matrix_entry_by_code("pump_swap") {
Some(matrix_entry) => matrix_entry,
None => panic!("expected pump_swap matrix entry"),
};
assert_eq!(item.code, matrix_entry.code);
assert_eq!(item.name, matrix_entry.display_name);
assert_eq!(item.program_id, matrix_entry.program_id);
assert_eq!(item.router_program_id, matrix_entry.router_program_id);
assert_eq!(item.is_enabled, matrix_entry.catalog_enabled);
}
#[test]
fn unknown_dex_code_is_not_silently_accepted() {
let item_option = crate::dex_catalog::dex_catalog_item_by_code("zora_solana");

View File

@@ -0,0 +1,910 @@
// file: kb_lib/src/dex_support_matrix.rs
//! Shared DEX support matrix.
//!
//! This module centralizes protocol metadata that was previously duplicated
//! across catalog, transaction classification, candidate recording and roadmap
//! surfaces. It intentionally does not decode instructions and does not decide
//! whether one decoded event is actionable.
/// Support matrix entry for one DEX, router, aggregator or launch surface.
#[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DexSupportMatrixEntry {
/// Stable internal protocol or surface code.
pub code: &'static str,
/// Human-readable protocol or surface name.
pub display_name: &'static str,
/// Protocol family, for example `raydium`, `meteora` or `pump`.
pub family: &'static str,
/// Protocol version or `unknown` when not verified locally.
pub version: &'static str,
/// Surface type: `launch`, `bonding_curve`, `AMM`, `CLMM`, `DLMM`, `router`, `aggregator` or `unknown`.
pub surface_type: &'static str,
/// Primary Solana program id, when verified in local constants or docs.
pub program_id: std::option::Option<&'static str>,
/// Optional router program id, when this entry uses a distinct router.
pub router_program_id: std::option::Option<&'static str>,
/// Program id confidence: `known`, `to_verify` or `unknown`.
pub program_id_status: &'static str,
/// Whether this protocol has been observed in the local replay corpus.
pub observed: bool,
/// Whether the code currently contains a decoder for this protocol.
pub decoded: bool,
/// Whether decoded events are currently materialized beyond raw decoded rows.
pub materialized: bool,
/// Whether this protocol can currently produce trade candidates.
pub trade_candidate: bool,
/// Whether this protocol can currently produce candle candidates.
pub candle_candidate: bool,
/// Whether this protocol can currently produce pair candidates.
pub pair_candidate: bool,
/// Whether this protocol can currently produce pool candidates.
pub pool_candidate: bool,
/// Operational support status: `supported`, `partial`, `planned`, `ignored`, `to_verify` or `unknown`.
pub status: &'static str,
/// Confidence level attached to this matrix entry.
pub confidence: &'static str,
/// Optional explicit skip reason for partial or ignored entries.
pub skip_reason: std::option::Option<&'static str>,
/// Whether the entry should be inserted as an enabled DEX in the storage catalog.
pub catalog_enabled: bool,
}
/// Owned DTO form of a DEX support matrix entry.
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DexSupportMatrixEntryDto {
/// Stable internal protocol or surface code.
pub code: std::string::String,
/// Human-readable protocol or surface name.
pub display_name: std::string::String,
/// Protocol family, for example `raydium`, `meteora` or `pump`.
pub family: std::string::String,
/// Protocol version or `unknown` when not verified locally.
pub version: std::string::String,
/// Surface type: `launch`, `bonding_curve`, `AMM`, `CLMM`, `DLMM`, `router`, `aggregator` or `unknown`.
pub surface_type: std::string::String,
/// Primary Solana program id, when verified in local constants or docs.
pub program_id: std::option::Option<std::string::String>,
/// Optional router program id, when this entry uses a distinct router.
pub router_program_id: std::option::Option<std::string::String>,
/// Program id confidence: `known`, `to_verify` or `unknown`.
pub program_id_status: std::string::String,
/// Whether this protocol has been observed in the local replay corpus.
pub observed: bool,
/// Whether the code currently contains a decoder for this protocol.
pub decoded: bool,
/// Whether decoded events are currently materialized beyond raw decoded rows.
pub materialized: bool,
/// Whether this protocol can currently produce trade candidates.
pub trade_candidate: bool,
/// Whether this protocol can currently produce candle candidates.
pub candle_candidate: bool,
/// Whether this protocol can currently produce pair candidates.
pub pair_candidate: bool,
/// Whether this protocol can currently produce pool candidates.
pub pool_candidate: bool,
/// Operational support status: `supported`, `partial`, `planned`, `ignored`, `to_verify` or `unknown`.
pub status: std::string::String,
/// Confidence level attached to this matrix entry.
pub confidence: std::string::String,
/// Optional explicit skip reason for partial or ignored entries.
pub skip_reason: std::option::Option<std::string::String>,
/// Whether the entry should be inserted as an enabled DEX in the storage catalog.
pub catalog_enabled: bool,
}
impl DexSupportMatrixEntryDto {
/// Builds an owned DTO from one static support matrix entry.
pub fn from_entry(entry: &crate::DexSupportMatrixEntry) -> Self {
return Self {
code: entry.code.to_string(),
display_name: entry.display_name.to_string(),
family: entry.family.to_string(),
version: entry.version.to_string(),
surface_type: entry.surface_type.to_string(),
program_id: match entry.program_id {
Some(program_id) => Some(program_id.to_string()),
None => None,
},
router_program_id: match entry.router_program_id {
Some(router_program_id) => Some(router_program_id.to_string()),
None => None,
},
program_id_status: entry.program_id_status.to_string(),
observed: entry.observed,
decoded: entry.decoded,
materialized: entry.materialized,
trade_candidate: entry.trade_candidate,
candle_candidate: entry.candle_candidate,
pair_candidate: entry.pair_candidate,
pool_candidate: entry.pool_candidate,
status: entry.status.to_string(),
confidence: entry.confidence.to_string(),
skip_reason: match entry.skip_reason {
Some(skip_reason) => Some(skip_reason.to_string()),
None => None,
},
catalog_enabled: entry.catalog_enabled,
};
}
}
const DEX_SUPPORT_MATRIX_ENTRIES: &[DexSupportMatrixEntry] = &[
DexSupportMatrixEntry {
code: "pump_fun",
display_name: "Pump.fun",
family: "pump",
version: "bonding_curve",
surface_type: "launch",
program_id: Some(crate::PUMP_FUN_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: false,
decoded: true,
materialized: true,
trade_candidate: true,
candle_candidate: true,
pair_candidate: true,
pool_candidate: true,
status: "partial",
confidence: "high",
skip_reason: Some("launch_surface_requires_migration_linking_before_live_trading"),
catalog_enabled: true,
},
DexSupportMatrixEntry {
code: "pump_swap",
display_name: "PumpSwap",
family: "pump",
version: "amm",
surface_type: "AMM",
program_id: Some(crate::PUMP_SWAP_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: true,
decoded: true,
materialized: true,
trade_candidate: true,
candle_candidate: true,
pair_candidate: true,
pool_candidate: true,
status: "supported",
confidence: "high",
skip_reason: None,
catalog_enabled: true,
},
DexSupportMatrixEntry {
code: "raydium_cpmm",
display_name: "Raydium CPMM",
family: "raydium",
version: "cpmm",
surface_type: "AMM",
program_id: Some(crate::RAYDIUM_CPMM_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: true,
decoded: true,
materialized: true,
trade_candidate: true,
candle_candidate: true,
pair_candidate: true,
pool_candidate: true,
status: "supported",
confidence: "high",
skip_reason: None,
catalog_enabled: true,
},
DexSupportMatrixEntry {
code: "raydium_clmm",
display_name: "Raydium CLMM",
family: "raydium",
version: "clmm",
surface_type: "CLMM",
program_id: Some(crate::RAYDIUM_CLMM_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: true,
decoded: true,
materialized: true,
trade_candidate: true,
candle_candidate: true,
pair_candidate: true,
pool_candidate: true,
status: "supported",
confidence: "high",
skip_reason: None,
catalog_enabled: true,
},
DexSupportMatrixEntry {
code: "raydium_amm_v4",
display_name: "Raydium AMM v4",
family: "raydium",
version: "amm_v4",
surface_type: "AMM",
program_id: Some(crate::RAYDIUM_AMM_V4_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: false,
decoded: true,
materialized: true,
trade_candidate: true,
candle_candidate: true,
pair_candidate: true,
pool_candidate: true,
status: "partial",
confidence: "medium",
skip_reason: Some("not_observed_in_0_7_28_replay"),
catalog_enabled: true,
},
DexSupportMatrixEntry {
code: "raydium",
display_name: "Raydium AMM v4",
family: "raydium",
version: "amm_v4_alias",
surface_type: "AMM",
program_id: Some(crate::RAYDIUM_AMM_V4_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: false,
decoded: true,
materialized: true,
trade_candidate: true,
candle_candidate: true,
pair_candidate: true,
pool_candidate: true,
status: "partial",
confidence: "medium",
skip_reason: Some("legacy_catalog_alias_for_raydium_amm_v4"),
catalog_enabled: true,
},
DexSupportMatrixEntry {
code: "raydium_launchlab",
display_name: "Raydium LaunchLab",
family: "raydium",
version: "launchlab",
surface_type: "launch",
program_id: Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: true,
pool_candidate: true,
status: "planned",
confidence: "medium",
skip_reason: Some("decoder_and_materialization_not_enabled"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "raydium_launchpad",
display_name: "Raydium Launchpad",
family: "raydium",
version: "unknown",
surface_type: "launch",
program_id: None,
router_program_id: None,
program_id_status: "to_verify",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: true,
pool_candidate: true,
status: "to_verify",
confidence: "low",
skip_reason: Some("program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "raydium_router",
display_name: "Raydium Router",
family: "raydium",
version: "router",
surface_type: "router",
program_id: Some(crate::RAYDIUM_AMM_ROUTING_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: false,
pool_candidate: false,
status: "partial",
confidence: "medium",
skip_reason: Some("router_not_materialized_as_direct_trade_surface"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "raydium_stable_swap",
display_name: "Raydium Stable Swap AMM",
family: "raydium",
version: "stable_swap",
surface_type: "AMM",
program_id: Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: false,
pool_candidate: false,
status: "planned",
confidence: "medium",
skip_reason: Some("deprecated_program_not_prioritized"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "meteora_dlmm",
display_name: "Meteora DLMM",
family: "meteora",
version: "dlmm",
surface_type: "DLMM",
program_id: Some(crate::METEORA_DLMM_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: true,
decoded: true,
materialized: true,
trade_candidate: true,
candle_candidate: true,
pair_candidate: true,
pool_candidate: true,
status: "supported",
confidence: "high",
skip_reason: None,
catalog_enabled: true,
},
DexSupportMatrixEntry {
code: "meteora_dlc",
display_name: "Meteora DLC",
family: "meteora",
version: "unknown",
surface_type: "unknown",
program_id: None,
router_program_id: None,
program_id_status: "to_verify",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: false,
pool_candidate: false,
status: "to_verify",
confidence: "low",
skip_reason: Some("surface_and_program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "meteora_damm_v1",
display_name: "Meteora DAMM v1",
family: "meteora",
version: "damm_v1",
surface_type: "AMM",
program_id: Some(crate::METEORA_DAMM_V1_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: true,
decoded: true,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: true,
pool_candidate: true,
status: "partial",
confidence: "medium",
skip_reason: Some("meteora_damm_v1_swap_without_amount_payload"),
catalog_enabled: true,
},
DexSupportMatrixEntry {
code: "meteora_damm_v2",
display_name: "Meteora DAMM v2",
family: "meteora",
version: "damm_v2",
surface_type: "AMM",
program_id: Some(crate::METEORA_DAMM_V2_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: false,
decoded: true,
materialized: true,
trade_candidate: true,
candle_candidate: true,
pair_candidate: true,
pool_candidate: true,
status: "partial",
confidence: "medium",
skip_reason: Some("not_observed_in_0_7_28_replay"),
catalog_enabled: true,
},
DexSupportMatrixEntry {
code: "meteora_dbc",
display_name: "Meteora DBC",
family: "meteora",
version: "dbc",
surface_type: "bonding_curve",
program_id: Some(crate::METEORA_DBC_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: false,
decoded: true,
materialized: true,
trade_candidate: true,
candle_candidate: true,
pair_candidate: true,
pool_candidate: true,
status: "partial",
confidence: "medium",
skip_reason: Some("not_observed_in_0_7_28_replay"),
catalog_enabled: true,
},
DexSupportMatrixEntry {
code: "orca_whirlpools",
display_name: "Orca Whirlpools",
family: "orca",
version: "whirlpools",
surface_type: "CLMM",
program_id: Some(crate::ORCA_WHIRLPOOLS_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: false,
decoded: true,
materialized: true,
trade_candidate: true,
candle_candidate: true,
pair_candidate: true,
pool_candidate: true,
status: "partial",
confidence: "medium",
skip_reason: Some("not_observed_in_0_7_28_replay"),
catalog_enabled: true,
},
DexSupportMatrixEntry {
code: "fluxbeam",
display_name: "FluxBeam",
family: "fluxbeam",
version: "unknown",
surface_type: "AMM",
program_id: Some(crate::FLUXBEAM_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: false,
decoded: true,
materialized: true,
trade_candidate: true,
candle_candidate: true,
pair_candidate: true,
pool_candidate: true,
status: "partial",
confidence: "medium",
skip_reason: Some("not_observed_in_0_7_28_replay"),
catalog_enabled: true,
},
DexSupportMatrixEntry {
code: "dexlab",
display_name: "DexLab Swap/Pool",
family: "dexlab",
version: "unknown",
surface_type: "AMM",
program_id: Some(crate::DEXLAB_PROGRAM_ID),
router_program_id: None,
program_id_status: "known",
observed: false,
decoded: true,
materialized: true,
trade_candidate: true,
candle_candidate: true,
pair_candidate: true,
pool_candidate: true,
status: "partial",
confidence: "medium",
skip_reason: Some("not_observed_in_0_7_28_replay"),
catalog_enabled: true,
},
DexSupportMatrixEntry {
code: "bags",
display_name: "Bags",
family: "bags",
version: "unknown",
surface_type: "launch",
program_id: None,
router_program_id: None,
program_id_status: "unknown",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: true,
pool_candidate: true,
status: "planned",
confidence: "low",
skip_reason: Some("program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "letsbonk",
display_name: "LetsBonk / Bonk.fun",
family: "bonk",
version: "unknown",
surface_type: "launch",
program_id: None,
router_program_id: None,
program_id_status: "unknown",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: true,
pool_candidate: true,
status: "planned",
confidence: "low",
skip_reason: Some("program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "bonk",
display_name: "Bonk launch surface",
family: "bonk",
version: "unknown",
surface_type: "launch",
program_id: None,
router_program_id: None,
program_id_status: "unknown",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: true,
pool_candidate: true,
status: "planned",
confidence: "low",
skip_reason: Some("program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "okx_dex",
display_name: "OKX DEX",
family: "okx",
version: "unknown",
surface_type: "aggregator",
program_id: None,
router_program_id: None,
program_id_status: "unknown",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: false,
pool_candidate: false,
status: "planned",
confidence: "low",
skip_reason: Some("program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "boop_fun",
display_name: "Boop.fun",
family: "boop",
version: "unknown",
surface_type: "launch",
program_id: None,
router_program_id: None,
program_id_status: "unknown",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: true,
pool_candidate: true,
status: "planned",
confidence: "low",
skip_reason: Some("program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "moonshot",
display_name: "Moonshot",
family: "moonshot",
version: "unknown",
surface_type: "launch",
program_id: None,
router_program_id: None,
program_id_status: "unknown",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: true,
pool_candidate: true,
status: "planned",
confidence: "low",
skip_reason: Some("program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "believe",
display_name: "Believe",
family: "believe",
version: "unknown",
surface_type: "launch",
program_id: None,
router_program_id: None,
program_id_status: "unknown",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: true,
pool_candidate: true,
status: "planned",
confidence: "low",
skip_reason: Some("program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "zora",
display_name: "Zora",
family: "zora",
version: "unknown",
surface_type: "unknown",
program_id: None,
router_program_id: None,
program_id_status: "to_verify",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: false,
pool_candidate: false,
status: "to_verify",
confidence: "low",
skip_reason: Some("surface_and_program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "moonit",
display_name: "Moonit",
family: "moonit",
version: "unknown",
surface_type: "launch",
program_id: None,
router_program_id: None,
program_id_status: "unknown",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: true,
pool_candidate: true,
status: "planned",
confidence: "low",
skip_reason: Some("program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "launchbeam",
display_name: "LaunchBeam",
family: "launchbeam",
version: "unknown",
surface_type: "launch",
program_id: None,
router_program_id: None,
program_id_status: "unknown",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: true,
pool_candidate: true,
status: "planned",
confidence: "low",
skip_reason: Some("program_id_to_verify"),
catalog_enabled: false,
},
DexSupportMatrixEntry {
code: "heaven",
display_name: "Heaven",
family: "heaven",
version: "unknown",
surface_type: "launch",
program_id: None,
router_program_id: None,
program_id_status: "unknown",
observed: false,
decoded: false,
materialized: false,
trade_candidate: false,
candle_candidate: false,
pair_candidate: true,
pool_candidate: true,
status: "planned",
confidence: "low",
skip_reason: Some("program_id_to_verify"),
catalog_enabled: false,
},
];
/// Returns all static DEX support matrix entries.
pub fn dex_support_matrix_entries() -> &'static [crate::DexSupportMatrixEntry] {
return DEX_SUPPORT_MATRIX_ENTRIES;
}
/// Returns all DEX support matrix entries as owned DTOs.
pub fn dex_support_matrix_entry_dtos() -> std::vec::Vec<crate::DexSupportMatrixEntryDto> {
let mut entries = std::vec::Vec::new();
for entry in crate::dex_support_matrix_entries() {
entries.push(crate::DexSupportMatrixEntryDto::from_entry(entry));
}
return entries;
}
/// Looks up one DEX support matrix entry by internal code.
pub fn dex_support_matrix_entry_by_code(
code: &str,
) -> std::option::Option<&'static crate::DexSupportMatrixEntry> {
for entry in crate::dex_support_matrix_entries() {
if entry.code == code {
return Some(entry);
}
}
return None;
}
/// Looks up one DEX support matrix entry by primary or router program id.
pub fn dex_support_matrix_entry_by_program_id(
program_id: &str,
) -> std::option::Option<&'static crate::DexSupportMatrixEntry> {
for entry in crate::dex_support_matrix_entries() {
if let Some(entry_program_id) = entry.program_id {
if entry_program_id == program_id {
return Some(entry);
}
}
if let Some(entry_router_program_id) = entry.router_program_id {
if entry_router_program_id == program_id {
return Some(entry);
}
}
}
return None;
}
#[cfg(test)]
mod tests {
#[test]
fn matrix_contains_required_0_7_29_entries() {
let codes = [
"pump_fun",
"pump_swap",
"raydium_cpmm",
"raydium_clmm",
"raydium_amm_v4",
"raydium_launchlab",
"raydium_launchpad",
"meteora_dlmm",
"meteora_dlc",
"meteora_damm_v1",
"meteora_damm_v2",
"bags",
"bonk",
"okx_dex",
"boop_fun",
"moonshot",
"believe",
"zora",
"moonit",
"launchbeam",
"heaven",
"dexlab",
];
for code in codes {
let entry = crate::dex_support_matrix_entry_by_code(code);
assert!(entry.is_some(), "missing matrix entry for {}", code);
}
}
#[test]
fn matrix_does_not_invent_program_ids_for_unverified_planned_surfaces() {
let codes = [
"bags",
"bonk",
"okx_dex",
"boop_fun",
"moonshot",
"believe",
"zora",
"moonit",
"launchbeam",
"heaven",
];
for code in codes {
let entry = match crate::dex_support_matrix_entry_by_code(code) {
Some(entry) => entry,
None => panic!("missing matrix entry for {}", code),
};
assert!(entry.program_id.is_none(), "{} should not have invented program id", code);
assert!(!entry.catalog_enabled, "{} should not be enabled before verification", code);
}
}
#[test]
fn matrix_lookup_by_program_id_returns_expected_protocol() {
let entry = match crate::dex_support_matrix_entry_by_program_id(crate::METEORA_DLMM_PROGRAM_ID)
{
Some(entry) => entry,
None => panic!("expected meteora_dlmm program id lookup"),
};
assert_eq!(entry.code, "meteora_dlmm");
let raydium_entry = match crate::dex_support_matrix_entry_by_program_id(
crate::RAYDIUM_AMM_V4_PROGRAM_ID,
) {
Some(entry) => entry,
None => panic!("expected raydium AMM v4 program id lookup"),
};
assert_eq!(raydium_entry.code, "raydium_amm_v4");
}
#[test]
fn matrix_marks_partial_meteora_damm_v1_correctly() {
let entry = match crate::dex_support_matrix_entry_by_code("meteora_damm_v1") {
Some(entry) => entry,
None => panic!("expected meteora_damm_v1 matrix entry"),
};
assert_eq!(entry.status, "partial");
assert!(entry.observed);
assert!(entry.decoded);
assert!(!entry.trade_candidate);
assert!(!entry.candle_candidate);
assert_eq!(entry.skip_reason, Some("meteora_damm_v1_swap_without_amount_payload"));
}
#[test]
fn matrix_marks_launch_surfaces_as_launch_or_bonding_curve() {
let codes = ["pump_fun", "raydium_launchlab", "bags", "moonshot", "moonit"];
for code in codes {
let entry = match crate::dex_support_matrix_entry_by_code(code) {
Some(entry) => entry,
None => panic!("missing matrix entry for {}", code),
};
assert!(
entry.surface_type == "launch" || entry.surface_type == "bonding_curve",
"{} has unexpected surface type {}",
code,
entry.surface_type
);
}
}
#[test]
fn matrix_dto_preserves_core_fields() {
let entry = match crate::dex_support_matrix_entry_by_code("pump_swap") {
Some(entry) => entry,
None => panic!("expected pump_swap matrix entry"),
};
let dto = crate::DexSupportMatrixEntryDto::from_entry(entry);
assert_eq!(dto.code, "pump_swap");
assert_eq!(dto.program_id, Some(crate::PUMP_SWAP_PROGRAM_ID.to_string()));
assert!(dto.trade_candidate);
assert!(dto.candle_candidate);
}
}

View File

@@ -39,6 +39,8 @@ mod dex_detection_route;
mod dex_event_classification;
/// Shared DEX pool materialization helpers.
mod dex_pool_materialization;
/// Shared DEX support matrix.
mod dex_support_matrix;
/// Shared error type for `kb_lib`.
mod error;
/// Generic asynchronous HTTP JSON-RPC client.
@@ -859,6 +861,18 @@ pub use dex_event_classification::is_dex_pool_lifecycle_event_kind;
pub use dex_event_classification::is_dex_reward_event_kind;
/// Returns true for swap-like DEX events.
pub use dex_event_classification::is_dex_trade_event_kind;
/// Static DEX support matrix entry.
pub use dex_support_matrix::DexSupportMatrixEntry;
/// Owned DEX support matrix entry DTO.
pub use dex_support_matrix::DexSupportMatrixEntryDto;
/// Returns all static DEX support matrix entries.
pub use dex_support_matrix::dex_support_matrix_entries;
/// Looks up one DEX support matrix entry by internal code.
pub use dex_support_matrix::dex_support_matrix_entry_by_code;
/// Looks up one DEX support matrix entry by primary or router program id.
pub use dex_support_matrix::dex_support_matrix_entry_by_program_id;
/// Returns all DEX support matrix entries as owned DTOs.
pub use dex_support_matrix::dex_support_matrix_entry_dtos;
/// Global error type used by the `kb_lib` crate.
///
/// The project intentionally avoids `anyhow` and `thiserror`, so this

View File

@@ -64,7 +64,7 @@ impl LocalPipelineValidationConfig {
/// Builds the strict validation config for `0.7.27` non-regression runs.
pub fn v0_7_27_multi_dex_non_regression() -> Self {
return Self {
profile_code: "0.7.27_multi_dex_non_regression".to_string(),
profile_code: "0.7.27_multi_dex_non_regression (obsolete)".to_string(),
expected_dex_codes: vec![
"pump_fun".to_string(),
"pump_swap".to_string(),
@@ -115,6 +115,37 @@ impl LocalPipelineValidationConfig {
require_candles_per_dex: false,
};
}
/// Builds the `0.7.29` DEX support matrix baseline validation config.
///
/// This profile preserves the `0.7.28` trade/candle non-regression checks
/// while requiring the observed partial Meteora DAMM v1 surface to remain
/// visible in diagnostics. It also exposes the DEX support matrix in the
/// validation report without making planned DEXes blocking.
pub fn v0_7_29_multi_dex_matrix_baseline() -> Self {
return Self {
profile_code: "0.7.29_multi_dex_matrix_baseline".to_string(),
expected_dex_codes: vec![
"pump_swap".to_string(),
"raydium_cpmm".to_string(),
"raydium_clmm".to_string(),
"meteora_dlmm".to_string(),
"meteora_damm_v1".to_string(),
],
require_all_expected_dexes: true,
allow_unexpected_dexes: true,
require_clean_diagnostics: false,
require_ok_trade_candidates_fully_materialized: true,
require_no_invalid_trade_events: true,
require_no_duplicate_decoded_event_trades: true,
require_no_duplicate_candle_buckets: true,
require_no_pair_gaps: false,
require_decoded_events_per_dex: true,
require_trade_events_per_dex: false,
require_candles_per_dex: false,
};
}
}
/// A single local pipeline validation issue.
@@ -147,6 +178,10 @@ pub struct LocalPipelineValidationReportDto {
pub expected_dex_codes: std::vec::Vec<std::string::String>,
/// Observed DEX codes found in diagnostics.
pub observed_dex_codes: std::vec::Vec<std::string::String>,
/// Number of entries currently exposed by the DEX support matrix.
pub dex_support_matrix_entry_count: i64,
/// DEX support matrix snapshot exposed with the validation report.
pub dex_support_matrix: std::vec::Vec<crate::DexSupportMatrixEntryDto>,
/// Issues produced by validation.
pub issues: std::vec::Vec<LocalPipelineValidationIssueDto>,
}
@@ -224,6 +259,15 @@ impl LocalPipelineValidationService {
let config = crate::LocalPipelineValidationConfig::v0_7_28_multi_dex_non_regression();
return self.validate_current_database(&config).await;
}
/// Diagnoses the current database with the `0.7.29` DEX matrix baseline profile.
pub async fn validate_v0_7_29_current_database(
&self,
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
let config = crate::LocalPipelineValidationConfig::v0_7_29_multi_dex_matrix_baseline();
return self.validate_current_database(&config).await;
}
}
/// Validates a diagnostics summary without performing database access.
@@ -383,6 +427,8 @@ pub fn validate_local_pipeline_diagnostics_summary(
warning_count,
expected_dex_codes,
observed_dex_codes,
dex_support_matrix_entry_count: crate::dex_support_matrix_entries().len() as i64,
dex_support_matrix: crate::dex_support_matrix_entry_dtos(),
issues,
};
}
@@ -553,6 +599,38 @@ mod tests {
assert!(report.observed_dex_codes.contains(&"meteora_damm_v1".to_string()));
}
#[test]
fn validation_accepts_0_7_29_matrix_baseline_summary() {
let summary = make_0_7_28_summary_with_meteora();
let config = crate::LocalPipelineValidationConfig::v0_7_29_multi_dex_matrix_baseline();
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
assert!(report.validation_passed);
assert_eq!(report.validation_profile_code, "0.7.29_multi_dex_matrix_baseline");
assert!(report.observed_dex_codes.contains(&"meteora_damm_v1".to_string()));
assert!(report.expected_dex_codes.contains(&"meteora_damm_v1".to_string()));
}
#[test]
fn validation_report_exposes_dex_support_matrix() {
let summary = make_0_7_28_summary_with_meteora();
let config = crate::LocalPipelineValidationConfig::v0_7_29_multi_dex_matrix_baseline();
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
assert!(report.dex_support_matrix_entry_count > 0);
assert_eq!(
report.dex_support_matrix_entry_count,
crate::dex_support_matrix_entries().len() as i64
);
let mut found_pump_swap = false;
for entry in &report.dex_support_matrix {
if entry.code == "pump_swap" {
found_pump_swap = true;
assert!(entry.trade_candidate);
assert!(entry.candle_candidate);
}
}
assert!(found_pump_swap);
}
#[test]
fn validation_rejects_missing_expected_dex() {
let mut summary = make_clean_summary();

View File

@@ -192,52 +192,11 @@ fn build_instruction_evidence_json(
}
fn known_dex_protocol_name(program_id: &str) -> std::option::Option<&'static str> {
if program_id == crate::RAYDIUM_AMM_V4_PROGRAM_ID {
return Some("raydium_amm_v4");
}
if program_id == crate::RAYDIUM_CPMM_PROGRAM_ID {
return Some("raydium_cpmm");
}
if program_id == crate::RAYDIUM_CLMM_PROGRAM_ID {
return Some("raydium_clmm");
}
if program_id == crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID {
return Some("raydium_launchlab");
}
if program_id == crate::RAYDIUM_AMM_ROUTING_PROGRAM_ID {
return Some("raydium_router");
}
if program_id == crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID {
return Some("raydium_stable_swap");
}
if program_id == crate::PUMP_FUN_PROGRAM_ID {
return Some("pump_fun");
}
if program_id == crate::PUMP_SWAP_PROGRAM_ID {
return Some("pump_swap");
}
if program_id == crate::METEORA_DBC_PROGRAM_ID {
return Some("meteora_dbc");
}
if program_id == crate::METEORA_DLMM_PROGRAM_ID {
return Some("meteora_dlmm");
}
if program_id == crate::METEORA_DAMM_V1_PROGRAM_ID {
return Some("meteora_damm_v1");
}
if program_id == crate::METEORA_DAMM_V2_PROGRAM_ID {
return Some("meteora_damm_v2");
}
if program_id == crate::ORCA_WHIRLPOOLS_PROGRAM_ID {
return Some("orca_whirlpools");
}
if program_id == crate::FLUXBEAM_PROGRAM_ID {
return Some("fluxbeam");
}
if program_id == crate::DEXLAB_PROGRAM_ID {
return Some("dexlab");
}
return None;
let matrix_entry = match crate::dex_support_matrix_entry_by_program_id(program_id) {
Some(matrix_entry) => matrix_entry,
None => return None,
};
return Some(matrix_entry.code);
}
fn should_ignore_program_id(program_id: &str) -> bool {
@@ -385,6 +344,24 @@ fn is_known_launch_surface_program_id(_program_id: &str) -> bool {
#[cfg(test)]
mod tests {
#[test]
fn known_dex_candidate_uses_support_matrix_for_priority_dexes() {
let samples = [
(crate::PUMP_SWAP_PROGRAM_ID, "pump_swap"),
(crate::RAYDIUM_CPMM_PROGRAM_ID, "raydium_cpmm"),
(crate::RAYDIUM_CLMM_PROGRAM_ID, "raydium_clmm"),
(crate::METEORA_DLMM_PROGRAM_ID, "meteora_dlmm"),
(crate::METEORA_DAMM_V1_PROGRAM_ID, "meteora_damm_v1"),
];
for (program_id, expected_protocol) in samples {
let protocol = match super::known_dex_protocol_name(program_id) {
Some(protocol) => protocol,
None => panic!("expected known protocol for {}", program_id),
};
assert_eq!(protocol, expected_protocol);
}
}
#[test]
fn associated_token_program_is_ignored() {
let transaction = test_transaction();

View File

@@ -139,7 +139,7 @@ impl TradeAggregationService {
Ok(amount_resolution) => amount_resolution,
Err(error) => return Err(error),
};
let trade_side = match amount_resolution.resolved_trade_side.clone() {
let trade_side = match amount_resolution.resolved_trade_side {
Some(resolved_trade_side) => resolved_trade_side,
None => trade_side,
};

View File

@@ -1321,7 +1321,7 @@ fn collect_account_keys_from_candidate_path(
target.push(text.to_string());
continue;
}
if let Some(pubkey) = item.get("pubkey").and_then(|value| value.as_str()) {
if let Some(pubkey) = item.get("pubkey").and_then(|value| return value.as_str()) {
target.push(pubkey.to_string());
}
}
@@ -1344,7 +1344,7 @@ fn collect_loaded_address_array(
key: &str,
target: &mut std::vec::Vec<std::string::String>,
) {
let array = match loaded_addresses.get(key).and_then(|value| value.as_array()) {
let array = match loaded_addresses.get(key).and_then(|value| return value.as_array()) {
Some(array) => array,
None => return,
};
@@ -1391,12 +1391,12 @@ fn token_balance_amount_for_account_index(
key: &str,
account_index: usize,
) -> std::option::Option<i128> {
let balances = match meta.get(key).and_then(|value| value.as_array()) {
let balances = match meta.get(key).and_then(|value| return value.as_array()) {
Some(balances) => balances,
None => return None,
};
for balance in balances {
let balance_index = balance.get("accountIndex").and_then(|value| value.as_u64());
let balance_index = balance.get("accountIndex").and_then(|value| return value.as_u64());
let balance_index = match balance_index {
Some(balance_index) => balance_index,
None => continue,
@@ -1410,8 +1410,8 @@ fn token_balance_amount_for_account_index(
}
let amount_text = balance
.get("uiTokenAmount")
.and_then(|value| value.get("amount"))
.and_then(|value| value.as_str());
.and_then(|value| return value.get("amount"))
.and_then(|value| return value.as_str());
let amount_text = match amount_text {
Some(amount_text) => amount_text,
None => return None,
@@ -1504,7 +1504,7 @@ mod tests {
stack_height,
accounts_json: "[]".to_string(),
data_json: None,
parsed_type: parsed_type.map(|value| value.to_string()),
parsed_type: parsed_type.map(|value| return value.to_string()),
parsed_json,
created_at: chrono::Utc::now(),
};

View File

@@ -352,41 +352,12 @@ fn known_dex_program_match(
Some(program_id) => program_id,
None => return None,
};
let protocol_name = if program_id == crate::RAYDIUM_AMM_V4_PROGRAM_ID {
"raydium_amm_v4"
} else if program_id == crate::RAYDIUM_CPMM_PROGRAM_ID {
"raydium_cpmm"
} else if program_id == crate::RAYDIUM_CLMM_PROGRAM_ID {
"raydium_clmm"
} else if program_id == crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID {
"raydium_launchlab"
} else if program_id == crate::RAYDIUM_AMM_ROUTING_PROGRAM_ID {
"raydium_router"
} else if program_id == crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID {
"raydium_stable_swap"
} else if program_id == crate::PUMP_FUN_PROGRAM_ID {
"pump_fun"
} else if program_id == crate::PUMP_SWAP_PROGRAM_ID {
"pump_swap"
} else if program_id == crate::METEORA_DBC_PROGRAM_ID {
"meteora_dbc"
} else if program_id == crate::METEORA_DLMM_PROGRAM_ID {
"meteora_dlmm"
} else if program_id == crate::METEORA_DAMM_V1_PROGRAM_ID {
"meteora_damm_v1"
} else if program_id == crate::METEORA_DAMM_V2_PROGRAM_ID {
"meteora_damm_v2"
} else if program_id == crate::ORCA_WHIRLPOOLS_PROGRAM_ID {
"orca_whirlpools"
} else if program_id == crate::FLUXBEAM_PROGRAM_ID {
"fluxbeam"
} else if program_id == crate::DEXLAB_PROGRAM_ID {
"dexlab"
} else {
return None;
let matrix_entry = match crate::dex_support_matrix_entry_by_program_id(program_id) {
Some(matrix_entry) => matrix_entry,
None => return None,
};
return Some(KnownDexProgramMatch {
protocol_name,
protocol_name: matrix_entry.code,
program_id: program_id.to_string(),
instruction_id: instruction.id,
instruction_index: instruction.instruction_index,
@@ -442,6 +413,26 @@ mod tests {
assert_eq!(program_match.instruction_index, 0);
}
#[test]
fn known_program_match_uses_support_matrix_for_priority_dexes() {
let samples = [
(crate::PUMP_SWAP_PROGRAM_ID, "pump_swap"),
(crate::RAYDIUM_CPMM_PROGRAM_ID, "raydium_cpmm"),
(crate::RAYDIUM_CLMM_PROGRAM_ID, "raydium_clmm"),
(crate::METEORA_DLMM_PROGRAM_ID, "meteora_dlmm"),
(crate::METEORA_DAMM_V1_PROGRAM_ID, "meteora_damm_v1"),
];
for (program_id, expected_protocol) in samples {
let instruction = test_instruction(Some(program_id.to_string()));
let program_match = match super::known_dex_program_match(&instruction) {
Some(program_match) => program_match,
None => panic!("expected program match for {}", expected_protocol),
};
assert_eq!(program_match.protocol_name, expected_protocol);
assert_eq!(program_match.program_id, program_id);
}
}
#[test]
fn unknown_program_id_is_not_matched() {
let instruction =