0.7.43-E5C

This commit is contained in:
2026-05-27 11:28:36 +02:00
parent 69c8f6c957
commit d9558a5c16
28 changed files with 4451 additions and 325 deletions

View File

@@ -73,3 +73,4 @@
0.7.40 - Ajout de Demo3 pour la constitution de corpus on-chain par `dex_code` / `program_id` via `getSignaturesForAddress` + `getTransaction`, extraction des mints, deltas SPL Token, comptes pool/state/vault/program candidats, ajout du backfill par signature dans Demo Pipeline 2, et validation pratique sur Raydium AMM v4 sans promotion automatique des comptes candidats. 0.7.40 - Ajout de Demo3 pour la constitution de corpus on-chain par `dex_code` / `program_id` via `getSignaturesForAddress` + `getTransaction`, extraction des mints, deltas SPL Token, comptes pool/state/vault/program candidats, ajout du backfill par signature dans Demo Pipeline 2, et validation pratique sur Raydium AMM v4 sans promotion automatique des comptes candidats.
0.7.41 - Raydium AMM v4 swap decoder v1 : décodage des inner instructions `675kPX...`, extraction pool/state, authority, vaults, mints, routeSource et montants exploitables, matérialisation trades/candles sur transactions OK, matrice AMM v4 passée en `supported`, et validation locale avec invariants trade/candle propres. 0.7.41 - Raydium AMM v4 swap decoder v1 : décodage des inner instructions `675kPX...`, extraction pool/state, authority, vaults, mints, routeSource et montants exploitables, matérialisation trades/candles sur transactions OK, matrice AMM v4 passée en `supported`, et validation locale avec invariants trade/candle propres.
0.7.42 - Consolidation famille Raydium : audit conservatoire des instructions Raydium non décodées, décodage CLMM legacy `swap`, cleanup des audits remplacés, classification HTTP `getTransaction` comme requête lourde avec retry/backoff de backfill, mapping des événements non-swap prouvés `raydium_clmm` (`increase_liquidity_v2`, `decrease_liquidity_v2`, `open_position_with_token22_nft`, `close_position`) et `raydium_cpmm` (`initialize`, `withdraw`, `collect_creator_fee`), matérialisation de 25 liquidity events, 1 lifecycle event et 2 fee events sur corpus élargi, conservation des non-swaps AMM v4 legacy en audit. 0.7.42 - Consolidation famille Raydium : audit conservatoire des instructions Raydium non décodées, décodage CLMM legacy `swap`, cleanup des audits remplacés, classification HTTP `getTransaction` comme requête lourde avec retry/backoff de backfill, mapping des événements non-swap prouvés `raydium_clmm` (`increase_liquidity_v2`, `decrease_liquidity_v2`, `open_position_with_token22_nft`, `close_position`) et `raydium_cpmm` (`initialize`, `withdraw`, `collect_creator_fee`), matérialisation de 25 liquidity events, 1 lifecycle event et 2 fee events sur corpus élargi, conservation des non-swaps AMM v4 legacy en audit.
0.7.43-E5C - Reprise documentaire et normalisation DEX-first : `0.7.43` est conservé comme point de reprise non clos pour le lot Meteora, la suite est redécoupée par DEX/version séparés, le besoin dun ledger de décodage/replay est acté, les statuts `known` / `observed` / `decoded` / `materialized` / `verified_by_corpus` deviennent obligatoires, et aucun `program_id` ne doit être marqué vérifié sans preuve/corpus reproductible.

View File

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

327
README.md
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. `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 clôture documentaire `0.7.42` : 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, Demo3, le backfill par signature et la consolidation Raydium DEX-first existent déjà. La famille Raydium est maintenant couverte sur swaps effectifs et premiers événements non-trade prouvés ; les non-swaps AMM v4 legacy restent conservés en audit, et le cas Orca Whirlpools est reporté à `0.7.44`. Ce document reflète le point de reprise `0.7.43-E5C`. La version Cargo reste `0.7.43`, mais le lot Meteora ouvert en `0.7.43` nest pas considéré comme terminé. La priorité immédiate est maintenant de normaliser la roadmap DEX-first, dajouter un ledger de décodage/replay pour éviter les rescans inutiles, puis de reprendre les DEX un par un, variante par variante.
## 1. Objectif ## 1. Objectif
@@ -13,12 +13,12 @@ Lobjectif opérationnel est de construire progressivement une application cap
- détecter lapparition de nouveaux tokens, pools, paires et listings sur Solana ; - détecter lapparition de nouveaux tokens, pools, paires et listings sur Solana ;
- identifier en priorité les DEX effectifs sur lesquels les swaps, liquidités et événements de marché sont réellement exécutés ; - identifier en priorité les DEX effectifs sur lesquels les swaps, liquidités et événements de marché sont réellement exécutés ;
- décoder les transactions pertinentes des DEX ciblés, puis seulement ensuite les launch surfaces et origines de mint/lancement ; - décoder les transactions pertinentes des DEX ciblés, puis seulement ensuite les launch surfaces et origines de mint/lancement ;
- séparer les swaps/candles des événements utiles seulement à lanalyse : liquidité, cycle de vie de pool, fees, rewards, administration, wallets observés ; - séparer strictement les swaps/candles des événements utiles seulement à lanalyse : liquidité, cycle de vie de pool, fees, rewards, administration, wallet activity, mint/burn, migration ;
- produire des métriques exploitables : prix, volume, candles/OHLCV, activité, bursts, déséquilibres buy/sell, signaux analytiques ; - produire des métriques exploitables : prix, volume, candles/OHLCV, activité, bursts, déséquilibres buy/sell, signaux analytiques ;
- préparer ensuite des règles déterministes de filtrage, dachat, de vente, de stop-loss et de trailing stop ; - préparer ensuite des règles déterministes de filtrage, dachat, de vente, de stop-loss et de trailing stop ;
- conserver une traçabilité locale suffisante pour rejouer, diagnostiquer et améliorer les décodeurs. - conserver une traçabilité locale suffisante pour rejouer, diagnostiquer et améliorer les décodeurs sans retraiter inutilement toute la base.
Le but court terme nest pas encore le live trading. Le but court terme est de fiabiliser le décodage multi-DEX et la matérialisation des objets métier nécessaires au trading. Le but court terme nest pas encore le live trading. Le but court terme est de fiabiliser le décodage multi-DEX, la matérialisation des objets métier nécessaires au trading et le mécanisme de replay incrémental.
## 2. Workspace ## 2. Workspace
@@ -26,12 +26,12 @@ Le workspace contient deux crates principales.
| Crate | Rôle | | Crate | Rôle |
|---|---| |---|---|
| `kb_lib` | Bibliothèque métier : configuration, tracing, clients réseau, pool HTTP, manager WS, résolution transactionnelle, décodage DEX, détection métier, persistance SQLite, backfill, metadata, candles, signaux analytiques. | | `kb_lib` | Bibliothèque métier : configuration, tracing, clients réseau, pool HTTP, manager WS, résolution transactionnelle, décodage DEX, détection métier, persistance SQLite, backfill, metadata, candles, signaux analytiques, validation et diagnostics. |
| `kb_demo_app` | Application Tauri V2 de démonstration et dinspection : fenêtres `Demo Ws`, `Demo Ws Manager`, `Demo Http`, `Demo Pipeline`, `Demo Pipeline 2`, graphiques candles et commandes de backfill/replay. | | `kb_demo_app` | Application Tauri V2 de démonstration et dinspection : fenêtres `Demo Ws`, `Demo Ws Manager`, `Demo Http`, `Demo Pipeline`, `Demo Pipeline 2`, `Demo3`, graphiques candles et commandes de backfill/replay. |
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. La logique métier doit rester dans `kb_lib`. `kb_demo_app` doit rester une façade UI/Tauri utilisée pour inspecter, backfiller et constituer du corpus on-chain ; elle ne doit pas récupérer de logique DEX profonde.
## 3. État actuel après `0.7.42` ## 3. État actuel au point de reprise `0.7.43-E5C`
### 3.1. Socle stabilisé à ne pas refactorer maintenant ### 3.1. Socle stabilisé à ne pas refactorer maintenant
@@ -44,7 +44,7 @@ Ces éléments fonctionnent et ne sont pas bloquants pour les DEX. Ils ne doiven
- couches JSON-RPC WS/HTTP déjà stabilisées ; - couches JSON-RPC WS/HTTP déjà stabilisées ;
- orchestration réseau utilisée par les fenêtres de démonstration. - orchestration réseau utilisée par les fenêtres de démonstration.
Ils pourront être améliorés plus tard, mais la priorité actuelle est le décodage DEX, les événements métier et les tables danalyse. Ils pourront être améliorés plus tard, mais la priorité actuelle est le décodage DEX, les événements métier, les tables danalyse et le replay incrémental.
### 3.2. Pipeline métier existant ### 3.2. Pipeline métier existant
@@ -56,95 +56,118 @@ Le pipeline `0.7.x` couvre déjà les étapes suivantes :
4. décodage DEX dans `k_sol_dex_decoded_events` ; 4. décodage DEX dans `k_sol_dex_decoded_events` ;
5. détection métier vers tokens, pools, paires, listings, origins et wallets observés ; 5. détection métier vers tokens, pools, paires, listings, origins et wallets observés ;
6. matérialisation des trades exploitables ; 6. matérialisation des trades exploitables ;
7. agrégation pair metrics ; 7. matérialisation partielle des événements non-trade prouvés ;
8. génération candles/OHLCV ; 8. agrégation pair metrics ;
9. signaux analytiques simples ; 9. génération candles/OHLCV ;
10. inspection via lapplication de démonstration. 10. signaux analytiques simples ;
11. inspection via lapplication de démonstration.
### 3.3. Connecteurs validés ou observés via lapplication de démo ### 3.3. Résultat local observé avant normalisation
Les connecteurs suivants sont les surfaces actuellement les plus importantes pour la validation locale : Un corpus local de reprise contient notamment :
- `pump_fun` comme surface de launch / mint initial ; | Indicateur | Valeur observée |
- `pump_swap` pour les swaps post-migration Pump ; |---|---:|
- `raydium_cpmm` : swaps `swap_base_input` / `swap_base_output`, lifecycle `initialize`, liquidity `withdraw` et `collect_creator_fee` sur corpus prouvé ; | transactions chain | `2956` |
- `raydium_clmm` : swaps `swap_v2` et legacy `swap`, liquidité/positions `increase_liquidity_v2`, `decrease_liquidity_v2`, `open_position_with_token22_nft`, `close_position` sur corpus prouvé ; | decoded events | `7159` |
- `raydium_amm_v4` : swaps AMM v4 legacy `675kPX...` matérialisés ; non-swaps legacy conservés en audit tant que le corpus ne permet pas une promotion fiable ; | trade events | `2738` |
- `meteora_dlmm` ; | liquidity events | `0` |
- `meteora_damm_v1`, partiel : les swaps sans payload montant/prix exploitable restent conservés mais non actionnables ; | pool lifecycle events | `1` |
- `meteora_damm_v2`, observé dans le corpus `0.7.36`, mais les swaps sans payload montant/prix exploitable sont maintenant `non_actionable_trade` ; | fee events | `0` |
- `meteora_dbc`, observé dans le corpus `0.7.36`, mais les swaps sans payload montant/prix exploitable sont maintenant `non_actionable_trade`. | reward events | `0` |
| admin events | `0` |
### 3.4. Connecteurs à consolider par corpus en priorité DEX-first Cette distribution montre que les swaps sont déjà fortement présents, mais que la matérialisation non-trade nest pas encore homogène sur le corpus courant. Les nombreux `instruction_audit`, surtout côté Meteora, doivent devenir un axe de travail DEX par DEX.
Les modules ou surfaces suivantes existent, sont partiellement représentés dans le code ou doivent être recherchés, mais doivent être consolidés par corpus local, invariants et documentation avant de reprendre les launch surfaces : ### 3.4. Connecteurs validés ou observés via lapplication de démo
- `raydium_stable_swap` ; Les surfaces suivantes existent dans le code, dans la matrice ou dans le corpus local. Leur niveau de preuve doit rester explicite.
- `orca_whirlpools` ;
- `fluxbeam` ;
- `dexlab` ;
- `meteora_damm_v1`, `meteora_damm_v2`, `meteora_dbc` et `meteora_dlmm` pour la couverture non-trade complète ;
- `metaDAO` et `printr`, apparus comme DEX à vérifier côté sources externes de découverte ;
- tout DEX ou programme récurrent détecté dans `k_sol_protocol_candidates`, DEX Screener ou les transactions locales.
Les launch origins déjà amorcées (`meteora_fun_launch`, `bags`, `moonit`) et les launch surfaces enregistrables (`raydium_launchlab`, `raydium_launchpad`, `letsbonk`, `bonk_fun`, `bags`, `moonshot`, `moonit`, `boop_fun`, `believe`, `heaven`) sont conservées dans la documentation, mais elles ne sont plus prioritaires tant que les DEX effectifs ne sont pas suffisamment couverts. | Code | Statut de travail | Commentaire |
|---|---|---|
| `pump_fun` | launch surface | Surface de launch / mint initial. À traiter après les DEX effectifs sauf besoin de migration. |
| `pump_swap` | DEX effectif | Swaps `buy` / `sell` observés. Non-trade à étendre plus tard. |
| `raydium_cpmm` | DEX effectif consolidé partiellement | Swaps et premiers events non-trade prouvés sur corpus antérieur. |
| `raydium_clmm` | DEX effectif consolidé partiellement | Swaps v2/legacy, positions et liquidity events prouvés sur corpus antérieur. |
| `raydium_amm_v4` | DEX effectif legacy | Swaps AMM v4 legacy matérialisés ; non-swaps legacy conservés en audit tant que le corpus ne permet pas une promotion fiable. |
| `meteora_dlmm` | DEX effectif à reprendre séparément | Présent et observé ; events non-trade à normaliser. |
| `meteora_damm_v1` | DEX effectif à reprendre séparément | Swaps présents ; plusieurs events restent en audit ou non actionnables. |
| `meteora_damm_v2` | DEX effectif à reprendre séparément | Swaps et create_pool observés ; nombreux audits à traiter. |
| `meteora_dbc` | launch/bonding + DEX effectif partiel à reprendre séparément | Gros volume daudits ; séparer bonding/launch, swap effectif et migration. |
| `orca_whirlpools` | DEX effectif à vérifier | À revalider par corpus dédié avant promotion. |
| `fluxbeam` | DEX effectif à vérifier | Program id, corpus et events à vérifier. |
| `dexlab` | DEX effectif à vérifier | Program id, corpus et events à vérifier. |
| `metaDAO` | candidat à vérifier | Aucun `program_id` ne doit être déclaré vérifié sans preuve/corpus. |
| `printr` | candidat à vérifier | Aucun `program_id` ne doit être déclaré vérifié sans preuve/corpus. |
### 3.5. Validation acquise en `0.7.36` ### 3.5. Statuts de preuve obligatoires
La validation `0.7.36_meteora_family_consolidation` est considérée comme réalisée lorsque les invariants suivants restent vrais après replay local : Aucun `program_id`, DEX ou event ne doit être documenté comme vérifié sans preuve reproductible.
- `validationPassed = true` ; | Statut | Sens |
- `diagnosticsClean = true` ; |---|---|
- `blockingIssueCount = 0` ; | `known` | Connu/listé dans le code, une doc, une source externe ou la matrice. |
- `decodedTradeCandidateWithoutTradeEventCount = 0` ; | `observed` | Vu dans une transaction, une instruction, une base locale ou un corpus on-chain. |
- `decodedTradeCandidateWithoutAmountPayloadCount = 0` ; | `decoded` | Un decoder produit un event structuré ou un `instruction_audit` classé. |
- `missingTradeEventCount = 0` ; | `materialized` | Levent alimente une table métier dédiée : trade, liquidity, lifecycle, fee, reward, admin, mint/burn, etc. |
- `pairWithoutTradeCount = 0` pour les paires actionnables ; | `verified_by_corpus` | Validé par requêtes SQL, signatures/corpus reproductibles et invariants de validation. |
- `pairWithoutCandleCount = 0` pour les paires actionnables.
Point important : `meteora_damm_v2` et `meteora_dbc` peuvent produire beaucoup dévénements `swap` décodés sans produire de `trade_events` lorsque les montants ou prix ne sont pas fiables. Ces événements doivent rester `non_actionable_trade` et ne doivent pas être comptés comme `tradeCandidate` ou `candleCandidate`. ## 4. Matrice DEX : priorité révisée
## 4. Matrice DEX : DEX effectifs dabord, launch surfaces ensuite À partir du point de reprise `0.7.43-E5C`, la priorité est :
La distinction de travail à partir de `0.7.39` est la suivante : 1. **DEX effectifs actuels** : programmes où les swaps, pools, liquidités, positions, fees, rewards, admin/config, burns/mints ou migrations sont réellement exécutés ;
2. **launch surfaces** : surfaces de mint, bonding, launchpad, migration ou origine ;
3. **DEX historiques / legacy / faibles priorités** : programmes anciens, peu observés, ou uniquement utiles à la compatibilité/replay historique.
- un **DEX effectif** est un programme ou une famille de programmes sur lesquels des swaps, pools, liquidités, fees, rewards, admin/config, burns ou mints utiles sont réellement observables ; Chaque DEX ou variante de DEX doit avoir sa propre étape de validation. Les familles larges restent utiles pour la navigation, mais le travail de décodage doit être fait par version/protocole : `raydium_cpmm`, `raydium_clmm`, `raydium_amm_v4`, `meteora_dlmm`, `meteora_damm_v1`, `meteora_damm_v2`, `meteora_dbc`, etc.
- une **launch surface** peut être la première source de mint ou de lancement, mais elle ne doit pas masquer la priorité donnée aux programmes où le swap final est exécuté ;
- pour le trading, le DEX effectif, la qualité de décodage du swap et les événements de pool sont prioritaires ; la launch origin reste une information de filtrage à réintroduire après stabilisation des DEX.
### 4.1. Matrice de travail ### 4.1. Ordre de travail DEX effectifs
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. | Priorité | Code cible | Rôle | Action prochaine |
|---:|---|---|---|
| 1 | `pump_swap` | AMM / swap | Maintenir les invariants swaps et chercher les non-trade prouvés. |
| 2 | `raydium_cpmm` | AMM | Garder consolidé ; ne rouvrir que sur nouveaux discriminators/corpus. |
| 3 | `raydium_clmm` | CLMM | Garder consolidé ; ne rouvrir que sur nouveaux discriminators/corpus. |
| 4 | `raydium_amm_v4` | AMM legacy actif | Garder swaps ; ne promouvoir les non-swaps quavec corpus. |
| 5 | `meteora_dlmm` | DLMM | Reprendre séparément : swaps, liquidity, positions, lifecycle, fees/rewards/admin. |
| 6 | `meteora_damm_v1` | AMM | Reprendre séparément : swaps exploitables, pools, liquidity, fees/admin. |
| 7 | `meteora_damm_v2` | AMM | Reprendre séparément : create_pool, swaps exploitables, configs dynamiques, non-trade. |
| 8 | `meteora_dbc` | bonding / DEX effectif partiel | Reprendre séparément : swap effectif, bonding curve, migration, launch attribution. |
| 9 | `orca_whirlpools` | CLMM | Revalider par corpus dédié. |
| 10 | `fluxbeam` | AMM | Vérifier program id, events et corpus. |
| 11 | `dexlab` | AMM | Vérifier program id, events et corpus. |
| 12 | `metaDAO` | candidat DEX | Vérifier par corpus avant toute promotion. |
| 13 | `printr` | candidat DEX | Vérifier par corpus avant toute promotion. |
Depuis `0.7.30`, les événements décodés reçoivent aussi une classification plus fine : `eventLifecycleKind`, `eventActionability` et `nonTradeUseful`. Cette classification sert aux diagnostics et prépare la matérialisation future des événements non-trade sans alimenter directement les trades/candles. ### 4.2. Launch surfaces reportées
Depuis `0.7.32`, les diagnostics distinguent explicitement les gaps littéraux de catalogue (`literalPairWithoutTradeCount`, `literalPairWithoutCandleCount`) des gaps bloquants/actionnables (`blockingPairWithoutTradeCount`, `blockingPairWithoutCandleCount`). Les anciens champs `pairWithoutTradeCount` et `pairWithoutCandleCount` restent exposés comme alias de compatibilité pour les gaps bloquants/actionnables. À reprendre après les DEX effectifs, sauf si une surface est indispensable pour comprendre une migration vers un pool tradable :
Depuis `0.7.33`, les diagnostics ajoutent une classification `pairTradingReadiness` au niveau des paires et des résumés agrégés `pairTradingReadinessSummaries`. Cette classification sépare les paires directement lisibles/tradables contre WSOL ou stable, les paires inversées avec WSOL/stable en base, les paires cross-quote nécessitant un routeur/aggregator, les paires non matérialisées en trade et les cas de quote inconnue. Elle reste purement diagnostique : elle ne modifie ni le replay, ni les `trade_events`, ni les candles. - `pump_fun` ;
- `raydium_launchlab` ;
- `raydium_launchpad` ;
- `letsbonk` / `bonk_fun` ;
- `bags` ;
- `moonshot` ;
- `moonit` ;
- `boop_fun` ;
- `believe` ;
- `heaven` ;
- autres launch origins découvertes par corpus.
| Code cible | Type | Priorité `0.7.39+` | Prochaine action | ### 4.3. DEX historiques ou faibles priorités
|---|---:|---|---|
| `pump_swap` | AMM / swap | haute | conserver les invariants trade/candle et étendre les événements non-trade prouvés. |
| `raydium_cpmm` | AMM | haute | couvert pour swaps input/output, `initialize`, `withdraw` et `collect_creator_fee` prouvés ; poursuivre seulement sur nouveaux discriminators. |
| `raydium_clmm` | CLMM | haute | couvert pour swaps v2/legacy, increase/decrease liquidity et open/close position prouvés ; poursuivre seulement sur nouveaux discriminators. |
| `raydium_amm_v4` | AMM legacy | haute | swaps legacy couverts et matérialisés ; non-swaps AMM v4 conservés en audit, à compléter plus tard si corpus suffisant. |
| `raydium_stable_swap` | AMM legacy | moyenne | vérifier lusage réel de `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h` et ne lactiver quavec corpus. |
| `meteora_dlmm` | DLMM | haute | verrouiller swaps, positions, liquidité et lifecycle. |
| `meteora_damm_v1` | AMM legacy | haute | conserver le skip sans amounts exploitables et rechercher un corpus swap/liquidité exploitable. |
| `meteora_damm_v2` | AMM | haute | vérifier `cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG`, les swaps, les configs dynamiques et les événements non-trade. |
| `meteora_dbc` | bonding curve / DEX effectif partiel | haute | séparer ce qui relève du swap, du bonding curve et de la migration sans faux trade/candle. |
| `orca_whirlpools` | CLMM | haute | consolider par corpus fiable : create_pool, swap, liquidité/positions. |
| `fluxbeam` | AMM | moyenne | vérifier program id, corpus réel et instructions utiles. |
| `dexlab` | AMM | moyenne | vérifier program id, corpus réel, swaps et liens OpenBook éventuels. |
| `metaDAO` | DEX à vérifier | haute | rechercher le ou les program ids, corpus, comptes et types dévénements avant toute promotion. |
| `printr` | DEX à vérifier | haute | rechercher le ou les program ids, corpus, comptes et types dévénements avant toute promotion. |
| `okx_dex` | Aggregator/router | basse DEX direct | classifier sans matérialiser en trade direct avant preuve. |
| Launch surfaces (`pump_fun`, `raydium_launchlab`, `raydium_launchpad`, `bags`, `letsbonk`, `bonk_fun`, `boop_fun`, `moonshot`, `moonit`, `believe`, `heaven`) | Launch / origine | reportée | reprendre après consolidation des DEX effectifs ; aucun `program_id` fictif. |
| `zora` | À vérifier | hors priorité | ne pas intégrer avant preuve de programme Solana pertinent. |
## 5. Base de données À garder dans la matrice mais sans bloquer les versions immédiates :
SQLite reste le stockage local initial. - `raydium_stable_swap` tant que son usage réel nest pas démontré par corpus local ;
- vieux programmes legacy uniquement utiles pour compatibilité ou replay historique ;
- agrégateurs/routeurs comme `okx_dex` tant quils ne correspondent pas à un DEX direct matérialisable ;
- entrées ambiguës comme `zora` tant quaucun programme Solana pertinent nest prouvé.
## 5. Base de données et replay incrémental
SQLite reste le stockage local initial. Le fichier `config.json` peut pointer vers une base différente pour travailler par corpus, par DEX ou sur base vierge. Le schéma est créé au lancement via les `CREATE TABLE IF NOT EXISTS` existants.
Organisation actuelle à conserver : Organisation actuelle à conserver :
@@ -154,60 +177,76 @@ Organisation actuelle à conserver :
- les entités persistées sont sous `kb_lib/src/db/entities/` ; - les entités persistées sont sous `kb_lib/src/db/entities/` ;
- les DTO applicatifs sont sous `kb_lib/src/db/dtos/`. - les DTO applicatifs sont sous `kb_lib/src/db/dtos/`.
`schema.rs` nest donc pas un fichier métier à splitter immédiatement. Il reste acceptable tant quil garde uniquement la responsabilité de création de schéma.
### 5.1. Tables existantes importantes ### 5.1. Tables existantes importantes
Le modèle actuel contient déjà notamment : Le modèle actuel contient déjà notamment :
- transactions et instructions Solana normalisées ; - `k_sol_chain_transactions` ;
- DEX connus ; - `k_sol_chain_instructions` ;
- événements DEX décodés ; - `k_sol_dexes` ;
- tokens, pools, pool tokens, paires, listings ; - `k_sol_dex_decoded_events` ;
- launch surfaces et attributions ; - `k_sol_tokens` ;
- pool origins ; - `k_sol_pools` ;
- swaps et trade events ; - `k_sol_pairs` ;
- liquidity events ; - `k_sol_pool_tokens` ;
- wallets, participations, holdings ; - `k_sol_trade_events` ;
- candles ; - `k_sol_liquidity_events` ;
- metrics et analytic signals ; - `k_sol_pool_lifecycle_events` ;
- diagnostics locaux. - `k_sol_fee_events` ;
- `k_sol_reward_events` ;
- `k_sol_pool_admin_events` ;
- `k_sol_token_mint_events` ;
- `k_sol_token_burn_events` ;
- `k_sol_transaction_classifications` ;
- `k_sol_protocol_candidates`.
### 5.2. Tables existantes à stabiliser pour les cas non-trade et inconnus ### 5.2. Prochaine évolution DB : ledger de décodage/replay
Avant détendre trop agressivement les DEX, ces tables doivent être stabilisées et raccordées progressivement aux diagnostics et matérialisations : Le replay actuel peut retraiter trop largement les transactions déjà connues. La prochaine tranche DB doit ajouter une structure de suivi permettant de ne pas rescanner les events certains, sauf option explicite.
| Table cible | Rôle | Objectifs :
- mémoriser quune transaction/instruction a déjà été traitée par un decoder donné ;
- stocker le statut de décodage : certain, partiel, inconnu, erreur, non-actionnable, multi-token ambigu ;
- associer le résultat au `decoder_code` et à une version logique de decoder ;
- permettre un mode `force` qui ignore le ledger ;
- permettre un mode de reprise ciblé lorsque le decoder change ;
- ne pas skipper automatiquement les transactions multi-token, multi-pool ou multi-event ambiguës ;
- conserver les failed transactions comme traçables mais non actionnables.
Nom de table à décider dans la tranche DB, par exemple :
- `k_sol_decode_attempts` ; ou
- `k_sol_replay_decode_ledger`.
Champs conceptuels attendus :
| Champ conceptuel | Rôle |
|---|---| |---|---|
| `k_sol_transaction_classifications` | classifier les transactions connues, inconnues, partielles, échouées, non-DEX, DEX-candidates, launch-candidates. | | `transaction_id` / `signature` | rattachement transactionnel stable |
| `k_sol_protocol_candidates` | conserver les programmes ou patterns suspects/récurrents qui ne correspondent pas encore à un DEX connu. | | `instruction_id` / `instruction_index` | granularité instruction si possible |
| `k_sol_pool_lifecycle_events` | matérialiser initialize/create/migrate/open/close/status events. | | `program_id` | programme effectivement traité |
| `k_sol_fee_events` | conserver fees, creator fees, protocol fees, fund fees. | | `protocol_name` | protocole/DEx résolu |
| `k_sol_reward_events` | conserver reward params, init rewards, collect rewards. | | `decoder_code` | decoder logique utilisé |
| `k_sol_pool_admin_events` | conserver changements de config, authority, pause/resume, paramètres de pool. | | `decoder_version` | version logique du decoder |
| `decode_status` | certain, partial, unknown, failed, skipped, ambiguous |
| `certainty` | niveau de confiance machine |
| `event_count` | nombre devents produits |
| `payload_hash` / `instruction_hash` | détection de changement dentrée |
| `reason_code` / `error_json` | diagnostic sans panic |
| `created_at` / `updated_at` | audit local |
`k_sol_liquidity_events` existe déjà et doit être stabilisée/étendue plutôt que recréée sans nécessité. ## 6. Politique de replay
## 6. Politique de refactor actuelle Règles cibles :
Le code et la documentation sont vivants. Les refactors agressifs sont acceptables lorsque cela rend le pipeline plus propre et plus durable, à condition de respecter ces limites : - par défaut, ne pas retraiter une instruction dont le ledger indique un décodage certain avec le même decoder/version et la même entrée ;
- retraiter si `force = true` ;
- ne pas casser les fonctionnalités déjà validées ; - retraiter si le decoder concerné change de version logique ;
- ne pas toucher pour le moment aux clients et managers réseau stabilisés ; - retraiter si la transaction contient plusieurs tokens/pools/events et que le ledger la classe comme ambiguë ou partielle ;
- faire des étapes courtes, testables et rejouables ; - retraiter si un event précédemment `instruction_audit` devient décodable par un nouveau decoder ;
- conserver les invariants de replay local ; - ne jamais créer de faux trade/candle pour un event dont les montants ne sont pas fiables ;
- ne pas transformer un événement non price-action en trade/candle ; - conserver les audits utiles pour améliorer les decoders.
- documenter les nouveaux types publics avec une rustdoc utile mais pas surchargée ;
- laisser `local_pipeline_diagnostics` servir doutil temporaire de validation tant que les DEX ne sont pas stabilisés.
Les fichiers à surveiller en priorité sont :
| Fichier | Action recommandée |
|---|---|
| `kb_lib/src/dex_decode.rs` | extraire classification, catégories dévénements et enrichissement commun. |
| `kb_lib/src/dex_detect.rs` | extraire helpers communs pool/pair/listing/origin/wallets et isoler les handlers par famille. |
| `kb_lib/src/trade_aggregation.rs` | isoler extraction de montants, normalisation trade et pricing. |
| `kb_lib/src/dex/*.rs` | homogénéiser les contrats de décodeurs sans forcer un gros trait prématuré. |
## 7. Contraintes de code ## 7. Contraintes de code
@@ -221,36 +260,36 @@ Contraintes maintenues :
- `#![deny(unreachable_pub)]` et `#![warn(missing_docs)]` dans les racines concernées ; - `#![deny(unreachable_pub)]` et `#![warn(missing_docs)]` dans les racines concernées ;
- pas de `anyhow` ; - pas de `anyhow` ;
- pas de `thiserror` ; - pas de `thiserror` ;
- pas de `?`, `unwrap`, `expect` dans le code applicatif ; - pas de `?`, `unwrap`, `expect` dans le code applicatif de `kb_lib` ;
- usage privilégié de `match`, `if let Err`, `let Err = ... else` ; - `kb_demo_app` peut rester plus souple tant quelle reste une application de démonstration ;
- usage privilégié de `match`, `if let Err`, `let Err = ... else` dans `kb_lib` ;
- imports externes limités, sauf traits lorsque nécessaire ; - imports externes limités, sauf traits lorsque nécessaire ;
- tests unitaires et tests de replay maintenus. - tests unitaires et tests de replay maintenus.
Les tests peuvent rester plus souples lorsque cela clarifie le test. Si une requête DB est ajoutée ou modifiée, mettre à jour les re-exports dans `kb_lib/src/db.rs`, puis dans `kb_lib/src/lib.rs` si la surface publique lexige.
## 8. Priorité immédiate ## 8. Priorité immédiate
Les phases `0.7.39`, `0.7.40`, `0.7.41` et `0.7.42` sont considérées comme closes côté documentation lorsque les tests locaux passent et que les requêtes SQL de contrôle Raydium confirment les invariants de la famille Raydium. La priorité immédiate après le point de reprise `0.7.43-E5C` est :
État acquis : 1. `0.7.43` : resynchronisation documentaire, normalisation DEX-first et gel du point de reprise ;
2. `0.7.44` : ajout du ledger de décodage/replay et options de replay `force` / skip sûr ;
3. `0.7.45` : reprise séparée de `meteora_dlmm` ;
4. `0.7.46` : reprise séparée de `meteora_damm_v1` ;
5. `0.7.47` : reprise séparée de `meteora_damm_v2` ;
6. `0.7.48` : reprise séparée de `meteora_dbc` ;
7. `0.7.49` : revalidation séparée de `orca_whirlpools` ;
8. `0.7.50+` : FluxBeam, DexLab, metaDAO, Printr, puis launch surfaces.
- `0.7.39_dex_first_effective_swap_surfaces` : matrice DEX-first, suppression de lalias `raydium`, ajout de `metaDAO` et `Printr` en `to_verify`, aucun `program_id` fictif ; Garde-fous constants :
- `0.7.40` : Demo3 découvre on-chain signatures, mints, deltas SPL Token et comptes candidats par DEX/program id ; Demo Pipeline 2 peut backfiller une signature précise ;
- `0.7.41` : `raydium_amm_v4.swap` décode les inner instructions `675kPX...`, produit trades/candles lorsque les montants sont exploitables, et conserve les failed transactions sans matérialisation marché ;
- `0.7.42` : `raydium_cpmm`, `raydium_clmm` et `raydium_amm_v4` sont consolidés comme surfaces Raydium effectives ; CLMM/CPMM couvrent les premiers événements non-trade prouvés ; AMM v4 non-swap reste en audit legacy.
Résultat de corpus Raydium `0.7.42` : - pas de faux trade ;
- pas de fausse candle ;
- swaps Raydium matérialisés : AMM v4, CLMM `swap_v2`, CLMM legacy `swap`, CPMM `swap_base_input`, CPMM `swap_base_output` ; - pas de `program_id` fictif ;
- non-trade Raydium matérialisés : `25` liquidity events, `1` pool lifecycle event, `2` fee events ; - pas de promotion dun DEX sans corpus transactionnel ;
- événements CLMM prouvés : `increase_liquidity_v2`, `decrease_liquidity_v2`, `open_position_with_token22_nft`, `close_position` ; - pas de logique métier DEX profonde dans `kb_demo_app` ;
- événements CPMM prouvés : `initialize`, `withdraw`, `collect_creator_fee` ; - pas de metadata manquante bloquante ;
- audits restants AMM v4 : conservés comme `raydium_amm_v4.instruction_audit` informatifs, non promus sans preuve ; - pas de refactor réseau inutile tant que les clients HTTP/WS existants suffisent.
- transactions failed : traçables mais exclues de `trade_events`, metrics et candles.
Limite connue hors Raydium : la base locale peut encore contenir des decoded events `orca_whirlpools.swap` partiels issus du corpus courant. Orca Whirlpools est volontairement reporté à `0.7.44`; ce point ne doit pas bloquer la clôture Raydium `0.7.42`.
La prochaine étape est maintenant `0.7.43_meteora_effective_surfaces`, puis `0.7.44` pour Orca/FluxBeam/DexLab/metaDAO/Printr.
## 9. Fichiers utiles pour reprendre dans une nouvelle session ## 9. Fichiers utiles pour reprendre dans une nouvelle session
@@ -260,6 +299,8 @@ Pour reprendre rapidement le codage dans une nouvelle session, fournir au minimu
- `ROADMAP.md` ; - `ROADMAP.md` ;
- `CHANGELOG.md` ; - `CHANGELOG.md` ;
- `Cargo.toml` racine ; - `Cargo.toml` racine ;
- `clippy.toml` ;
- `config.json` ;
- `kb_lib/Cargo.toml` ; - `kb_lib/Cargo.toml` ;
- `kb_lib/src/lib.rs` ; - `kb_lib/src/lib.rs` ;
- `kb_lib/src/constants.rs` ; - `kb_lib/src/constants.rs` ;
@@ -267,15 +308,19 @@ Pour reprendre rapidement le codage dans une nouvelle session, fournir au minimu
- `kb_lib/src/dex/*.rs` ; - `kb_lib/src/dex/*.rs` ;
- `kb_lib/src/dex_decode.rs` ; - `kb_lib/src/dex_decode.rs` ;
- `kb_lib/src/dex_detect.rs` ; - `kb_lib/src/dex_detect.rs` ;
- `kb_lib/src/dex_support_matrix.rs` ;
- `kb_lib/src/dex_event_classification.rs` ;
- `kb_lib/src/non_trade_event_materialization.rs` ;
- `kb_lib/src/trade_aggregation.rs` ; - `kb_lib/src/trade_aggregation.rs` ;
- `kb_lib/src/pair_candle_aggregation.rs` ; - `kb_lib/src/pair_candle_aggregation.rs` ;
- `kb_lib/src/local_pipeline_replay.rs` ; - `kb_lib/src/local_pipeline_replay.rs` ;
- `kb_lib/src/local_pipeline_validation.rs` ; - `kb_lib/src/local_pipeline_validation.rs` ;
- `kb_lib/src/local_pipeline_diagnostics.rs` ; - `kb_lib/src/local_pipeline_diagnostics.rs` ;
- `kb_lib/src/onchain_dex_pair_discovery.rs` ;
- `kb_lib/src/db/schema.rs` ; - `kb_lib/src/db/schema.rs` ;
- `kb_lib/src/db.rs` ; - `kb_lib/src/db.rs` ;
- `kb_lib/src/db/entities.rs` et `kb_lib/src/db/entities/*` ; - `kb_lib/src/db/entities.rs` et `kb_lib/src/db/entities/*` ;
- `kb_lib/src/db/dtos.rs` et `kb_lib/src/db/dtos/*` ; - `kb_lib/src/db/dtos.rs` et `kb_lib/src/db/dtos/*` ;
- `kb_lib/src/db/queries.rs` et `kb_lib/src/db/queries/*`. - `kb_lib/src/db/queries.rs` et `kb_lib/src/db/queries/*`.
Ajouter `kb_demo_app/src/demo_pipeline*.rs`, les fichiers frontend associés et les nouvelles démos (`Demo3`, `Demo4`, `Demo10`) seulement si la tâche concerne lUI, la recherche de corpus, les diagnostics affichés ou le watcher temps réel. Ajouter `kb_demo_app/src/demo_pipeline*.rs`, `kb_demo_app/src/demo3.rs`, les fichiers frontend associés et les nouvelles démos seulement si la tâche concerne lUI, la recherche de corpus, les diagnostics affichés ou le watcher temps réel.

View File

@@ -1019,35 +1019,129 @@ Réalisé :
Limite connue non-Raydium : un corpus local peut encore contenir des événements `orca_whirlpools.swap` partiels. Orca Whirlpools est explicitement reporté à `0.7.44`; cela ne remet pas en cause la clôture Raydium `0.7.42`. Limite connue non-Raydium : un corpus local peut encore contenir des événements `orca_whirlpools.swap` partiels. Orca Whirlpools est explicitement reporté à `0.7.44`; cela ne remet pas en cause la clôture Raydium `0.7.42`.
Décision : `0.7.42` est clos côté Raydium. La suite immédiate est `0.7.43` pour Meteora, puis `0.7.44` pour Orca/FluxBeam/DexLab/metaDAO/Printr. Décision : `0.7.42` est clos côté Raydium. Le lot `0.7.43` ouvert pour Meteora nest pas considéré comme clos : il devient le point de reprise `0.7.43-E5C`, puis la suite est découpée en étapes plus petites pour éviter les lots multi-DEX trop larges.
### 6.075. Version `0.7.43` — Meteora effectif : DLMM, DAMM v1/v2, DBC ### 6.075. Version `0.7.43` — Point de reprise, normalisation DEX-first et documentation
Objectif : compléter la famille Meteora en couvrant les événements réellement utiles au DEX effectif, avec corpus enrichi par Demo3 si nécessaire. Objectif : figer le point de reprise après saturation de session, clarifier létat réel du corpus et empêcher la roadmap de regrouper plusieurs DEX/versions dans une seule tranche de validation.
À faire / acté :
- documenter que `0.7.43` nest pas une clôture Meteora complète ;
- conserver les résultats locaux observés : `2956` transactions, `7159` decoded events, `2738` trade events, `0` liquidity events sur le corpus courant, `1` lifecycle event, `0` fee/reward/admin events ;
- acter que les nombreux `instruction_audit` Meteora sont une dette de décodage, pas une preuve dévénements non-trade matérialisés ;
- imposer un ordre de travail : vrais DEX effectifs, puis launch surfaces, puis DEX historiques/legacy ;
- imposer une validation séparée par DEX/version : `meteora_dlmm`, `meteora_damm_v1`, `meteora_damm_v2`, `meteora_dbc`, etc. ;
- distinguer les statuts `known`, `observed`, `decoded`, `materialized`, `verified_by_corpus` ;
- maintenir la règle : aucun `program_id` nest vérifié sans signature/corpus/requête de validation.
### 6.076. Version `0.7.44` — Ledger de décodage/replay et skip sûr
Objectif : empêcher le replay local de rescanner inutilement les transactions/instructions dont le décodage est déjà certain, tout en gardant la possibilité de forcer ou de retraiter les cas ambigus.
À faire : À faire :
- conserver la validation `0.7.36` : les swaps DAMM v2 / DBC sans amounts fiables restent `non_actionable_trade` ; - ajouter une table de suivi type `k_sol_decode_attempts` ou `k_sol_replay_decode_ledger` dans `kb_lib/src/db/schema.rs` ;
- vérifier `cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG` et les autres programmes Meteora dans le corpus local ; - stocker `transaction_id`, `signature`, `instruction_id` lorsque disponible, `program_id`, `protocol_name`, `decoder_code`, `decoder_version`, `decode_status`, `certainty`, `event_count`, hash dentrée, reason/error et timestamps ;
- consolider `meteora_dlmm` pour swaps, positions, add/remove liquidity, lifecycle et non-trade events ; - ajouter les entities/dtos/queries associées ;
- consolider `meteora_damm_v1` et `meteora_damm_v2` pour pools, swaps exploitables, configs dynamiques, fees/admin ; - mettre à jour les re-exports dans `kb_lib/src/db.rs`, puis `kb_lib/src/lib.rs` si nécessaire ;
- clarifier la part `launch/bonding_curve` et la part `dex_effective` de `meteora_dbc` ; - intégrer le ledger dans `local_pipeline_replay.rs` sans changer la sémantique trade/candle ;
- éviter tout trade/candle artificiel lorsque les montants ou prix ne sont pas prouvés. - ajouter une option `force` pour ignorer le ledger ;
- ne pas skipper automatiquement les transactions multi-token, multi-pool, multi-event ou marquées `partial` / `ambiguous` ;
- retraiter les lignes concernées lorsquun decoder change de version logique ;
- ajouter les diagnostics SQL permettant de mesurer skipped/replayed/ambiguous/forced.
### 6.076. Version `0.7.44` — Autres DEX effectifs : Orca, FluxBeam, DexLab, metaDAO, Printr ### 6.077. Version `0.7.45` — `meteora_dlmm` séparé
Objectif : consolider les DEX non-Raydium/Meteora et intégrer les DEX récemment observés comme candidats vérifiables. Objectif : consolider `meteora_dlmm` comme DEX effectif séparé, avec corpus dédié et events utiles au trading.
À faire : À faire :
- revalider `orca_whirlpools` sur corpus dédié : create_pool, swap, liquidité, positions, mints et montants fiables ; - vérifier le ou les `program_id` par corpus local, pas seulement par constante ;
- traiter explicitement les swaps Orca partiels comme non-actionnables tant que les montants ne sont pas reconstruits ; - consolider swaps exploitables, add/remove liquidity, positions, lifecycle et audits restants ;
- constituer des corpus locaux pour `fluxbeam` et `dexlab` ; - matérialiser uniquement les events prouvés dans les tables dédiées ;
- vérifier les `program_id`, comptes, préfixes `data_json` et familles dinstructions utiles ; - conserver tout event incomplet en `instruction_audit` ou non-actionnable ;
- vérifier `metaDAO` et `Printr` par corpus on-chain local avant toute promotion ; - ajouter les compteurs diagnostics par event kind.
- ne pas confondre source externe de découverte et preuve on-chain ;
- marquer explicitement les variantes partiellement supportées ou heuristiques.
### 6.077. Version `0.7.45` — Couverture événementielle DEX : swap, liquidité, fees, rewards, admin, burns ### 6.078. Version `0.7.46` — `meteora_damm_v1` séparé
Objectif : sassurer que chaque DEX effectif expose les événements utiles au scoring et au risque sans polluer les trades/candles. Objectif : reprendre `meteora_damm_v1` sans le mélanger à DAMM v2, DBC ou DLMM.
À faire :
- valider les swaps exploitables et les cas sans amounts ;
- rechercher create/init pool, liquidity, fee/admin/config et autres events utiles ;
- maintenir la règle : pas de trade/candle si base/quote amount ou prix ne sont pas fiables ;
- produire un corpus SQL minimal pour chaque event promu.
### 6.079. Version `0.7.47` — `meteora_damm_v2` séparé
Objectif : reprendre `meteora_damm_v2` comme DEX effectif séparé, avec traitement spécifique des nombreux `instruction_audit`.
À faire :
- vérifier `cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG` dans le corpus local avant de le marquer `verified_by_corpus` ;
- consolider `create_pool`, swaps exploitables, configs dynamiques, fees/admin et events lifecycle ;
- conserver les swaps sans payload montant/prix fiables comme `non_actionable_trade` ;
- ne promouvoir aucun event depuis `instruction_audit` sans signature de validation.
### 6.080. Version `0.7.48` — `meteora_dbc` séparé
Objectif : séparer proprement bonding/launch, swap effectif, migration et attribution dorigine dans `meteora_dbc`.
À faire :
- distinguer les events de bonding curve / launch des events de DEX effectif ;
- vérifier swaps exploitables, migration, lifecycle, mint/burn éventuels et launch attribution ;
- éviter toute candle artificielle sur events de bonding/launch non pricés ;
- documenter les signatures/corpus avant toute promotion.
### 6.081. Version `0.7.49` — `orca_whirlpools` séparé
Objectif : revalider Orca Whirlpools par corpus dédié avant toute promotion au même niveau que Raydium/Meteora.
À faire :
- revalider create_pool, swap, liquidité, positions, mints et montants fiables ;
- traiter les swaps Orca partiels comme non-actionnables tant que les montants ne sont pas reconstruits ;
- matérialiser uniquement les events prouvés ;
- ajouter des diagnostics par event kind.
### 6.082. Version `0.7.50` — `fluxbeam` séparé
Objectif : vérifier FluxBeam comme DEX effectif distinct.
À faire :
- constituer un corpus local ;
- vérifier `program_id`, comptes, préfixes `data_json` et familles dinstructions utiles ;
- valider swaps, pools, liquidity et events non-trade prouvés ;
- marquer explicitement les parties heuristiques ou non-actionnables.
### 6.083. Version `0.7.51` — `dexlab` séparé
Objectif : vérifier DexLab comme DEX effectif distinct, sans le confondre avec OpenBook ou une autre couche de marché.
À faire :
- constituer un corpus local ;
- vérifier `program_id`, comptes, préfixes `data_json` et familles dinstructions utiles ;
- valider swaps, pools et éventuels liens de market/pool ;
- conserver les cas partiels en audit.
### 6.084. Version `0.7.52` — `metaDAO` candidat DEX
Objectif : rechercher et vérifier metaDAO sans inventer de `program_id`.
À faire :
- rechercher les signatures/corpus via Demo3, DEX Screener ou sources externes de découverte ;
- ne considérer une source externe que comme indice ;
- promouvoir uniquement après preuve on-chain locale ;
- documenter chaque programme, event et limite.
### 6.085. Version `0.7.53` — `printr` candidat DEX
Objectif : rechercher et vérifier Printr sans inventer de `program_id`.
À faire :
- rechercher les signatures/corpus via Demo3, DEX Screener ou sources externes de découverte ;
- ne considérer une source externe que comme indice ;
- promouvoir uniquement après preuve on-chain locale ;
- documenter chaque programme, event et limite.
### 6.086. Version `0.7.54` — Couverture événementielle DEX consolidée
Objectif : sassurer que chaque DEX effectif supporté expose les événements utiles au scoring et au risque sans polluer les trades/candles.
À faire : À faire :
@@ -1060,8 +1154,8 @@ Objectif : sassurer que chaque DEX effectif expose les événements utiles au
- ajouter des compteurs et samples diagnostics par DEX et par type dévénement ; - ajouter des compteurs et samples diagnostics par DEX et par type dévénement ;
- conserver linvariant : aucun fee/reward/admin/liquidity/lifecycle/burn non price-action ne produit de trade, metric ou candle. - conserver linvariant : aucun fee/reward/admin/liquidity/lifecycle/burn non price-action ne produit de trade, metric ou candle.
### 6.078. Version `0.7.46` — `kb_demo_app` Demo4 : DEX Screener et sources externes de découverte ### 6.087. Version `0.7.55` — `kb_demo_app` Demo4 : DEX Screener et sources externes de découverte
Objectif : utiliser des sources externes comme aides à la découverte de corpus sans les traiter comme vérité métier, après la première consolidation Raydium. Objectif : utiliser des sources externes comme aides à la découverte de corpus sans les traiter comme vérité métier.
À faire : À faire :
@@ -1072,7 +1166,7 @@ Objectif : utiliser des sources externes comme aides à la découverte de corpus
- permettre de copier les signatures/adresses candidates pour backfill ; - permettre de copier les signatures/adresses candidates pour backfill ;
- ne jamais promouvoir automatiquement un DEX, un `program_id` ou une paire sur la seule base dune réponse externe. - ne jamais promouvoir automatiquement un DEX, un `program_id` ou une paire sur la seule base dune réponse externe.
### 6.079. Version `0.7.47` — Démos spécialisées launch surfaces après DEX effectifs ### 6.088. Version `0.7.56` — Démos spécialisées launch surfaces après DEX effectifs
Objectif : préparer des vues spécialisées pour les launch surfaces, mais seulement après stabilisation des DEX effectifs. Objectif : préparer des vues spécialisées pour les launch surfaces, mais seulement après stabilisation des DEX effectifs.
À faire plus tard : À faire plus tard :
@@ -1083,7 +1177,7 @@ Objectif : préparer des vues spécialisées pour les launch surfaces, mais seul
- exposer les origins dans les diagnostics et lUI dinspection ; - exposer les origins dans les diagnostics et lUI dinspection ;
- maintenir linterdiction de faux program ids, faux trades et fausses candles. - maintenir linterdiction de faux program ids, faux trades et fausses candles.
### 6.080. Version `0.7.48` — `kb_demo_app` Demo10 : watcher WebSocket live DEX ### 6.089. Version `0.7.57` — `kb_demo_app` Demo10 : watcher WebSocket live DEX
Objectif : valider le passage du replay/backfill vers lobservation temps réel contrôlée. Objectif : valider le passage du replay/backfill vers lobservation temps réel contrôlée.
À faire : À faire :
@@ -1097,7 +1191,7 @@ Objectif : valider le passage du replay/backfill vers lobservation temps rée
- afficher les compteurs live, erreurs, subscriptions actives et derniers objets persistés ; - afficher les compteurs live, erreurs, subscriptions actives et derniers objets persistés ;
- prévoir un arrêt propre avec unsubscribe avant close. - prévoir un arrêt propre avec unsubscribe avant close.
### 6.081. Version `0.7.49` — Validation DEX v1 consolidée ### 6.090. Version `0.7.58` — Validation DEX v1 consolidée
Objectif : rejouer tous les DEX effectifs supportés et valider les invariants du pipeline complet avant de revenir aux launch surfaces ou à lanalyse `0.8.x`. Objectif : rejouer tous les DEX effectifs supportés et valider les invariants du pipeline complet avant de revenir aux launch surfaces ou à lanalyse `0.8.x`.
À faire : À faire :
@@ -1110,7 +1204,7 @@ Objectif : rejouer tous les DEX effectifs supportés et valider les invariants d
- conserver une matrice de support par DEX, variante, instruction et type dévénement ; - conserver une matrice de support par DEX, variante, instruction et type dévénement ;
- verrouiller les invariants avant douvrir lanalyse `0.8.x`. - verrouiller les invariants avant douvrir lanalyse `0.8.x`.
### 6.082. Version `0.8.x` — Analyse et filtrage ### 6.091. Version `0.8.x` — Analyse et filtrage
Objectif : transformer les événements bruts en signaux exploitables. Objectif : transformer les événements bruts en signaux exploitables.
À faire : À faire :
@@ -1282,29 +1376,33 @@ Le projet doit maintenir au minimum :
## 12. Priorité immédiate ## 12. Priorité immédiate
La priorité immédiate après `0.7.42` est `0.7.43_meteora_effective_surfaces`. La consolidation Raydium est considérée comme close côté Raydium : `raydium_cpmm`, `raydium_clmm` et `raydium_amm_v4` sont traités comme surfaces Raydium effectives, avec les limites explicitement documentées ci-dessous. La priorité immédiate après le point de reprise `0.7.43-E5C` nest plus de terminer Meteora en un seul bloc. Le lot Meteora groupé a montré ses limites : les events, les audits, les surfaces bonding/launch et les variantes de DEX doivent être traités séparément.
Préconditions validées avant `0.7.43` : Préconditions considérées acquises avant cette reprise :
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é ; 1. validation `0.7.36` acquise : Meteora consolidé au niveau baseline, 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 ; 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é, registre local `WSOL`/`USDC`/`USDT`/`JUP`/`RAY`/`BONK` disponible ; 3. validation `0.7.38` acquise : `tokenMetadataGapSamples` priorisés, Demo Pipeline 2 raccordé, registre local `WSOL`/`USDC`/`USDT`/`JUP`/`RAY`/`BONK` disponible ;
4. `0.7.39` acquis : matrice DEX-first, suppression de lalias `raydium`, `metaDAO` et `Printr` en `to_verify`, aucun `program_id` fictif ; 4. `0.7.39` acquis : matrice DEX-first, suppression de lalias `raydium`, `metaDAO` et `Printr` en `to_verify`, aucun `program_id` fictif ;
5. `0.7.40` acquis : `Demo3` découvre on-chain des signatures, mints, deltas et comptes candidats ; Demo Pipeline 2 peut backfiller une signature précise ; 5. `0.7.40` acquis : `Demo3` découvre on-chain des signatures, mints, deltas et comptes candidats ; Demo Pipeline 2 peut backfiller une signature précise ;
6. `0.7.41` acquis : `raydium_amm_v4.swap` décode les inner instructions `675kPX...`, produit des trades/candles lorsque les montants sont exploitables, et conserve les transactions failed sans matérialisation marché ; 6. `0.7.41` acquis : `raydium_amm_v4.swap` décode les inner instructions `675kPX...`, produit des trades/candles lorsque les montants sont exploitables, et conserve les transactions failed sans matérialisation marché ;
7. `0.7.42` acquis côté Raydium : CLMM/CPMM couvrent swaps et premiers non-swaps prouvés ; AMM v4 couvre les swaps et conserve les non-swaps legacy en audit ; les non-trade events utiles prouvés alimentent leurs tables dédiées ; 7. `0.7.42` acquis côté Raydium : CLMM/CPMM couvrent swaps et premiers non-swaps prouvés ; AMM v4 couvre les swaps et conserve les non-swaps legacy en audit ;
8. la dette Orca Whirlpools observée dans le corpus local est reportée à `0.7.44` et ne doit pas être mélangée à la clôture Raydium. 8. `0.7.43-E5C` est le point de reprise documentaire et technique, avec Clippy `kb_lib` validé localement après correction.
Ordre de travail recommandé pour la suite : Ordre de travail recommandé pour la suite :
1. `0.7.43` : consolider Meteora effectif (`meteora_dlmm`, `meteora_damm_v1`, `meteora_damm_v2`, `meteora_dbc`) avec la même approche corpus-first ; 1. `0.7.44` : ledger de décodage/replay et skip sûr ;
2. `0.7.44` : revalider Orca Whirlpools, puis FluxBeam, DexLab, metaDAO et Printr ; 2. `0.7.45` : `meteora_dlmm` ;
3. `0.7.45` : établir une couverture transversale des événements DEX utiles au scoring : swaps, liquidités, lifecycle, fees, rewards, admin/config, burns/mints ; 3. `0.7.46` : `meteora_damm_v1` ;
4. `0.7.46` : ajouter Demo4 pour les sources externes de découverte sans promotion automatique ; 4. `0.7.47` : `meteora_damm_v2` ;
5. `0.7.47` : ajouter des démos spécialisées launch surfaces après DEX effectifs ; 5. `0.7.48` : `meteora_dbc` ;
6. `0.7.48` : ajouter Demo10 pour le watcher WebSocket live DEX ; 6. `0.7.49` : `orca_whirlpools` ;
7. `0.7.49` : validation DEX v1 consolidée ; 7. `0.7.50` : `fluxbeam` ;
8. passer ensuite à lanalyse `0.8.x`, puis à la couche wallet. 8. `0.7.51` : `dexlab` ;
9. `0.7.52` : `metaDAO` candidat DEX ;
10. `0.7.53` : `printr` candidat DEX ;
11. `0.7.54` : couverture événementielle DEX consolidée ;
12. `0.7.55+` : sources externes de découverte, launch surfaces, watcher live et validation consolidée.
Garde-fous constants : Garde-fous constants :
@@ -1314,4 +1412,5 @@ Garde-fous constants :
- pas de promotion dun DEX sans corpus transactionnel ; - pas de promotion dun DEX sans corpus transactionnel ;
- pas de logique métier DEX profonde dans `kb_demo_app` ; - pas de logique métier DEX profonde dans `kb_demo_app` ;
- pas de metadata manquante bloquante ; - pas de metadata manquante bloquante ;
- pas de refactor réseau inutile tant que les clients HTTP/WS existants suffisent. - pas de refactor réseau inutile tant que les clients HTTP/WS existants suffisent ;
- pas de skip replay sur transaction/instruction ambiguë, multi-token ou multi-event sans preuve ledger.

View File

@@ -50,11 +50,46 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="demo3ProgramIdInput" class="form-label">Program id</label> <label for="demo3ProgramIdInput" class="form-label">Program id filter</label>
<input id="demo3ProgramIdInput" type="text" class="form-control font-monospace" spellcheck="false" /> <input id="demo3ProgramIdInput" type="text" class="form-control font-monospace" spellcheck="false" />
<div class="form-text">Used to filter matched instructions. With address source, signatures are fetched from Source address instead.</div>
</div>
<div class="row g-2 mb-3">
<div class="col-6">
<label for="demo3SignatureSourceSelect" class="form-label">Signature source</label>
<select id="demo3SignatureSourceSelect" class="form-select">
<option value="program_id">program_id</option>
<option value="address">address / pool / vault / position</option>
</select>
</div>
<div class="col-6">
<label for="demo3SourceAddressInput" class="form-label">Source address</label>
<input id="demo3SourceAddressInput" type="text" class="form-control font-monospace" spellcheck="false" placeholder="pool / vault / position / config / mint address" />
</div>
<div class="col-12 form-text">Use address source to discover signatures around a pool, vault, position, config or mint while keeping the program id filter.</div>
</div> </div>
<div class="row g-2"> <div class="row g-2">
<div class="col-12">
<label for="demo3TargetEventSelect" class="form-label">Target event</label>
<select id="demo3TargetEventSelect" class="form-select">
<option value="">Any / generic pair-pool discovery</option>
<option value="swap">swap</option>
<option value="add_liquidity">add_liquidity</option>
<option value="remove_liquidity">remove_liquidity</option>
<option value="claim_fee">claim_fee / collect_fee</option>
<option value="claim_reward">claim_reward</option>
<option value="position_open">position_open</option>
<option value="position_close">position_close</option>
<option value="pool_create">pool_create / initialize_pool</option>
<option value="pool_admin">pool_admin / config / authority</option>
<option value="unknown_non_swap">unknown_non_swap</option>
<option value="audit_non_swap_like">audit_non_swap_like</option>
<option value="unclassified_instruction">unclassified_instruction</option>
</select>
<div class="form-text">Use this to find corpus signatures for non-swap decoders without promoting unverified events.</div>
</div>
<div class="col-6"> <div class="col-6">
<label for="demo3HttpRoleInput" class="form-label">HTTP role</label> <label for="demo3HttpRoleInput" class="form-label">HTTP role</label>
<input id="demo3HttpRoleInput" type="text" class="form-control" value="history_backfill" /> <input id="demo3HttpRoleInput" type="text" class="form-control" value="history_backfill" />
@@ -71,6 +106,18 @@
<label for="demo3CandidateLimitInput" class="form-label">Candidate limit</label> <label for="demo3CandidateLimitInput" class="form-label">Candidate limit</label>
<input id="demo3CandidateLimitInput" type="number" min="1" max="100" class="form-control" value="25" /> <input id="demo3CandidateLimitInput" type="number" min="1" max="100" class="form-control" value="25" />
</div> </div>
<div class="col-6">
<div class="form-check mt-2">
<input id="demo3ExcludeSwapsInput" class="form-check-input" type="checkbox" />
<label for="demo3ExcludeSwapsInput" class="form-check-label">Exclude tx with swap logs</label>
</div>
</div>
<div class="col-6">
<div class="form-check mt-2">
<input id="demo3IncludeFailedInput" class="form-check-input" type="checkbox" checked />
<label for="demo3IncludeFailedInput" class="form-check-label">Include failed tx</label>
</div>
</div>
</div> </div>
<div class="d-flex flex-wrap gap-2 mt-3"> <div class="d-flex flex-wrap gap-2 mt-3">
@@ -109,9 +156,14 @@
<h2 class="h5 mb-3">Résumé</h2> <h2 class="h5 mb-3">Résumé</h2>
<div class="row g-2 small"> <div class="row g-2 small">
<div class="col-6"><strong>Signatures:</strong> <span id="demo3SummarySignatureCount">0</span></div> <div class="col-6"><strong>Signatures:</strong> <span id="demo3SummarySignatureCount">0</span></div>
<div class="col-6"><strong>Unique candidates:</strong> <span id="demo3SummaryUniqueSignatureCount">0</span></div>
<div class="col-6"><strong>Tx fetched:</strong> <span id="demo3SummaryFetchedTxCount">0</span></div> <div class="col-6"><strong>Tx fetched:</strong> <span id="demo3SummaryFetchedTxCount">0</span></div>
<div class="col-6"><strong>Missing tx:</strong> <span id="demo3SummaryMissingTxCount">0</span></div> <div class="col-6"><strong>Missing tx:</strong> <span id="demo3SummaryMissingTxCount">0</span></div>
<div class="col-6"><strong>Failed tx:</strong> <span id="demo3SummaryFailedTxCount">0</span></div> <div class="col-6"><strong>Failed tx:</strong> <span id="demo3SummaryFailedTxCount">0</span></div>
<div class="col-6"><strong>Skipped failed:</strong> <span id="demo3SummarySkippedFailedTxCount">0</span></div>
<div class="col-6"><strong>Skipped swap tx:</strong> <span id="demo3SummarySkippedSwapTxCount">0</span></div>
<div class="col-6"><strong>Extracted:</strong> <span id="demo3SummaryExtractedCandidateCount">0</span></div>
<div class="col-6"><strong>Rejected by target:</strong> <span id="demo3SummaryRejectedCandidateCount">0</span></div>
<div class="col-6"><strong>Candidates:</strong> <span id="demo3SummaryCandidateCount">0</span></div> <div class="col-6"><strong>Candidates:</strong> <span id="demo3SummaryCandidateCount">0</span></div>
<div class="col-6"><strong>Local pairs:</strong> <span id="demo3SummaryLocalPairCount">0</span></div> <div class="col-6"><strong>Local pairs:</strong> <span id="demo3SummaryLocalPairCount">0</span></div>
</div> </div>
@@ -120,6 +172,10 @@
<strong>Target:</strong> <strong>Target:</strong>
<span id="demo3TargetText" class="font-monospace">-</span> <span id="demo3TargetText" class="font-monospace">-</span>
</div> </div>
<div class="small text-body-secondary mt-2">
<strong>Backfill signatures:</strong>
<span id="demo3UniqueSignatureText" class="font-monospace">-</span>
</div>
</div> </div>
</div> </div>
@@ -144,6 +200,7 @@
<th>Slot</th> <th>Slot</th>
<th>Kind</th> <th>Kind</th>
<th>Confidence</th> <th>Confidence</th>
<th>Data prefix</th>
<th>Verified pool</th> <th>Verified pool</th>
<th>Token A</th> <th>Token A</th>
<th>Token B</th> <th>Token B</th>
@@ -154,7 +211,33 @@
</tr> </tr>
</thead> </thead>
<tbody id="demo3OnchainCandidateTableBody"> <tbody id="demo3OnchainCandidateTableBody">
<tr><td colspan="11" class="text-body-secondary">No on-chain candidate.</td></tr> <tr>
<td colspan="12" class="text-body-secondary">No on-chain candidate.</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<h2 class="h5 mb-3">Rejected candidate summary</h2>
<div class="table-responsive">
<table class="table table-sm align-middle mb-0">
<thead>
<tr>
<th>Kind</th>
<th>Prefix</th>
<th>Instruction</th>
<th>Reason</th>
<th>Count</th>
</tr>
</thead>
<tbody id="demo3RejectedSummaryTableBody">
<tr>
<td colspan="5" class="text-body-secondary">No rejected candidate summary.</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@@ -179,7 +262,9 @@
</tr> </tr>
</thead> </thead>
<tbody id="demo3LocalPoolPairTableBody"> <tbody id="demo3LocalPoolPairTableBody">
<tr><td colspan="8" class="text-body-secondary">No local pool/pair sample.</td></tr> <tr>
<td colspan="8" class="text-body-secondary">No local pool/pair sample.</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@@ -179,7 +179,8 @@
<div class="mb-3"> <div class="mb-3">
<label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label> <label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label>
<select id="demoPipeline2ValidationProfileSelect" class="form-select"> <select id="demoPipeline2ValidationProfileSelect" class="form-select">
<option value="0.7.42_raydium_family_event_coverage" selected>0.7.42Raydium family event coverage</option> <option value="0.7.43_meteora_effective_surfaces" selected>0.7.43Meteora family event coverage</option>
<option value="0.7.42_raydium_family_event_coverage">0.7.42 — Raydium family event coverage</option>
<option value="0.7.41_raydium_amm_v4_swap_decoder">0.7.41 — Raydium AMM v4 swap decoder</option> <option value="0.7.41_raydium_amm_v4_swap_decoder">0.7.41 — Raydium AMM v4 swap decoder</option>
<option value="0.7.40_raydium_effective_surfaces">0.7.40 — Raydium effective surfaces</option> <option value="0.7.40_raydium_effective_surfaces">0.7.40 — Raydium effective surfaces</option>
<option value="0.7.39_dex_first_effective_swap_surfaces">0.7.39 — DEX-first effective swap surfaces</option> <option value="0.7.39_dex_first_effective_swap_surfaces">0.7.39 — DEX-first effective swap surfaces</option>

View File

@@ -12,6 +12,26 @@ dexCode: string | null,
* Optional Solana program id. When absent, dex_code must resolve to a verified program id. * Optional Solana program id. When absent, dex_code must resolve to a verified program id.
*/ */
programId: string | null, programId: string | null,
/**
* Optional signature source: `program_id` or `address`.
*/
signatureSource: string | null,
/**
* Optional source address used when signature_source is `address`.
*/
sourceAddress: string | null,
/**
* Optional target event family used to find non-swap signatures.
*/
targetEvent: string | null,
/**
* Whether transactions containing swap-like logs should be skipped.
*/
excludeSwaps: boolean,
/**
* Whether failed transactions should be returned as candidates.
*/
includeFailed: boolean,
/** /**
* HTTP role used to query Solana RPC. * HTTP role used to query Solana RPC.
*/ */

View File

@@ -1,6 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Demo3OnchainDexDiscoveryRequest } from "./Demo3OnchainDexDiscoveryRequest"; import type { Demo3OnchainDexDiscoveryRequest } from "./Demo3OnchainDexDiscoveryRequest";
import type { Demo3OnchainDexPairCandidate } from "./Demo3OnchainDexPairCandidate"; import type { Demo3OnchainDexPairCandidate } from "./Demo3OnchainDexPairCandidate";
import type { Demo3OnchainDexRejectedCandidateSummary } from "./Demo3OnchainDexRejectedCandidateSummary";
/** /**
* Structured on-chain DEX discovery result. * Structured on-chain DEX discovery result.
@@ -15,9 +16,29 @@ request: Demo3OnchainDexDiscoveryRequest,
*/ */
resolvedDexCode: string | null, resolvedDexCode: string | null,
/** /**
* Program id scanned with getSignaturesForAddress. * Program id used to filter matched instructions.
*/ */
resolvedProgramId: string, resolvedProgramId: string,
/**
* Signature source actually used by getSignaturesForAddress.
*/
resolvedSignatureSource: string,
/**
* Address scanned with getSignaturesForAddress.
*/
resolvedSignatureAddress: string,
/**
* Number of unique candidate signatures.
*/
uniqueSignatureCount: number,
/**
* Unique signatures ready for signature backfill.
*/
uniqueBackfillSignatures: Array<string>,
/**
* Rejected candidate summary.
*/
rejectedCandidateSummary: Array<Demo3OnchainDexRejectedCandidateSummary>,
/** /**
* Number of signatures returned by Solana RPC. * Number of signatures returned by Solana RPC.
*/ */
@@ -34,6 +55,22 @@ missingTransactionCount: number,
* Number of failed transactions encountered. * Number of failed transactions encountered.
*/ */
failedTransactionCount: number, failedTransactionCount: number,
/**
* Number of failed transactions skipped because include_failed is false.
*/
skippedFailedTransactionCount: number,
/**
* Number of swap-log transactions skipped by the transaction-level swap guard.
*/
skippedSwapLogTransactionCount: number,
/**
* Number of candidate rows extracted before target-event filtering.
*/
extractedCandidateCount: number,
/**
* Number of candidate rows rejected by target-event filtering.
*/
targetRejectedCandidateCount: number,
/** /**
* Number of candidate rows returned. * Number of candidate rows returned.
*/ */

View File

@@ -50,6 +50,10 @@ innerInstructionIndex: number | null,
* Instruction name inferred from data/logs. * Instruction name inferred from data/logs.
*/ */
instructionName: string | null, instructionName: string | null,
/**
* Prefix of the raw base58 instruction data, useful for audit grouping.
*/
instructionDataPrefix: string | null,
/** /**
* Candidate pool address. * Candidate pool address.
*/ */

View File

@@ -0,0 +1,26 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* Rejected on-chain discovery candidate summary.
*/
export type Demo3OnchainDexRejectedCandidateSummary = {
/**
* Candidate kind rejected by target filtering.
*/
candidateKind: string,
/**
* Optional instruction data prefix.
*/
instructionDataPrefix: string | null,
/**
* Optional instruction name.
*/
instructionName: string | null,
/**
* Rejection reason.
*/
rejectionReason: string,
/**
* Count of matching rejected candidates.
*/
count: number, };

View File

@@ -37,6 +37,59 @@ const presets: Demo3Preset[] = [
{ label: "Orca Whirlpools", dexCode: "orca_whirlpools", programId: "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc", description: "Orca Whirlpools CLMM." }, { label: "Orca Whirlpools", dexCode: "orca_whirlpools", programId: "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc", description: "Orca Whirlpools CLMM." },
{ label: "FluxBeam", dexCode: "fluxbeam", programId: "FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X", description: "FluxBeam." }, { label: "FluxBeam", dexCode: "fluxbeam", programId: "FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X", description: "FluxBeam." },
{ label: "DexLab", dexCode: "dexlab", programId: "DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N", description: "DexLab Swap/Pool." }, { label: "DexLab", dexCode: "dexlab", programId: "DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N", description: "DexLab Swap/Pool." },
{ label: "Aldrin (historical)", dexCode: "aldrin", programId: "AMM55ShdkoGRB5jVYPjWziwk8m5MpwyDgsMWHaMSQWH6", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Aldrin V2 (historical)", dexCode: "aldrin_v2", programId: "CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Crema (historical)", dexCode: "crema", programId: "CLMM9tUoggJu2wagPkkqs9eFG4BWhVBZWkP1qv3Sp7tR", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Cropper (historical)", dexCode: "cropper", programId: "H8W3ctz92svYg6mkn1UtGfu2aQr2fnUFHM1RhScEtQDt", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Lifinity V1 (historical)", dexCode: "lifinity_v1", programId: "EewxydAPCCVuNEyrVN68PuSYdQ7wKn27V9Gjeoi8dy3S", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Lifinity V2 (historical)", dexCode: "lifinity_v2", programId: "2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Mercurial (historical)", dexCode: "mercurial", programId: "MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Orca V1 (historical)", dexCode: "orca_v1", programId: "DjVE6JNiYqPL2QXyCUUh8rNjHrbz9hXHNYt99MQ59qw1", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Orca V2 (historical)", dexCode: "orca_v2", programId: "9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Phoenix (historical)", dexCode: "phoenix", programId: "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Saber (historical)", dexCode: "saber", programId: "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "OpenBook V2 (historical)", dexCode: "openbook_v2", programId: "opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "OpenBook (historical)", dexCode: "openbook", programId: "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Fox (historical)", dexCode: "fox", programId: "HyhpEq587ANShDdbx1mP4dTmDZC44CXWft29oYQXDb53", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Sanctum Infinity (historical)", dexCode: "sanctum_infinity", programId: "5ocnV1qiCgaQR8Jb8xWnVbApfaygJ8tNoZfgPwsgx9kx", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Saros (historical)", dexCode: "saros", programId: "SSwapUtytfBdBn1b9NUGG6foMVPtcWgpRU32HToDUZr", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Perps (historical)", dexCode: "perps", programId: "PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "StepN (historical)", dexCode: "stepn", programId: "Dooar9JkhdZ7J3LHN3A7YCuoGRUggXhQaG4kijfLGU2j", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Solayer (historical)", dexCode: "solayer", programId: "endoLNCKTqDn8gSVnN2hDdpgACUPWHZTwoYnnMybpAT", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Penguin (historical)", dexCode: "penguin", programId: "PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Sanctum (historical)", dexCode: "sanctum", programId: "stkitrT1Uoy18Dk1fTrgPw8W6MVzoCfYoAFT4MLsmhq", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Guacswap (historical)", dexCode: "guacswap", programId: "Gswppe6ERWKpUTXvRPfXdzHhiCyJvLadVvXGfdpBqcE1", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Oasis (historical)", dexCode: "oasis", programId: "9tKE7Mbmj4mxDjWatikzGAtkoWosiiZX9y6J4Hfm2R8H", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Saber Decimals (historical)", dexCode: "saber_decimals", programId: "DecZY86MU5Gj7kppfUCEmd4LbXXuyZH1yHaP2NTqdiZB", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Stabble Stable Swap (historical)", dexCode: "stabble_stable_swap", programId: "swapNyd8XiQwJ6ianp9snpu4brUqFxadzvHebnAXjJZ", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Stabble Weighted Swap (historical)", dexCode: "stabble_weighted_swap", programId: "swapFpHZwjELNnjvThjajtiVmkz3yPQEHjLtka2fwHW", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "1DEX (historical)", dexCode: "one_dex", programId: "DEXYosS6oEGvk8uCDayvwEZz4qEyDJRf9nFgYCaqPMTm", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "SolFi (historical)", dexCode: "solfi", programId: "SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Moonshot (historical)", dexCode: "moonshot", programId: "MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Bonkswap (historical)", dexCode: "bonkswap", programId: "BSwp6bEBihVLdqJRKGgzjcGLHkcTuzmSo1TQkHepzH8p", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Invariant (historical)", dexCode: "invariant", programId: "HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Token Swap (historical)", dexCode: "token_swap", programId: "SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Helium Network (historical)", dexCode: "helium_network", programId: "treaf4wWBBty3fHdyBpo35Mz84M8k3heKXmjmi9vFt5", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Raydium Legacy V2 (historical)", dexCode: "raydium_legacy_v2", programId: "27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Raydium Legacy V3 (historical)", dexCode: "raydium_legacy_v3", programId: "7quYqsZdpWSZ3qgDextersDqoKjZy7aCgwHBBfRb7KPt", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Serum V3 (historical)", dexCode: "serum_v3", programId: "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Mango Private Pools (historical)", dexCode: "mango_private_pools", programId: "AtdP2iyfh6xBGwVZzHvY73E7uKKkZBTH2siHh3ZuEf1P", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Marinade Liquid Staking (historical)", dexCode: "marinade_liquid_staking", programId: "MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Step Finance Pools (historical)", dexCode: "step_finance_pools", programId: "StepAscQoEioFxxWGnh2sLBDFp9d8rvKz2Yp39iDpyT", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Francium Yield Pools (historical)", dexCode: "francium_yield_pools", programId: "FC81tbGt6JWRXidaWYFXxGnTk4VgobhJHATvTRVMqgWj", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Cropper Legacy (historical)", dexCode: "cropper_legacy", programId: "CyZuD7RPDcrqCGbNvzrNVs1zpCQehqp7SuXB7rdFKSzo", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Dexlab Beta (historical)", dexCode: "dexlab_beta", programId: "9qvG1zP8ZzY1sTnExx7mkyxhW1063YHVYZxMYz2HkM4m", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Marinade Governance (historical)", dexCode: "marinade_governance", programId: "GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Serum DAO (historical)", dexCode: "serum_dao", programId: "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Port Finance (historical)", dexCode: "port_finance", programId: "Port7uDYB3wk6GJAw4KT1WpTeMtSu9bTcChBHkX2LfR", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Solend Classic (historical)", dexCode: "solend_classic", programId: "So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Hyperspace NFT AMM (historical)", dexCode: "hyperspace_nft_amm", programId: "HYPERfwdTjyJ2SCaKHmpF2MtrXqWxrsotYDsTrshHWq8", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Magic Eden NFT AMM (historical)", dexCode: "magic_eden_nft_amm", programId: "MEisE1HzehtrDpAAT8PnLHjpSSkRYakotTuJRPjTpo8", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Raydium Staking Early (historical)", dexCode: "raydium_staking_early", programId: "EhhTKczWMGQt46ynNeRX1WfeagwwJd7ufHvCDjRxjo5Q", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Orca Aquafarm V1 (historical)", dexCode: "orca_aquafarm_v1", programId: "82yxjeMsvaURa4MbZZ7WZZHfobirZYkH1zF8fmeGtyaQ", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Quarry Merge Mining (historical)", dexCode: "quarry_merge_mining", programId: "QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "SWAB Finance Beta (historical)", dexCode: "swab_finance_beta", programId: "SWABxNGyxEBVoNRGn6RvYBt5UqercSE5PBHuJeYXYHq", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "CRAFT Early (historical)", dexCode: "craft_early", programId: "CrAFTUv7zKXBaS5471aBiARBu6x7nP4rDzr8xwBewbr1", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "metaDAO", dexCode: "metadao", programId: "", description: "DEX à vérifier. Aucun program id n'est inventé." }, { label: "metaDAO", dexCode: "metadao", programId: "", description: "DEX à vérifier. Aucun program id n'est inventé." },
{ label: "Printr", dexCode: "printr", programId: "", description: "DEX à vérifier. Aucun program id n'est inventé." }, { label: "Printr", dexCode: "printr", programId: "", description: "DEX à vérifier. Aucun program id n'est inventé." },
]; ];
@@ -56,6 +109,26 @@ function valueOrNull(value: string): string | null {
return trimmed === "" ? null : trimmed; return trimmed === "" ? null : trimmed;
} }
function isSolanaAddressLike(value: string): boolean {
const trimmed = value.trim();
if (trimmed.length < 32 || trimmed.length > 44) {
return false;
}
return /^[1-9A-HJ-NP-Za-km-z]+$/.test(trimmed);
}
function validateOnchainRequest(request: Demo3OnchainDexDiscoveryRequest): void {
if (request.signatureSource === "address") {
const sourceAddress = request.sourceAddress ?? "";
if (!isSolanaAddressLike(sourceAddress)) {
throw new Error("Signature source is 'address': Source address must be a real Solana account address, pool, vault, position, config or mint. It cannot be empty or the literal value 'address'.");
}
}
if (request.programId !== null && !isSolanaAddressLike(request.programId)) {
throw new Error("Program id filter must be a valid Solana program id, or empty when using a preset that resolves it.");
}
}
function numberValueOrNull(value: string): number | null { function numberValueOrNull(value: string): number | null {
const trimmed = value.trim(); const trimmed = value.trim();
if (trimmed === "") { if (trimmed === "") {
@@ -157,6 +230,11 @@ function readOnchainRequest(): Demo3OnchainDexDiscoveryRequest {
return { return {
dexCode: valueOrNull(byId<HTMLInputElement>("demo3DexCodeInput").value), dexCode: valueOrNull(byId<HTMLInputElement>("demo3DexCodeInput").value),
programId: valueOrNull(byId<HTMLInputElement>("demo3ProgramIdInput").value), programId: valueOrNull(byId<HTMLInputElement>("demo3ProgramIdInput").value),
signatureSource: valueOrNull(byId<HTMLSelectElement>("demo3SignatureSourceSelect").value),
sourceAddress: valueOrNull(byId<HTMLInputElement>("demo3SourceAddressInput").value),
targetEvent: valueOrNull(byId<HTMLSelectElement>("demo3TargetEventSelect").value),
excludeSwaps: byId<HTMLInputElement>("demo3ExcludeSwapsInput").checked,
includeFailed: byId<HTMLInputElement>("demo3IncludeFailedInput").checked,
httpRole: byId<HTMLInputElement>("demo3HttpRoleInput").value.trim() || "history_backfill", httpRole: byId<HTMLInputElement>("demo3HttpRoleInput").value.trim() || "history_backfill",
signatureLimit: intValue("demo3SignatureLimitInput", 50), signatureLimit: intValue("demo3SignatureLimitInput", 50),
transactionLimit: intValue("demo3TransactionLimitInput", 25), transactionLimit: intValue("demo3TransactionLimitInput", 25),
@@ -179,28 +257,57 @@ function readLocalRequest(): Demo3LocalDexCorpusSearchRequest {
function clearFilters(): void { function clearFilters(): void {
byId<HTMLInputElement>("demo3DexCodeInput").value = ""; byId<HTMLInputElement>("demo3DexCodeInput").value = "";
byId<HTMLInputElement>("demo3ProgramIdInput").value = ""; byId<HTMLInputElement>("demo3ProgramIdInput").value = "";
byId<HTMLSelectElement>("demo3SignatureSourceSelect").value = "program_id";
byId<HTMLInputElement>("demo3SourceAddressInput").value = "";
byId<HTMLInputElement>("demo3PairIdInput").value = ""; byId<HTMLInputElement>("demo3PairIdInput").value = "";
byId<HTMLInputElement>("demo3PoolAddressInput").value = ""; byId<HTMLInputElement>("demo3PoolAddressInput").value = "";
byId<HTMLInputElement>("demo3TokenMintInput").value = ""; byId<HTMLInputElement>("demo3TokenMintInput").value = "";
byId<HTMLInputElement>("demo3SignatureInput").value = ""; byId<HTMLInputElement>("demo3SignatureInput").value = "";
byId<HTMLSelectElement>("demo3TargetEventSelect").value = "";
byId<HTMLInputElement>("demo3ExcludeSwapsInput").checked = false;
byId<HTMLInputElement>("demo3IncludeFailedInput").checked = true;
byId<HTMLSelectElement>("demo3PresetSelect").value = ""; byId<HTMLSelectElement>("demo3PresetSelect").value = "";
byId<HTMLElement>("demo3PresetHelp").textContent = "Choisis un DEX ou saisis un program id manuellement."; byId<HTMLElement>("demo3PresetHelp").textContent = "Choisis un DEX ou saisis un program id manuellement.";
} }
function renderOnchainResult(result: Demo3OnchainDexDiscoveryResult): void { function renderOnchainResult(result: Demo3OnchainDexDiscoveryResult): void {
byId<HTMLElement>("demo3SummarySignatureCount").textContent = String(result.fetchedSignatureCount); byId<HTMLElement>("demo3SummarySignatureCount").textContent = String(result.fetchedSignatureCount);
byId<HTMLElement>("demo3SummaryUniqueSignatureCount").textContent = String(result.uniqueSignatureCount);
byId<HTMLElement>("demo3SummaryFetchedTxCount").textContent = String(result.fetchedTransactionCount); byId<HTMLElement>("demo3SummaryFetchedTxCount").textContent = String(result.fetchedTransactionCount);
byId<HTMLElement>("demo3SummaryMissingTxCount").textContent = String(result.missingTransactionCount); byId<HTMLElement>("demo3SummaryMissingTxCount").textContent = String(result.missingTransactionCount);
byId<HTMLElement>("demo3SummaryFailedTxCount").textContent = String(result.failedTransactionCount); byId<HTMLElement>("demo3SummaryFailedTxCount").textContent = String(result.failedTransactionCount);
byId<HTMLElement>("demo3SummarySkippedFailedTxCount").textContent = String(result.skippedFailedTransactionCount);
byId<HTMLElement>("demo3SummarySkippedSwapTxCount").textContent = String(result.skippedSwapLogTransactionCount);
byId<HTMLElement>("demo3SummaryExtractedCandidateCount").textContent = String(result.extractedCandidateCount);
byId<HTMLElement>("demo3SummaryRejectedCandidateCount").textContent = String(result.targetRejectedCandidateCount);
byId<HTMLElement>("demo3SummaryCandidateCount").textContent = String(result.candidateCount); byId<HTMLElement>("demo3SummaryCandidateCount").textContent = String(result.candidateCount);
byId<HTMLElement>("demo3TargetText").textContent = `${result.resolvedDexCode ?? "custom"} / ${result.resolvedProgramId}`; const targetEvent = result.request.targetEvent ?? "any";
byId<HTMLElement>("demo3TargetText").textContent = `${result.resolvedDexCode ?? "custom"} / program=${result.resolvedProgramId} / source=${result.resolvedSignatureSource}:${result.resolvedSignatureAddress} / target=${targetEvent}`;
byId<HTMLElement>("demo3UniqueSignatureText").textContent = result.uniqueBackfillSignatures.length === 0 ? "-" : result.uniqueBackfillSignatures.join(", ");
renderRejectedSummary(result);
renderOnchainCandidates(result.candidates); renderOnchainCandidates(result.candidates);
} }
function renderRejectedSummary(result: Demo3OnchainDexDiscoveryResult): void {
const body = byId<HTMLTableSectionElement>("demo3RejectedSummaryTableBody");
if (result.rejectedCandidateSummary.length === 0) {
body.innerHTML = '<tr><td colspan="5" class="text-body-secondary">No rejected candidate summary.</td></tr>';
return;
}
body.innerHTML = result.rejectedCandidateSummary.map((summary) => `
<tr>
<td>${escapeHtml(summary.candidateKind)}</td>
<td class="font-monospace" title="${escapeHtml(summary.instructionDataPrefix ?? "")}">${escapeHtml(shortText(summary.instructionDataPrefix, 16))}</td>
<td>${escapeHtml(summary.instructionName ?? "-")}</td>
<td>${escapeHtml(summary.rejectionReason)}</td>
<td>${summary.count}</td>
</tr>`).join("");
}
function renderOnchainCandidates(candidates: Demo3OnchainDexPairCandidate[]): void { function renderOnchainCandidates(candidates: Demo3OnchainDexPairCandidate[]): void {
const body = byId<HTMLTableSectionElement>("demo3OnchainCandidateTableBody"); const body = byId<HTMLTableSectionElement>("demo3OnchainCandidateTableBody");
if (candidates.length === 0) { if (candidates.length === 0) {
body.innerHTML = '<tr><td colspan="11" class="text-body-secondary">No on-chain candidate.</td></tr>'; body.innerHTML = '<tr><td colspan="12" class="text-body-secondary">No on-chain candidate.</td></tr>';
return; return;
} }
body.innerHTML = candidates.map((candidate) => { body.innerHTML = candidates.map((candidate) => {
@@ -225,6 +332,7 @@ function renderOnchainCandidates(candidates: Demo3OnchainDexPairCandidate[]): vo
<td>${candidate.slot ?? "-"}</td> <td>${candidate.slot ?? "-"}</td>
<td><span class="badge text-bg-info">${escapeHtml(candidate.candidateKind)}</span></td> <td><span class="badge text-bg-info">${escapeHtml(candidate.candidateKind)}</span></td>
<td><span class="badge text-bg-${candidate.confidence === "high" ? "success" : candidate.confidence === "medium" ? "warning" : "secondary"}">${escapeHtml(candidate.confidence)}</span></td> <td><span class="badge text-bg-${candidate.confidence === "high" ? "success" : candidate.confidence === "medium" ? "warning" : "secondary"}">${escapeHtml(candidate.confidence)}</span></td>
<td class="font-monospace" title="${escapeHtml(candidate.instructionDataPrefix ?? "")}">${escapeHtml(shortText(candidate.instructionDataPrefix, 14))}</td>
<td class="font-monospace" title="${escapeHtml(verifiedPool ?? "")}">${escapeHtml(shortText(verifiedPool, 14))}</td> <td class="font-monospace" title="${escapeHtml(verifiedPool ?? "")}">${escapeHtml(shortText(verifiedPool, 14))}</td>
<td class="font-monospace" title="${escapeHtml(candidate.tokenAMint ?? "")}">${escapeHtml(shortText(candidate.tokenAMint, 14))}</td> <td class="font-monospace" title="${escapeHtml(candidate.tokenAMint ?? "")}">${escapeHtml(shortText(candidate.tokenAMint, 14))}</td>
<td class="font-monospace" title="${escapeHtml(candidate.tokenBMint ?? "")}">${escapeHtml(shortText(candidate.tokenBMint, 14))}</td> <td class="font-monospace" title="${escapeHtml(candidate.tokenBMint ?? "")}">${escapeHtml(shortText(candidate.tokenBMint, 14))}</td>
@@ -258,15 +366,22 @@ function renderLocalResult(result: Demo3LocalDexCorpusSearchResult): void {
async function discoverOnchain(): Promise<void> { async function discoverOnchain(): Promise<void> {
const request = readOnchainRequest(); const request = readOnchainRequest();
try {
validateOnchainRequest(request);
} catch (error) {
setStatus("error", "text-bg-danger");
appendLogLine(`on-chain discovery rejected locally: ${String(error)}`);
return;
}
setStatus("running", "text-bg-warning"); setStatus("running", "text-bg-warning");
appendLogLine(`on-chain discovery dex='${request.dexCode ?? ""}' program='${request.programId ?? ""}' role='${request.httpRole}'`); appendLogLine(`on-chain discovery dex='${request.dexCode ?? ""}' program='${request.programId ?? ""}' source='${request.signatureSource ?? "program_id"}:${request.sourceAddress ?? ""}' target='${request.targetEvent ?? "any"}' excludeSwaps='${request.excludeSwaps}' role='${request.httpRole}'`);
try { try {
const payload = await invoke<Demo3OnchainDexDiscoveryPayload>("demo3_discover_onchain_dex_pairs", { request }); const payload = await invoke<Demo3OnchainDexDiscoveryPayload>("demo3_discover_onchain_dex_pairs", { request });
lastResultJson = payload.resultJson; lastResultJson = payload.resultJson;
byId<HTMLTextAreaElement>("demo3JsonTextarea").value = payload.resultJson; byId<HTMLTextAreaElement>("demo3JsonTextarea").value = payload.resultJson;
renderOnchainResult(payload.result); renderOnchainResult(payload.result);
setStatus("ok", "text-bg-success"); setStatus("ok", "text-bg-success");
appendLogLine(`on-chain discovery completed: candidates='${payload.result.candidateCount}' signatures='${payload.result.fetchedSignatureCount}'`); appendLogLine(`on-chain discovery completed: candidates='${payload.result.candidateCount}' unique='${payload.result.uniqueSignatureCount}' signatures='${payload.result.fetchedSignatureCount}' extracted='${payload.result.extractedCandidateCount}' rejected='${payload.result.targetRejectedCandidateCount}' skippedSwapTx='${payload.result.skippedSwapLogTransactionCount}'`);
} catch (error) { } catch (error) {
setStatus("error", "text-bg-danger"); setStatus("error", "text-bg-danger");
appendLogLine(`on-chain discovery failed: ${String(error)}`); appendLogLine(`on-chain discovery failed: ${String(error)}`);

View File

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

View File

@@ -431,6 +431,16 @@ pub(crate) struct Demo3OnchainDexDiscoveryRequest {
pub dex_code: std::option::Option<std::string::String>, pub dex_code: std::option::Option<std::string::String>,
/// Optional Solana program id. When absent, dex_code must resolve to a verified program id. /// Optional Solana program id. When absent, dex_code must resolve to a verified program id.
pub program_id: std::option::Option<std::string::String>, pub program_id: std::option::Option<std::string::String>,
/// Optional signature source: `program_id` or `address`.
pub signature_source: std::option::Option<std::string::String>,
/// Optional source address used when signature_source is `address`.
pub source_address: std::option::Option<std::string::String>,
/// Optional target event family used to find non-swap signatures.
pub target_event: std::option::Option<std::string::String>,
/// Whether transactions containing swap-like logs should be skipped.
pub exclude_swaps: bool,
/// Whether failed transactions should be returned as candidates.
pub include_failed: bool,
/// HTTP role used to query Solana RPC. /// HTTP role used to query Solana RPC.
pub http_role: std::string::String, pub http_role: std::string::String,
/// Maximum number of signatures to inspect. /// Maximum number of signatures to inspect.
@@ -463,8 +473,19 @@ pub(crate) struct Demo3OnchainDexDiscoveryResult {
pub request: Demo3OnchainDexDiscoveryRequest, pub request: Demo3OnchainDexDiscoveryRequest,
/// DEX code resolved from the support matrix when available. /// DEX code resolved from the support matrix when available.
pub resolved_dex_code: std::option::Option<std::string::String>, pub resolved_dex_code: std::option::Option<std::string::String>,
/// Program id scanned with getSignaturesForAddress. /// Program id used to filter matched instructions.
pub resolved_program_id: std::string::String, pub resolved_program_id: std::string::String,
/// Signature source actually used by getSignaturesForAddress.
pub resolved_signature_source: std::string::String,
/// Address scanned with getSignaturesForAddress.
pub resolved_signature_address: std::string::String,
/// Number of unique candidate signatures.
#[ts(type = "number")]
pub unique_signature_count: usize,
/// Unique signatures ready for signature backfill.
pub unique_backfill_signatures: std::vec::Vec<std::string::String>,
/// Rejected candidate summary.
pub rejected_candidate_summary: std::vec::Vec<Demo3OnchainDexRejectedCandidateSummary>,
/// Number of signatures returned by Solana RPC. /// Number of signatures returned by Solana RPC.
#[ts(type = "number")] #[ts(type = "number")]
pub fetched_signature_count: usize, pub fetched_signature_count: usize,
@@ -477,6 +498,18 @@ pub(crate) struct Demo3OnchainDexDiscoveryResult {
/// Number of failed transactions encountered. /// Number of failed transactions encountered.
#[ts(type = "number")] #[ts(type = "number")]
pub failed_transaction_count: usize, pub failed_transaction_count: usize,
/// Number of failed transactions skipped because include_failed is false.
#[ts(type = "number")]
pub skipped_failed_transaction_count: usize,
/// Number of swap-log transactions skipped by the transaction-level swap guard.
#[ts(type = "number")]
pub skipped_swap_log_transaction_count: usize,
/// Number of candidate rows extracted before target-event filtering.
#[ts(type = "number")]
pub extracted_candidate_count: usize,
/// Number of candidate rows rejected by target-event filtering.
#[ts(type = "number")]
pub target_rejected_candidate_count: usize,
/// Number of candidate rows returned. /// Number of candidate rows returned.
#[ts(type = "number")] #[ts(type = "number")]
pub candidate_count: usize, pub candidate_count: usize,
@@ -484,6 +517,27 @@ pub(crate) struct Demo3OnchainDexDiscoveryResult {
pub candidates: std::vec::Vec<Demo3OnchainDexPairCandidate>, pub candidates: std::vec::Vec<Demo3OnchainDexPairCandidate>,
} }
/// Rejected on-chain discovery candidate summary.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/Demo3OnchainDexRejectedCandidateSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3OnchainDexRejectedCandidateSummary {
/// Candidate kind rejected by target filtering.
pub candidate_kind: std::string::String,
/// Optional instruction data prefix.
pub instruction_data_prefix: std::option::Option<std::string::String>,
/// Optional instruction name.
pub instruction_name: std::option::Option<std::string::String>,
/// Rejection reason.
pub rejection_reason: std::string::String,
/// Count of matching rejected candidates.
#[ts(type = "number")]
pub count: usize,
}
/// Candidate on-chain transaction/instruction for a DEX program id. /// Candidate on-chain transaction/instruction for a DEX program id.
#[derive(Clone, Debug, serde::Serialize, TS)] #[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexPairCandidate.ts")] #[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexPairCandidate.ts")]
@@ -515,6 +569,8 @@ pub(crate) struct Demo3OnchainDexPairCandidate {
pub inner_instruction_index: std::option::Option<i64>, pub inner_instruction_index: std::option::Option<i64>,
/// Instruction name inferred from data/logs. /// Instruction name inferred from data/logs.
pub instruction_name: std::option::Option<std::string::String>, pub instruction_name: std::option::Option<std::string::String>,
/// Prefix of the raw base58 instruction data, useful for audit grouping.
pub instruction_data_prefix: std::option::Option<std::string::String>,
/// Candidate pool address. /// Candidate pool address.
pub pool_address: std::option::Option<std::string::String>, pub pool_address: std::option::Option<std::string::String>,
/// Candidate token A mint. /// Candidate token A mint.
@@ -627,6 +683,11 @@ fn to_lib_onchain_request(
return kb_lib::OnchainDexPairDiscoveryRequestDto { return kb_lib::OnchainDexPairDiscoveryRequestDto {
dex_code: normalize_optional_text(request.dex_code.clone()), dex_code: normalize_optional_text(request.dex_code.clone()),
program_id: normalize_optional_text(request.program_id.clone()), program_id: normalize_optional_text(request.program_id.clone()),
signature_source: normalize_optional_text(request.signature_source.clone()),
source_address: normalize_optional_text(request.source_address.clone()),
target_event: normalize_optional_text(request.target_event.clone()),
exclude_swaps: request.exclude_swaps,
include_failed: request.include_failed,
http_role: request.http_role.trim().to_string(), http_role: request.http_role.trim().to_string(),
signature_limit: request.signature_limit, signature_limit: request.signature_limit,
transaction_limit: request.transaction_limit, transaction_limit: request.transaction_limit,
@@ -645,6 +706,11 @@ fn from_lib_onchain_result(
request: Demo3OnchainDexDiscoveryRequest { request: Demo3OnchainDexDiscoveryRequest {
dex_code: result.request.dex_code, dex_code: result.request.dex_code,
program_id: result.request.program_id, program_id: result.request.program_id,
signature_source: result.request.signature_source,
source_address: result.request.source_address,
target_event: result.request.target_event,
exclude_swaps: result.request.exclude_swaps,
include_failed: result.request.include_failed,
http_role: result.request.http_role, http_role: result.request.http_role,
signature_limit: result.request.signature_limit, signature_limit: result.request.signature_limit,
transaction_limit: result.request.transaction_limit, transaction_limit: result.request.transaction_limit,
@@ -652,15 +718,42 @@ fn from_lib_onchain_result(
}, },
resolved_dex_code: result.resolved_dex_code, resolved_dex_code: result.resolved_dex_code,
resolved_program_id: result.resolved_program_id, resolved_program_id: result.resolved_program_id,
resolved_signature_source: result.resolved_signature_source,
resolved_signature_address: result.resolved_signature_address,
unique_signature_count: result.unique_signature_count,
unique_backfill_signatures: result.unique_backfill_signatures,
rejected_candidate_summary: from_lib_rejected_candidate_summary(
result.rejected_candidate_summary,
),
fetched_signature_count: result.fetched_signature_count, fetched_signature_count: result.fetched_signature_count,
fetched_transaction_count: result.fetched_transaction_count, fetched_transaction_count: result.fetched_transaction_count,
missing_transaction_count: result.missing_transaction_count, missing_transaction_count: result.missing_transaction_count,
failed_transaction_count: result.failed_transaction_count, failed_transaction_count: result.failed_transaction_count,
skipped_failed_transaction_count: result.skipped_failed_transaction_count,
skipped_swap_log_transaction_count: result.skipped_swap_log_transaction_count,
extracted_candidate_count: result.extracted_candidate_count,
target_rejected_candidate_count: result.target_rejected_candidate_count,
candidate_count: result.candidate_count, candidate_count: result.candidate_count,
candidates, candidates,
}; };
} }
fn from_lib_rejected_candidate_summary(
values: std::vec::Vec<kb_lib::OnchainDexRejectedCandidateSummaryDto>,
) -> std::vec::Vec<Demo3OnchainDexRejectedCandidateSummary> {
let mut mapped = std::vec::Vec::new();
for value in values {
mapped.push(Demo3OnchainDexRejectedCandidateSummary {
candidate_kind: value.candidate_kind,
instruction_data_prefix: value.instruction_data_prefix,
instruction_name: value.instruction_name,
rejection_reason: value.rejection_reason,
count: value.count,
});
}
return mapped;
}
fn from_lib_onchain_candidate( fn from_lib_onchain_candidate(
candidate: kb_lib::OnchainDexPairCandidateDto, candidate: kb_lib::OnchainDexPairCandidateDto,
) -> Demo3OnchainDexPairCandidate { ) -> Demo3OnchainDexPairCandidate {
@@ -676,6 +769,7 @@ fn from_lib_onchain_candidate(
instruction_index: candidate.instruction_index, instruction_index: candidate.instruction_index,
inner_instruction_index: candidate.inner_instruction_index, inner_instruction_index: candidate.inner_instruction_index,
instruction_name: candidate.instruction_name, instruction_name: candidate.instruction_name,
instruction_data_prefix: candidate.instruction_data_prefix,
pool_address: candidate.pool_address, pool_address: candidate.pool_address,
token_a_mint: candidate.token_a_mint, token_a_mint: candidate.token_a_mint,
token_b_mint: candidate.token_b_mint, token_b_mint: candidate.token_b_mint,

View File

@@ -1279,7 +1279,7 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
let service = kb_lib::LocalPipelineValidationService::new(database.clone()); let service = kb_lib::LocalPipelineValidationService::new(database.clone());
let profile_code = match request { let profile_code = match request {
Some(request) => request.profile_code, Some(request) => request.profile_code,
None => "0.7.42_raydium_family_event_coverage".to_string(), None => "0.7.43_meteora_effective_surfaces".to_string(),
}; };
let run_result = match profile_code.as_str() { let run_result = match profile_code.as_str() {
"0.7.27" | "0.7.27_dexes_non_regression" => { "0.7.27" | "0.7.27_dexes_non_regression" => {
@@ -1332,6 +1332,9 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
"0.7.42" | "0.7.42_raydium_family_event_coverage" => { "0.7.42" | "0.7.42_raydium_family_event_coverage" => {
service.validate_v0_7_42_current_database().await service.validate_v0_7_42_current_database().await
}, },
"0.7.43" | "0.7.43_meteora_effective_surfaces" => {
service.validate_v0_7_43_current_database().await
},
other => Err(kb_lib::Error::InvalidState(format!( other => Err(kb_lib::Error::InvalidState(format!(
"unsupported local pipeline validation profile: {other}" "unsupported local pipeline validation profile: {other}"
))), ))),

View File

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

View File

@@ -178,6 +178,43 @@ pub const METEORA_DBC_PROGRAM_ID: &str = "dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4Du
/// DLMM = Dynamic Liquidity Market Maker. /// DLMM = Dynamic Liquidity Market Maker.
pub const METEORA_DLMM_PROGRAM_ID: &str = "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"; pub const METEORA_DLMM_PROGRAM_ID: &str = "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo";
/// MetaDAO META active token mint identifier from MetaDAO documentation.
pub const METADAO_META_MINT_ID: &str = "METAwkXcqyXKy1AtsSgJ8JiUHwGCafnZL38n3vYmeta";
/// MetaDAO METAC legacy token mint identifier from MetaDAO documentation.
pub const METADAO_METAC_LEGACY_MINT_ID: &str = "METADDFL6wWMWEoKTFJwcThTbUmtarRJZjRpzUvkxhr";
/// MetaDAO-linked P2P token mint candidate observed on Solscan.
///
/// This is a token mint, not a DEX program id. It is exposed for discovery only.
pub const METADAO_P2P_MINT_ID: &str = "P2PXup1ZvMpCDkJn3PQxtBYgxeCSfH39SFeurGSmeta";
/// MetaDAO Launchpad v0.7.0 program id from MetaDAO documentation and Solscan.
pub const METADAO_LAUNCHPAD_V0_7_0_PROGRAM_ID: &str =
"moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM";
/// MetaDAO Bid Wall v0.7.0 program id from MetaDAO documentation.
pub const METADAO_BID_WALL_V0_7_0_PROGRAM_ID: &str =
"WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx";
/// MetaDAO Futarchy v0.6.0 program id from MetaDAO documentation.
pub const METADAO_FUTARCHY_V0_6_0_PROGRAM_ID: &str =
"FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq";
/// MetaDAO AMM v0.5.0 program id from MetaDAO documentation.
pub const METADAO_AMM_V0_5_0_PROGRAM_ID: &str =
"AMMJdEiCCa8mdugg6JPF7gFirmmxisTfDJoSNSUi5zDJ";
/// Printr program id candidate observed on Solscan.
///
/// This remains a discovery target until a local corpus confirms the instruction semantics.
pub const PRINTR_PROGRAM_ID: &str = "T8HsGYv7sMk3kTnyaRqZrbRPuntYzdh12evXBkprint";
/// Zora program id candidate observed on Solscan.
///
/// This remains a discovery target until a local corpus confirms the instruction semantics.
pub const ZORA_PROGRAM_ID: &str = "zoRabwLGd5zXaV7Gxacppw8tcceXEiTrSKyNLSaSTUc";
/// Orca Whirlpools program id. ("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"). /// Orca Whirlpools program id. ("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc").
pub const ORCA_WHIRLPOOLS_PROGRAM_ID: &str = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"; pub const ORCA_WHIRLPOOLS_PROGRAM_ID: &str = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";

View File

@@ -221,7 +221,76 @@ impl MeteoraDammV1Decoder {
continue; continue;
} }
if instruction_kind == MeteoraDammV1InstructionKind::Swap { if instruction_kind == MeteoraDammV1InstructionKind::Swap {
let trade_side = infer_trade_side(&log_messages); let decoded_amounts_result =
crate::meteora_swap_amount_inference::infer_meteora_swap_amounts_from_inner_transfers(
transaction,
instructions,
instruction,
pool_account.as_deref(),
);
let inner_transfer_amounts = match decoded_amounts_result {
Ok(decoded_amounts) => decoded_amounts,
Err(error) => return Err(error),
};
let mut amount_resolution_source = "none";
let decoded_amounts = match inner_transfer_amounts {
Some(decoded_amounts) => {
amount_resolution_source = "flattened_cpi_pool_transfer_window";
Some(decoded_amounts)
},
None => {
let event_log_amounts_result =
crate::meteora_swap_amount_inference::infer_meteora_damm_v1_swap_amounts_from_event_log(
transaction,
accounts.as_slice(),
log_messages.as_slice(),
pool_account.as_deref(),
);
match event_log_amounts_result {
Ok(event_log_amounts) => match event_log_amounts {
Some(event_log_amounts) => {
amount_resolution_source = "meteora_damm_v1_swap_event_log";
Some(event_log_amounts)
},
None => None,
},
Err(error) => return Err(error),
}
},
};
let fallback_trade_side = infer_trade_side(&log_messages);
let trade_side = match decoded_amounts.as_ref() {
Some(decoded_amounts) => decoded_amounts.trade_side,
None => fallback_trade_side,
};
let effective_token_a_mint = match decoded_amounts.as_ref() {
Some(decoded_amounts) => Some(decoded_amounts.base_token_mint.clone()),
None => token_a_mint.clone(),
};
let effective_token_b_mint = match decoded_amounts.as_ref() {
Some(decoded_amounts) => Some(decoded_amounts.quote_token_mint.clone()),
None => token_b_mint.clone(),
};
let base_vault = match decoded_amounts.as_ref() {
Some(decoded_amounts) => decoded_amounts.base_vault_address.clone(),
None => None,
};
let quote_vault = match decoded_amounts.as_ref() {
Some(decoded_amounts) => decoded_amounts.quote_vault_address.clone(),
None => None,
};
let base_amount_raw = match decoded_amounts.as_ref() {
Some(decoded_amounts) => {
serde_json::Value::String(decoded_amounts.base_amount_raw.clone())
},
None => serde_json::Value::Null,
};
let quote_amount_raw = match decoded_amounts.as_ref() {
Some(decoded_amounts) => {
serde_json::Value::String(decoded_amounts.quote_amount_raw.clone())
},
None => serde_json::Value::Null,
};
let payload_json = serde_json::json!({ let payload_json = serde_json::json!({
"decoder": "meteora_damm_v1", "decoder": "meteora_damm_v1",
"eventKind": "swap", "eventKind": "swap",
@@ -236,8 +305,15 @@ impl MeteoraDammV1Decoder {
"parsed": parsed_json, "parsed": parsed_json,
"logMessages": log_messages, "logMessages": log_messages,
"poolAccount": pool_account, "poolAccount": pool_account,
"tokenAMint": token_a_mint, "tokenAMint": effective_token_a_mint.clone(),
"tokenBMint": token_b_mint, "tokenBMint": effective_token_b_mint.clone(),
"inputTokenAccount": extract_account(&accounts, 1),
"outputTokenAccount": extract_account(&accounts, 2),
"baseVault": base_vault,
"quoteVault": quote_vault,
"baseAmountRaw": base_amount_raw,
"quoteAmountRaw": quote_amount_raw,
"amountResolutionSource": amount_resolution_source,
"tradeSide": format!("{:?}", trade_side) "tradeSide": format!("{:?}", trade_side)
}); });
decoded_events.push(crate::MeteoraDammV1DecodedEvent::Swap( decoded_events.push(crate::MeteoraDammV1DecodedEvent::Swap(
@@ -248,8 +324,8 @@ impl MeteoraDammV1Decoder {
program_id: program_id.clone(), program_id: program_id.clone(),
trade_side, trade_side,
pool_account, pool_account,
token_a_mint, token_a_mint: effective_token_a_mint,
token_b_mint, token_b_mint: effective_token_b_mint,
payload_json, payload_json,
}, },
)); ));

View File

@@ -238,9 +238,27 @@ impl MeteoraDammV2Decoder {
if instruction_kind == MeteoraDammV2InstructionKind::Swap { if instruction_kind == MeteoraDammV2InstructionKind::Swap {
let used_swap2 = log_messages_contain_keyword(&log_messages, "swap2") let used_swap2 = log_messages_contain_keyword(&log_messages, "swap2")
|| value_contains_any_key(parsed_json.as_ref(), &["swap2", "isSwap2"]); || value_contains_any_key(parsed_json.as_ref(), &["swap2", "isSwap2"]);
let trade_side = infer_trade_side(&log_messages); let decoded_amounts_result =
let has_trade_amount_payload = crate::meteora_swap_amount_inference::infer_meteora_swap_amounts_from_inner_transfers(
transaction,
instructions,
instruction,
pool_account.as_deref(),
);
let decoded_amounts = match decoded_amounts_result {
Ok(decoded_amounts) => decoded_amounts,
Err(error) => return Err(error),
};
let fallback_trade_side = infer_trade_side(&log_messages);
let trade_side = match decoded_amounts.as_ref() {
Some(decoded_amounts) => decoded_amounts.trade_side,
None => fallback_trade_side,
};
let has_direct_amount_payload =
parsed_json_has_trade_amount_or_price_payload(parsed_json.as_ref()); parsed_json_has_trade_amount_or_price_payload(parsed_json.as_ref());
let has_inferred_amount_payload = decoded_amounts.is_some();
let has_trade_amount_payload =
has_direct_amount_payload || has_inferred_amount_payload;
let event_actionability = if has_trade_amount_payload { let event_actionability = if has_trade_amount_payload {
"trade_candidate" "trade_candidate"
} else { } else {
@@ -251,6 +269,41 @@ impl MeteoraDammV2Decoder {
} else { } else {
serde_json::Value::String("swap_without_amount_payload".to_string()) serde_json::Value::String("swap_without_amount_payload".to_string())
}; };
let effective_token_a_mint = match decoded_amounts.as_ref() {
Some(decoded_amounts) => Some(decoded_amounts.base_token_mint.clone()),
None => token_a_mint.clone(),
};
let effective_token_b_mint = match decoded_amounts.as_ref() {
Some(decoded_amounts) => Some(decoded_amounts.quote_token_mint.clone()),
None => token_b_mint.clone(),
};
let base_vault = match decoded_amounts.as_ref() {
Some(decoded_amounts) => decoded_amounts.base_vault_address.clone(),
None => None,
};
let quote_vault = match decoded_amounts.as_ref() {
Some(decoded_amounts) => decoded_amounts.quote_vault_address.clone(),
None => None,
};
let base_amount_raw = match decoded_amounts.as_ref() {
Some(decoded_amounts) => {
serde_json::Value::String(decoded_amounts.base_amount_raw.clone())
},
None => serde_json::Value::Null,
};
let quote_amount_raw = match decoded_amounts.as_ref() {
Some(decoded_amounts) => {
serde_json::Value::String(decoded_amounts.quote_amount_raw.clone())
},
None => serde_json::Value::Null,
};
let amount_resolution_source = if has_inferred_amount_payload {
"flattened_cpi_pool_transfer_window"
} else if has_direct_amount_payload {
"parsed_payload"
} else {
"none"
};
let payload_json = serde_json::json!({ let payload_json = serde_json::json!({
"decoder": "meteora_damm_v2", "decoder": "meteora_damm_v2",
"eventKind": "swap", "eventKind": "swap",
@@ -265,6 +318,7 @@ impl MeteoraDammV2Decoder {
"candleCandidate": has_trade_amount_payload, "candleCandidate": has_trade_amount_payload,
"nonTradeUseful": false, "nonTradeUseful": false,
"materializationSkipReason": materialization_skip_reason, "materializationSkipReason": materialization_skip_reason,
"amountResolutionSource": amount_resolution_source,
"signature": transaction.signature, "signature": transaction.signature,
"instructionId": instruction_id, "instructionId": instruction_id,
"instructionIndex": instruction.instruction_index, "instructionIndex": instruction.instruction_index,
@@ -272,8 +326,12 @@ impl MeteoraDammV2Decoder {
"parsed": parsed_json, "parsed": parsed_json,
"logMessages": log_messages, "logMessages": log_messages,
"poolAccount": pool_account, "poolAccount": pool_account,
"tokenAMint": token_a_mint, "tokenAMint": effective_token_a_mint.clone(),
"tokenBMint": token_b_mint, "tokenBMint": effective_token_b_mint.clone(),
"baseVault": base_vault,
"quoteVault": quote_vault,
"baseAmountRaw": base_amount_raw,
"quoteAmountRaw": quote_amount_raw,
"tradeSide": format!("{:?}", trade_side) "tradeSide": format!("{:?}", trade_side)
}); });
decoded_events.push(crate::MeteoraDammV2DecodedEvent::Swap( decoded_events.push(crate::MeteoraDammV2DecodedEvent::Swap(
@@ -284,8 +342,8 @@ impl MeteoraDammV2Decoder {
program_id: program_id.clone(), program_id: program_id.clone(),
trade_side, trade_side,
pool_account, pool_account,
token_a_mint, token_a_mint: effective_token_a_mint,
token_b_mint, token_b_mint: effective_token_b_mint,
used_swap2, used_swap2,
payload_json, payload_json,
}, },

View File

@@ -215,9 +215,27 @@ impl MeteoraDbcDecoder {
continue; continue;
} }
if instruction_kind == MeteoraDbcInstructionKind::Swap { if instruction_kind == MeteoraDbcInstructionKind::Swap {
let trade_side = infer_trade_side(&log_messages); let decoded_amounts_result =
let has_trade_amount_payload = crate::meteora_swap_amount_inference::infer_meteora_swap_amounts_from_inner_transfers(
transaction,
instructions,
instruction,
pool_account.as_deref(),
);
let decoded_amounts = match decoded_amounts_result {
Ok(decoded_amounts) => decoded_amounts,
Err(error) => return Err(error),
};
let fallback_trade_side = infer_trade_side(&log_messages);
let trade_side = match decoded_amounts.as_ref() {
Some(decoded_amounts) => decoded_amounts.trade_side,
None => fallback_trade_side,
};
let has_direct_amount_payload =
parsed_json_has_trade_amount_or_price_payload(parsed_json.as_ref()); parsed_json_has_trade_amount_or_price_payload(parsed_json.as_ref());
let has_inferred_amount_payload = decoded_amounts.is_some();
let has_trade_amount_payload =
has_direct_amount_payload || has_inferred_amount_payload;
let event_actionability = if has_trade_amount_payload { let event_actionability = if has_trade_amount_payload {
"trade_candidate" "trade_candidate"
} else { } else {
@@ -228,6 +246,41 @@ impl MeteoraDbcDecoder {
} else { } else {
serde_json::Value::String("swap_without_amount_payload".to_string()) serde_json::Value::String("swap_without_amount_payload".to_string())
}; };
let effective_token_a_mint = match decoded_amounts.as_ref() {
Some(decoded_amounts) => Some(decoded_amounts.base_token_mint.clone()),
None => token_a_mint.clone(),
};
let effective_token_b_mint = match decoded_amounts.as_ref() {
Some(decoded_amounts) => Some(decoded_amounts.quote_token_mint.clone()),
None => token_b_mint.clone(),
};
let base_vault = match decoded_amounts.as_ref() {
Some(decoded_amounts) => decoded_amounts.base_vault_address.clone(),
None => None,
};
let quote_vault = match decoded_amounts.as_ref() {
Some(decoded_amounts) => decoded_amounts.quote_vault_address.clone(),
None => None,
};
let base_amount_raw = match decoded_amounts.as_ref() {
Some(decoded_amounts) => {
serde_json::Value::String(decoded_amounts.base_amount_raw.clone())
},
None => serde_json::Value::Null,
};
let quote_amount_raw = match decoded_amounts.as_ref() {
Some(decoded_amounts) => {
serde_json::Value::String(decoded_amounts.quote_amount_raw.clone())
},
None => serde_json::Value::Null,
};
let amount_resolution_source = if has_inferred_amount_payload {
"flattened_cpi_pool_transfer_window"
} else if has_direct_amount_payload {
"parsed_payload"
} else {
"none"
};
let payload_json = serde_json::json!({ let payload_json = serde_json::json!({
"decoder": "meteora_dbc", "decoder": "meteora_dbc",
"eventKind": "swap", "eventKind": "swap",
@@ -242,6 +295,7 @@ impl MeteoraDbcDecoder {
"candleCandidate": has_trade_amount_payload, "candleCandidate": has_trade_amount_payload,
"nonTradeUseful": false, "nonTradeUseful": false,
"materializationSkipReason": materialization_skip_reason, "materializationSkipReason": materialization_skip_reason,
"amountResolutionSource": amount_resolution_source,
"signature": transaction.signature, "signature": transaction.signature,
"instructionId": instruction_id, "instructionId": instruction_id,
"instructionIndex": instruction.instruction_index, "instructionIndex": instruction.instruction_index,
@@ -249,8 +303,12 @@ impl MeteoraDbcDecoder {
"parsed": parsed_json, "parsed": parsed_json,
"logMessages": log_messages, "logMessages": log_messages,
"poolAccount": pool_account, "poolAccount": pool_account,
"tokenAMint": token_a_mint, "tokenAMint": effective_token_a_mint.clone(),
"tokenBMint": token_b_mint, "tokenBMint": effective_token_b_mint.clone(),
"baseVault": base_vault,
"quoteVault": quote_vault,
"baseAmountRaw": base_amount_raw,
"quoteAmountRaw": quote_amount_raw,
"tradeSide": format!("{:?}", trade_side) "tradeSide": format!("{:?}", trade_side)
}); });
decoded_events.push(crate::MeteoraDbcDecodedEvent::Swap( decoded_events.push(crate::MeteoraDbcDecodedEvent::Swap(
@@ -261,8 +319,8 @@ impl MeteoraDbcDecoder {
program_id: program_id.clone(), program_id: program_id.clone(),
trade_side, trade_side,
pool_account, pool_account,
token_a_mint, token_a_mint: effective_token_a_mint,
token_b_mint, token_b_mint: effective_token_b_mint,
payload_json, payload_json,
}, },
)); ));

View File

@@ -82,9 +82,9 @@ pub struct RaydiumAmmV4SwapDecoded {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum RaydiumAmmV4DecodedEvent { pub enum RaydiumAmmV4DecodedEvent {
/// `initialize2` pool creation-like event. /// `initialize2` pool creation-like event.
Initialize2Pool(RaydiumAmmV4Initialize2PoolDecoded), Initialize2Pool(std::boxed::Box<RaydiumAmmV4Initialize2PoolDecoded>),
/// Swap event decoded from a direct or inner Raydium AMM v4 instruction. /// Swap event decoded from a direct or inner Raydium AMM v4 instruction.
Swap(RaydiumAmmV4SwapDecoded), Swap(std::boxed::Box<RaydiumAmmV4SwapDecoded>),
} }
/// Raydium AmmV4 decoder. /// Raydium AmmV4 decoder.
@@ -165,8 +165,9 @@ impl RaydiumAmmV4Decoder {
log_messages.as_slice(), log_messages.as_slice(),
); );
if let Some(initialize_event) = initialize_event { if let Some(initialize_event) = initialize_event {
decoded_events decoded_events.push(crate::RaydiumAmmV4DecodedEvent::Initialize2Pool(
.push(crate::RaydiumAmmV4DecodedEvent::Initialize2Pool(initialize_event)); std::boxed::Box::new(initialize_event),
));
continue; continue;
} }
} }
@@ -181,7 +182,8 @@ impl RaydiumAmmV4Decoder {
token_balances.as_slice(), token_balances.as_slice(),
); );
match swap_result { match swap_result {
Ok(Some(swap)) => decoded_events.push(crate::RaydiumAmmV4DecodedEvent::Swap(swap)), Ok(Some(swap)) => decoded_events
.push(crate::RaydiumAmmV4DecodedEvent::Swap(std::boxed::Box::new(swap))),
Ok(None) => {}, Ok(None) => {},
Err(error) => return Err(error), Err(error) => return Err(error),
} }
@@ -429,8 +431,8 @@ fn extract_transaction_account_keys(
let mut account_keys = std::vec::Vec::new(); let mut account_keys = std::vec::Vec::new();
let values = transaction let values = transaction
.get("transaction") .get("transaction")
.and_then(|value| value.get("message")) .and_then(|value| return value.get("message"))
.and_then(|value| value.get("accountKeys")) .and_then(|value| return value.get("accountKeys"))
.and_then(serde_json::Value::as_array); .and_then(serde_json::Value::as_array);
if let Some(values) = values { if let Some(values) = values {
let mut index = 0usize; let mut index = 0usize;
@@ -467,8 +469,8 @@ fn append_loaded_addresses(
key: &str, key: &str,
) { ) {
let addresses = meta let addresses = meta
.and_then(|value| value.get("loadedAddresses")) .and_then(|value| return value.get("loadedAddresses"))
.and_then(|value| value.get(key)) .and_then(|value| return value.get(key))
.and_then(serde_json::Value::as_array); .and_then(serde_json::Value::as_array);
let addresses = match addresses { let addresses = match addresses {
Some(addresses) => addresses, Some(addresses) => addresses,
@@ -511,7 +513,9 @@ fn collect_token_balance_side(
is_pre: bool, is_pre: bool,
accumulators: &mut std::vec::Vec<TokenBalanceAccumulator>, accumulators: &mut std::vec::Vec<TokenBalanceAccumulator>,
) { ) {
let values = meta.and_then(|value| value.get(key)).and_then(serde_json::Value::as_array); let values = meta
.and_then(|value| return value.get(key))
.and_then(serde_json::Value::as_array);
let values = match values { let values = match values {
Some(values) => values, Some(values) => values,
None => return, None => return,
@@ -525,12 +529,12 @@ fn collect_token_balance_side(
let owner = value let owner = value
.get("owner") .get("owner")
.and_then(serde_json::Value::as_str) .and_then(serde_json::Value::as_str)
.map(|text| text.to_string()); .map(|text| return text.to_string());
let amount = value let amount = value
.get("uiTokenAmount") .get("uiTokenAmount")
.and_then(|amount| amount.get("amount")) .and_then(|amount| return amount.get("amount"))
.and_then(serde_json::Value::as_str) .and_then(serde_json::Value::as_str)
.map(|text| text.to_string()); .map(|text| return text.to_string());
let account_address = match account_index { let account_address = match account_index {
Some(account_index) => account_address_by_index(account_keys, account_index), Some(account_index) => account_address_by_index(account_keys, account_index),
None => None, None => None,
@@ -652,7 +656,7 @@ fn infer_authority_owned_vault_pair(
} }
if candidates if candidates
.iter() .iter()
.any(|candidate: &TokenBalanceRecord| candidate.mint == record.mint) .any(|candidate: &TokenBalanceRecord| return candidate.mint == record.mint)
{ {
continue; continue;
} }

View File

@@ -708,8 +708,8 @@ fn extract_transaction_account_keys(
let mut account_keys = std::vec::Vec::new(); let mut account_keys = std::vec::Vec::new();
let values = transaction let values = transaction
.get("transaction") .get("transaction")
.and_then(|value| value.get("message")) .and_then(|value| return value.get("message"))
.and_then(|value| value.get("accountKeys")) .and_then(|value| return value.get("accountKeys"))
.and_then(serde_json::Value::as_array); .and_then(serde_json::Value::as_array);
if let Some(values) = values { if let Some(values) = values {
let mut index = 0usize; let mut index = 0usize;
@@ -746,8 +746,8 @@ fn append_loaded_addresses(
key: &str, key: &str,
) { ) {
let addresses = meta let addresses = meta
.and_then(|value| value.get("loadedAddresses")) .and_then(|value| return value.get("loadedAddresses"))
.and_then(|value| value.get(key)) .and_then(|value| return value.get(key))
.and_then(serde_json::Value::as_array); .and_then(serde_json::Value::as_array);
let addresses = match addresses { let addresses = match addresses {
Some(addresses) => addresses, Some(addresses) => addresses,
@@ -789,7 +789,9 @@ fn collect_token_balance_side(
is_pre: bool, is_pre: bool,
accumulators: &mut std::vec::Vec<TokenBalanceAccumulator>, accumulators: &mut std::vec::Vec<TokenBalanceAccumulator>,
) { ) {
let values = meta.and_then(|value| value.get(key)).and_then(serde_json::Value::as_array); let values = meta
.and_then(|value| return value.get(key))
.and_then(serde_json::Value::as_array);
let values = match values { let values = match values {
Some(values) => values, Some(values) => values,
None => return, None => return,
@@ -802,9 +804,9 @@ fn collect_token_balance_side(
}; };
let amount = value let amount = value
.get("uiTokenAmount") .get("uiTokenAmount")
.and_then(|amount| amount.get("amount")) .and_then(|amount| return amount.get("amount"))
.and_then(serde_json::Value::as_str) .and_then(serde_json::Value::as_str)
.map(|text| text.to_string()); .map(|text| return text.to_string());
let account_address = match account_index { let account_address = match account_index {
Some(account_index) => account_address_by_index(account_keys, account_index), Some(account_index) => account_address_by_index(account_keys, account_index),
None => None, None => None,

View File

@@ -131,6 +131,14 @@ impl DexDecodeService {
if let Err(error) = append_result { if let Err(error) = append_result {
return Err(error); return Err(error);
} }
let append_result = append_persisted_events_result(
&mut persisted,
self.preserve_unmatched_meteora_instruction_audits(&transaction, &instructions)
.await,
);
if let Err(error) = append_result {
return Err(error);
}
let append_result = append_persisted_events_result( let append_result = append_persisted_events_result(
&mut persisted, &mut persisted,
self.decode_and_persist_orca_whirlpools_events(&transaction, &instructions) self.decode_and_persist_orca_whirlpools_events(&transaction, &instructions)
@@ -1069,15 +1077,15 @@ impl DexDecodeService {
instruction.accounts_json.as_str(), instruction.accounts_json.as_str(),
); );
let token_a_mint = candidate_raydium_mapped_account( let token_a_mint = candidate_raydium_mapped_account(
mapped_spec.and_then(|spec| spec.token_a_mint_index), mapped_spec.and_then(|spec| return spec.token_a_mint_index),
accounts.as_slice(), accounts.as_slice(),
); );
let token_b_mint = candidate_raydium_mapped_account( let token_b_mint = candidate_raydium_mapped_account(
mapped_spec.and_then(|spec| spec.token_b_mint_index), mapped_spec.and_then(|spec| return spec.token_b_mint_index),
accounts.as_slice(), accounts.as_slice(),
); );
let lp_mint = candidate_raydium_mapped_account( let lp_mint = candidate_raydium_mapped_account(
mapped_spec.and_then(|spec| spec.lp_mint_index), mapped_spec.and_then(|spec| return spec.lp_mint_index),
accounts.as_slice(), accounts.as_slice(),
); );
let persist_result = self let persist_result = self
@@ -1105,6 +1113,95 @@ impl DexDecodeService {
return Ok(persisted); return Ok(persisted);
} }
async fn preserve_unmatched_meteora_instruction_audits(
&self,
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
let transaction_id = match transaction.id {
Some(transaction_id) => transaction_id,
None => {
return Err(crate::Error::InvalidState(format!(
"transaction '{}' has no internal id",
transaction.signature
)));
},
};
let decoded_events_result = crate::query_dex_decoded_events_list_by_transaction_id(
self.database.as_ref(),
transaction_id,
)
.await;
let decoded_events = match decoded_events_result {
Ok(decoded_events) => decoded_events,
Err(error) => return Err(error),
};
let mut decoded_instruction_ids = std::collections::HashSet::<i64>::new();
for decoded_event in &decoded_events {
if !decoded_event.protocol_name.starts_with("meteora_") {
continue;
}
if decoded_event.event_kind.ends_with(".instruction_audit") {
continue;
}
let instruction_id = match decoded_event.instruction_id {
Some(instruction_id) => instruction_id,
None => continue,
};
decoded_instruction_ids.insert(instruction_id);
}
let mut persisted = std::vec::Vec::new();
for instruction in instructions {
let program_id = match instruction.program_id.as_ref() {
Some(program_id) => program_id,
None => continue,
};
let audit_spec = match meteora_instruction_audit_spec(program_id.as_str()) {
Some(audit_spec) => audit_spec,
None => continue,
};
let instruction_id = match instruction.id {
Some(instruction_id) => instruction_id,
None => continue,
};
if decoded_instruction_ids.contains(&instruction_id) {
continue;
}
let accounts = parse_instruction_accounts_vec(instruction.accounts_json.as_str());
let payload = build_meteora_instruction_audit_payload(
transaction,
instruction,
audit_spec.protocol_name,
audit_spec.event_kind,
program_id.as_str(),
);
let pool_account =
candidate_meteora_audit_pool_account(audit_spec, accounts.as_slice());
let persist_result = self
.materialize_named_dex_event(
transaction,
transaction_id,
instruction_id,
audit_spec.protocol_name,
program_id.clone(),
audit_spec.event_kind,
pool_account,
None,
None,
None,
None,
payload,
)
.await;
let persisted_event = match persist_result {
Ok(persisted_event) => persisted_event,
Err(error) => return Err(error),
};
persisted.push(persisted_event);
}
return Ok(persisted);
}
async fn decode_and_persist_pump_fun_events( async fn decode_and_persist_pump_fun_events(
&self, &self,
transaction: &crate::ChainTransactionDto, transaction: &crate::ChainTransactionDto,
@@ -1318,6 +1415,13 @@ struct RaydiumInstructionAuditSpec {
candidate_pool_account_index: usize, candidate_pool_account_index: usize,
} }
#[derive(Clone, Copy)]
struct MeteoraInstructionAuditSpec {
protocol_name: &'static str,
event_kind: &'static str,
candidate_pool_account_index: std::option::Option<usize>,
}
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct RaydiumMappedNonTradeInstructionSpec { struct RaydiumMappedNonTradeInstructionSpec {
instruction_name: &'static str, instruction_name: &'static str,
@@ -1624,6 +1728,94 @@ fn read_u128_le_from_bytes(data: &[u8], offset: usize) -> std::option::Option<u1
return Some(u128::from_le_bytes(bytes)); return Some(u128::from_le_bytes(bytes));
} }
fn meteora_instruction_audit_spec(
program_id: &str,
) -> std::option::Option<MeteoraInstructionAuditSpec> {
if program_id == crate::METEORA_DBC_PROGRAM_ID {
return Some(MeteoraInstructionAuditSpec {
protocol_name: "meteora_dbc",
event_kind: "meteora_dbc.instruction_audit",
candidate_pool_account_index: Some(1),
});
}
if program_id == crate::METEORA_DLMM_PROGRAM_ID {
return Some(MeteoraInstructionAuditSpec {
protocol_name: "meteora_dlmm",
event_kind: "meteora_dlmm.instruction_audit",
candidate_pool_account_index: Some(0),
});
}
if program_id == crate::METEORA_DAMM_V1_PROGRAM_ID {
return Some(MeteoraInstructionAuditSpec {
protocol_name: "meteora_damm_v1",
event_kind: "meteora_damm_v1.instruction_audit",
candidate_pool_account_index: Some(0),
});
}
if program_id == crate::METEORA_DAMM_V2_PROGRAM_ID {
return Some(MeteoraInstructionAuditSpec {
protocol_name: "meteora_damm_v2",
event_kind: "meteora_damm_v2.instruction_audit",
candidate_pool_account_index: Some(1),
});
}
return None;
}
fn candidate_meteora_audit_pool_account(
audit_spec: MeteoraInstructionAuditSpec,
accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
let index = match audit_spec.candidate_pool_account_index {
Some(index) => index,
None => return None,
};
return accounts.get(index).cloned();
}
fn build_meteora_instruction_audit_payload(
transaction: &crate::ChainTransactionDto,
instruction: &crate::ChainInstructionDto,
protocol_name: &str,
event_kind: &str,
program_id: &str,
) -> serde_json::Value {
let accounts = parse_instruction_accounts_value(instruction.accounts_json.as_str());
let account_count = match accounts.as_array() {
Some(items) => items.len(),
None => 0,
};
let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref());
let discriminator_hex = discriminator_hex_from_base58(data_base58.as_deref());
let data_prefix = data_base58
.as_ref()
.map(|value| return value.chars().take(16).collect::<std::string::String>());
return serde_json::json!({
"decoder": protocol_name,
"eventKind": event_kind,
"signature": transaction.signature,
"instructionId": instruction.id,
"instructionIndex": instruction.instruction_index,
"innerInstructionIndex": instruction.inner_instruction_index,
"innerInstruction": instruction.inner_instruction_index.is_some(),
"parentInstructionId": instruction.parent_instruction_id,
"programId": program_id,
"programFamily": "meteora",
"accounts": accounts,
"accountCount": account_count,
"data": data_base58,
"dataPrefix": data_prefix,
"discriminatorHex": discriminator_hex,
"auditReason": "meteora_instruction_not_decoded_by_specific_decoder",
"proofStatus": "unclassified_local_corpus_instruction",
"tradeCandidate": false,
"candleCandidate": false,
"nonTradeUseful": false,
"skipTradeReason": "instruction_audit_only",
"skipCandleReason": "instruction_audit_only"
});
}
fn raydium_instruction_audit_event_kind_by_protocol( fn raydium_instruction_audit_event_kind_by_protocol(
protocol_name: &str, protocol_name: &str,
) -> std::option::Option<&'static str> { ) -> std::option::Option<&'static str> {
@@ -2774,7 +2966,6 @@ mod tests {
assert_eq!(decrease.pool_account_index, Some(3)); assert_eq!(decrease.pool_account_index, Some(3));
assert_eq!(decrease.token_a_mint_index, Some(14)); assert_eq!(decrease.token_a_mint_index, Some(14));
assert_eq!(decrease.token_b_mint_index, Some(15)); assert_eq!(decrease.token_b_mint_index, Some(15));
let increase = super::raydium_mapped_non_trade_instruction_spec( let increase = super::raydium_mapped_non_trade_instruction_spec(
"raydium_clmm", "raydium_clmm",
Some("851d59df45eeb00a"), Some("851d59df45eeb00a"),
@@ -2800,7 +2991,6 @@ mod tests {
None => panic!("collect_creator_fee discriminator must be mapped"), None => panic!("collect_creator_fee discriminator must be mapped"),
}; };
assert_eq!(collect_creator_fee.event_kind, "raydium_cpmm.collect_creator_fee"); assert_eq!(collect_creator_fee.event_kind, "raydium_cpmm.collect_creator_fee");
let withdraw = super::raydium_mapped_non_trade_instruction_spec( let withdraw = super::raydium_mapped_non_trade_instruction_spec(
"raydium_cpmm", "raydium_cpmm",
Some("b712469c946da122"), Some("b712469c946da122"),
@@ -2811,7 +3001,6 @@ mod tests {
None => panic!("withdraw discriminator must be mapped"), None => panic!("withdraw discriminator must be mapped"),
}; };
assert_eq!(withdraw.event_kind, "raydium_cpmm.withdraw"); assert_eq!(withdraw.event_kind, "raydium_cpmm.withdraw");
let initialize = super::raydium_mapped_non_trade_instruction_spec( let initialize = super::raydium_mapped_non_trade_instruction_spec(
"raydium_cpmm", "raydium_cpmm",
Some("afaf6d1f0d989bed"), Some("afaf6d1f0d989bed"),

View File

@@ -375,7 +375,7 @@ impl DexDetectService {
decoded_event: &crate::DexDecodedEventDto, decoded_event: &crate::DexDecodedEventDto,
) -> Result<crate::DexPoolDetectionResult, crate::Error> { ) -> Result<crate::DexPoolDetectionResult, crate::Error> {
return self return self
.detect_materialized_pool_from_decoded_event( .detect_materialized_pool_from_decoded_event_with_payload_vaults(
transaction, transaction,
decoded_event, decoded_event,
"meteora_dbc", "meteora_dbc",
@@ -446,7 +446,7 @@ impl DexDetectService {
decoded_event: &crate::DexDecodedEventDto, decoded_event: &crate::DexDecodedEventDto,
) -> Result<crate::DexPoolDetectionResult, crate::Error> { ) -> Result<crate::DexPoolDetectionResult, crate::Error> {
return self return self
.detect_materialized_pool_from_decoded_event( .detect_materialized_pool_from_decoded_event_with_payload_vaults(
transaction, transaction,
decoded_event, decoded_event,
"meteora_damm_v1", "meteora_damm_v1",
@@ -463,7 +463,7 @@ impl DexDetectService {
decoded_event: &crate::DexDecodedEventDto, decoded_event: &crate::DexDecodedEventDto,
) -> Result<crate::DexPoolDetectionResult, crate::Error> { ) -> Result<crate::DexPoolDetectionResult, crate::Error> {
return self return self
.detect_materialized_pool_from_decoded_event( .detect_materialized_pool_from_decoded_event_with_payload_vaults(
transaction, transaction,
decoded_event, decoded_event,
"meteora_damm_v2", "meteora_damm_v2",
@@ -743,6 +743,64 @@ impl DexDetectService {
return Ok(detection_result); return Ok(detection_result);
} }
async fn detect_materialized_pool_from_decoded_event_with_payload_vaults(
&self,
transaction: &crate::ChainTransactionDto,
decoded_event: &crate::DexDecodedEventDto,
dex_code: &str,
pool_kind: crate::PoolKind,
pool_status: crate::PoolStatus,
signal_prefix: &str,
) -> Result<crate::DexPoolDetectionResult, crate::Error> {
let dex_id_result =
crate::dex_catalog::ensure_known_dex(self.database.as_ref(), dex_code).await;
let dex_id = match dex_id_result {
Ok(dex_id) => dex_id,
Err(error) => return Err(error),
};
let payload_value_result = parse_payload_json(decoded_event.payload_json.as_str());
let payload_value = match payload_value_result {
Ok(payload_value) => payload_value,
Err(error) => return Err(error),
};
let base_vault = extract_payload_string_field(&payload_value, "baseVault");
let quote_vault = extract_payload_string_field(&payload_value, "quoteVault");
let input_result =
crate::dex_pool_materialization::DexPoolMaterializationInput::from_decoded_event(
decoded_event,
dex_id,
pool_kind,
pool_status,
crate::dex_pool_materialization::DexPoolTokenOrder::ChooseBaseQuoteFromTokenAB,
base_vault,
quote_vault,
transaction.source_endpoint_name.clone(),
);
let input = match input_result {
Ok(input) => input,
Err(error) => return Err(error),
};
let detection_result =
crate::dex_pool_materialization::materialize_dex_pool(self.database.as_ref(), &input)
.await;
let detection_result = match detection_result {
Ok(detection_result) => detection_result,
Err(error) => return Err(error),
};
let signal_result = self
.record_pool_detection_signals(
transaction,
signal_prefix,
&detection_result,
payload_value,
)
.await;
if let Err(error) = signal_result {
return Err(error);
}
return Ok(detection_result);
}
async fn record_detection_signal( async fn record_detection_signal(
&self, &self,
transaction: &crate::ChainTransactionDto, transaction: &crate::ChainTransactionDto,

File diff suppressed because it is too large Load Diff

View File

@@ -61,6 +61,8 @@ mod local_pipeline_diagnostics;
mod local_pipeline_replay; mod local_pipeline_replay;
/// Local pipeline validation helpers for non-regression runs. /// Local pipeline validation helpers for non-regression runs.
mod local_pipeline_validation; mod local_pipeline_validation;
/// Meteora swap amount inference from flattened CPI token transfers.
mod meteora_swap_amount_inference;
/// Useful non-trade DEX event materialization service. /// Useful non-trade DEX event materialization service.
mod non_trade_event_materialization; mod non_trade_event_materialization;
/// On-chain DEX pair/pool discovery helpers used by Demo3. /// On-chain DEX pair/pool discovery helpers used by Demo3.
@@ -182,6 +184,20 @@ pub use constants::JUP_MINT_ID;
/// Loader V4 program identifier. ("LoaderV411111111111111111111111111111111111"). /// Loader V4 program identifier. ("LoaderV411111111111111111111111111111111111").
/// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::loader_v4::ID /// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::loader_v4::ID
pub use constants::LOADER_V4_PROGRAM_ID; pub use constants::LOADER_V4_PROGRAM_ID;
/// MetaDAO AMM v0.5.0 program id.
pub use constants::METADAO_AMM_V0_5_0_PROGRAM_ID;
/// MetaDAO Bid Wall v0.7.0 program id.
pub use constants::METADAO_BID_WALL_V0_7_0_PROGRAM_ID;
/// MetaDAO Futarchy v0.6.0 program id.
pub use constants::METADAO_FUTARCHY_V0_6_0_PROGRAM_ID;
/// MetaDAO Launchpad v0.7.0 program id.
pub use constants::METADAO_LAUNCHPAD_V0_7_0_PROGRAM_ID;
/// MetaDAO META active token mint identifier.
pub use constants::METADAO_META_MINT_ID;
/// MetaDAO METAC legacy token mint identifier.
pub use constants::METADAO_METAC_LEGACY_MINT_ID;
/// MetaDAO-linked P2P token mint candidate.
pub use constants::METADAO_P2P_MINT_ID;
/// Meteora DAMM v1 program id. ("Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB"). /// Meteora DAMM v1 program id. ("Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB").
pub use constants::METEORA_DAMM_V1_PROGRAM_ID; pub use constants::METEORA_DAMM_V1_PROGRAM_ID;
/// Meteora DAMM v2 program id. ("cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG"). /// Meteora DAMM v2 program id. ("cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG").
@@ -195,6 +211,8 @@ pub use constants::METEORA_DLMM_PROGRAM_ID;
pub use constants::NATIVE_LOADER_PROGRAM_ID; pub use constants::NATIVE_LOADER_PROGRAM_ID;
/// Orca Whirlpools program id. ("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"). /// Orca Whirlpools program id. ("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc").
pub use constants::ORCA_WHIRLPOOLS_PROGRAM_ID; pub use constants::ORCA_WHIRLPOOLS_PROGRAM_ID;
/// Printr program id candidate observed on Solscan.
pub use constants::PRINTR_PROGRAM_ID;
/// Pump.fun program id. ("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"). /// Pump.fun program id. ("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P").
pub use constants::PUMP_FUN_PROGRAM_ID; pub use constants::PUMP_FUN_PROGRAM_ID;
/// PumpSwap / PumpAMM program id. ("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"). /// PumpSwap / PumpAMM program id. ("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA").
@@ -289,6 +307,8 @@ pub use constants::ZK_ELGAMAL_PROOF_PROGRAM_ID;
/// Zk Token Proof program identifier. ("ZkTokenProof1111111111111111111111111111111"). /// Zk Token Proof program identifier. ("ZkTokenProof1111111111111111111111111111111").
/// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::zk_token_proof_program::ID /// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::zk_token_proof_program::ID
pub use constants::ZK_TOKEN_PROOF_PROGRAM_ID; pub use constants::ZK_TOKEN_PROOF_PROGRAM_ID;
/// Zora program id candidate observed on Solscan.
pub use constants::ZORA_PROGRAM_ID;
/// Application-facing analysis signal DTO. /// Application-facing analysis signal DTO.
pub use db::AnalysisSignalDto; pub use db::AnalysisSignalDto;
/// Persisted analysis signal row. /// Persisted analysis signal row.
@@ -1114,6 +1134,10 @@ pub use local_pipeline_validation::LocalPipelineValidationRunDto;
pub use local_pipeline_validation::LocalPipelineValidationService; pub use local_pipeline_validation::LocalPipelineValidationService;
/// Validates a diagnostics summary without performing database access. /// Validates a diagnostics summary without performing database access.
pub use local_pipeline_validation::validate_local_pipeline_diagnostics_summary; pub use local_pipeline_validation::validate_local_pipeline_diagnostics_summary;
/// Result of non-trade event materialization for one transaction.
pub use non_trade_event_materialization::NonTradeEventMaterializationResult;
/// Materializes useful non-trade decoded DEX events.
pub use non_trade_event_materialization::NonTradeEventMaterializationService;
/// Candidate account inferred from generic transaction evidence. /// Candidate account inferred from generic transaction evidence.
pub use onchain_dex_pair_discovery::OnchainDexCandidateAccountDto; pub use onchain_dex_pair_discovery::OnchainDexCandidateAccountDto;
/// Candidate transaction/instruction observed on-chain for one DEX program id. /// Candidate transaction/instruction observed on-chain for one DEX program id.
@@ -1124,6 +1148,8 @@ pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryRequestDto;
pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryResultDto; pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryResultDto;
/// On-chain pair/pool discovery service. /// On-chain pair/pool discovery service.
pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryService; pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryService;
/// Rejected on-chain DEX candidate summary DTO.
pub use onchain_dex_pair_discovery::OnchainDexRejectedCandidateSummaryDto;
/// Token-balance delta observed in one transaction through Solana transaction metadata. /// Token-balance delta observed in one transaction through Solana transaction metadata.
pub use onchain_dex_pair_discovery::OnchainDexTokenBalanceDeltaDto; pub use onchain_dex_pair_discovery::OnchainDexTokenBalanceDeltaDto;
/// One pair-analytic-signal recording result. /// One pair-analytic-signal recording result.
@@ -1248,8 +1274,3 @@ pub use ws_manager::WsManagedEndpointSnapshot;
pub use ws_manager::WsManager; pub use ws_manager::WsManager;
/// Snapshot of the whole manager state. /// Snapshot of the whole manager state.
pub use ws_manager::WsManagerSnapshot; pub use ws_manager::WsManagerSnapshot;
/// Result of non-trade event materialization for one transaction.
pub use non_trade_event_materialization::NonTradeEventMaterializationResult;
/// Materializes useful non-trade decoded DEX events.
pub use non_trade_event_materialization::NonTradeEventMaterializationService;

View File

@@ -344,6 +344,30 @@ impl LocalPipelineValidationConfig {
return config; return config;
} }
/// Builds the `0.7.43` Meteora effective-surface validation config.
///
/// This profile restarts from the established Meteora-family validation but
/// keeps missing variants as warnings while a fresh corpus is being built.
/// It is intentionally strict on global trade/candle invariants: successful
/// trade candidates still need materialized trades, failed transactions stay
/// non-actionable, and non-trade Meteora events must not feed trades/candles.
pub fn v0_7_43_meteora_effective_surfaces() -> Self {
let mut config = Self::v0_7_36_meteora_family_consolidation();
config.profile_code = "0.7.43_meteora_effective_surfaces".to_string();
config.expected_dex_codes = vec![
"meteora_dbc".to_string(),
"meteora_damm_v1".to_string(),
"meteora_damm_v2".to_string(),
"meteora_dlmm".to_string(),
];
config.require_all_expected_dexes = false;
config.allow_unexpected_dexes = true;
config.require_trade_events_per_dex = false;
config.require_candles_per_dex = false;
config.require_pair_trading_readiness_semantics = false;
return config;
}
/// Builds the legacy `0.7.39` launch-surface validation alias. /// Builds the legacy `0.7.39` launch-surface validation alias.
/// ///
/// The implementation now delegates to the DEX-first profile so callers that /// The implementation now delegates to the DEX-first profile so callers that
@@ -474,7 +498,9 @@ impl LocalPipelineValidationService {
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> { ) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
let diagnostics_service = let diagnostics_service =
crate::LocalPipelineDiagnosticsService::new(self.database.clone()); crate::LocalPipelineDiagnosticsService::new(self.database.clone());
let summary_result = if config.profile_code == "0.7.42_raydium_family_event_coverage" { let summary_result = if config.profile_code == "0.7.42_raydium_family_event_coverage"
|| config.profile_code == "0.7.43_meteora_effective_surfaces"
{
diagnostics_service.diagnose_for_validation().await diagnostics_service.diagnose_for_validation().await
} else { } else {
diagnostics_service.diagnose().await diagnostics_service.diagnose().await
@@ -617,6 +643,14 @@ impl LocalPipelineValidationService {
let config = crate::LocalPipelineValidationConfig::v0_7_42_raydium_family_event_coverage(); let config = crate::LocalPipelineValidationConfig::v0_7_42_raydium_family_event_coverage();
return self.validate_current_database(&config).await; return self.validate_current_database(&config).await;
} }
/// Diagnoses the current database with the `0.7.43` Meteora effective-surface profile.
pub async fn validate_v0_7_43_current_database(
&self,
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
let config = crate::LocalPipelineValidationConfig::v0_7_43_meteora_effective_surfaces();
return self.validate_current_database(&config).await;
}
} }
/// Validates a diagnostics summary without performing database access. /// Validates a diagnostics summary without performing database access.
@@ -743,7 +777,8 @@ pub fn validate_local_pipeline_diagnostics_summary(
|| config.profile_code == "0.7.39_dex_first_effective_swap_surfaces" || config.profile_code == "0.7.39_dex_first_effective_swap_surfaces"
|| config.profile_code == "0.7.39_launch_surface_origin_baseline" || config.profile_code == "0.7.39_launch_surface_origin_baseline"
|| config.profile_code == "0.7.40_raydium_effective_surfaces" || config.profile_code == "0.7.40_raydium_effective_surfaces"
|| config.profile_code == "0.7.41_raydium_amm_v4_swap_decoder"; || config.profile_code == "0.7.41_raydium_amm_v4_swap_decoder"
|| config.profile_code == "0.7.43_meteora_effective_surfaces";
if config.require_all_expected_dexes || missing_expected_dex_is_warning { if config.require_all_expected_dexes || missing_expected_dex_is_warning {
for expected_dex_code in &expected_dex_codes { for expected_dex_code in &expected_dex_codes {
if !observed_dex_codes.contains(expected_dex_code) { if !observed_dex_codes.contains(expected_dex_code) {
@@ -1022,8 +1057,7 @@ fn validate_dex_support_matrix_semantics(
}); });
} }
if entry.surface_role == "to_verify" if entry.surface_role == "to_verify"
&& (entry.program_id.is_some() && (entry.program_id_status != "to_verify"
|| entry.program_id_status != "to_verify"
|| entry.catalog_enabled || entry.catalog_enabled
|| entry.decoded || entry.decoded
|| entry.materialized || entry.materialized
@@ -1033,7 +1067,7 @@ fn validate_dex_support_matrix_semantics(
issues.push(crate::LocalPipelineValidationIssueDto { issues.push(crate::LocalPipelineValidationIssueDto {
code: "to_verify_matrix_entry_promoted_without_proof".to_string(), code: "to_verify_matrix_entry_promoted_without_proof".to_string(),
message: format!( message: format!(
"to-verify surface '{}' must not expose program id, catalog activation, decoding or trade/candle materialization without corpus proof", "to-verify surface '{}' must not expose catalog activation, decoding or trade/candle materialization without corpus proof",
entry_code entry_code
), ),
subject: Some(entry_code.clone()), subject: Some(entry_code.clone()),
@@ -1599,6 +1633,31 @@ mod tests {
assert!(report.expected_dex_codes.contains(&"raydium_amm_v4".to_string())); assert!(report.expected_dex_codes.contains(&"raydium_amm_v4".to_string()));
} }
#[test]
fn validation_accepts_0_7_43_meteora_effective_surfaces_partial_corpus() {
let mut summary = make_0_7_28_summary_with_meteora();
summary.dex_summaries.retain(|dex_summary| {
return dex_summary.dex_code == "meteora_dlmm"
|| dex_summary.dex_code == "meteora_damm_v1";
});
summary.decoded_non_trade_useful_event_count = 7;
summary.liquidity_event_count = 4;
summary.pool_lifecycle_event_count = 1;
summary.fee_event_count = 2;
let config = crate::LocalPipelineValidationConfig::v0_7_43_meteora_effective_surfaces();
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
assert!(report.validation_passed);
assert_eq!(report.validation_profile_code, "0.7.43_meteora_effective_surfaces");
assert_eq!(report.blocking_issue_count, 0);
assert!(report.warning_count >= 2);
assert!(report.expected_dex_codes.contains(&"meteora_dbc".to_string()));
assert!(report.expected_dex_codes.contains(&"meteora_damm_v1".to_string()));
assert!(report.expected_dex_codes.contains(&"meteora_damm_v2".to_string()));
assert!(report.expected_dex_codes.contains(&"meteora_dlmm".to_string()));
assert_eq!(report.liquidity_event_count, 4);
assert_eq!(report.fee_event_count, 2);
}
#[test] #[test]
fn validation_rejects_0_7_33_pair_trading_readiness_mismatch() { fn validation_rejects_0_7_33_pair_trading_readiness_mismatch() {
let mut summary = make_0_7_28_summary_with_meteora(); let mut summary = make_0_7_28_summary_with_meteora();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff