This commit is contained in:
2026-06-19 10:29:17 +02:00
parent 319be14aa6
commit 58f9b36969
24 changed files with 6432 additions and 741 deletions

View File

@@ -89,3 +89,4 @@
0.7.54 - Clôture Pump.fun : decoder maximal local depuis IDL Solscan/upstream, décodage des 40 instructions et 23 events Anchor connus, matérialisation validée des trades `buy/sell/buy_exact_sol_in` et `trade_event` v2/exact sans double-count, non-trades launch/fee/reward/admin selon contexte, validation SQL Pump.fun propre et ouverture de `0.7.55 pump_fees`.
0.7.55 - Clôture Pump Fees : decoder local maximal `pump_fees` depuis l'IDL locale, `29` instructions et `20` events Anchor couverts, tests synthétiques des Anchor IDL non observés, matérialisation prudente fee/reward/admin/lifecycle, `get_fees` decoded-only, transactions failed audit-only, aucun trade/candle direct et SQL de validation Pump Fees propre.
0.7.56 - Clôture Meteora DBC : decoder local maximal depuis l'IDL `dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN`, couverture des 28 instructions et 23 events Anchor, matérialisation validée des swaps `swap/swap2`, lifecycle, admin/config, lockers/migrations et fees/surplus/leftover, ajout du modèle `k_sol_fee_event_amounts` pour les legs de montants fee, helper générique parent fee -> legs scalaires, recovery fee `allowlisted_inner_spl_transfer` strictement allowlistée pour anciens DEX, validation croisée Pump/Raydium, 446 tests passés, clippy OK et SQL de fermeture DBC propre.
0.7.57 - Clôture Meteora DLMM : decoder local maximal `meteora_dlmm` sur `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo`, couverture des `76` instructions IDL, `30` events Anchor et `12` accounts, correction du discriminant non-IDL local `75c73e67068e1fcb` en `initialize_preset_parameter_v2`, matérialisation validée des swaps, exact-out, liquidity, bins, positions, lifecycle, fees, rewards, admin/config et limit orders, `swap_event/swap2_evt` routés en lifecycle `swap_log` sans double-count trade/candle, recovery fee/reward strictement allowlistée depuis inner SPL transfers, cleanup coverage duplicates, validation finale `460` tests passés, clippy OK et SQL de fermeture DLMM propre.

View File

@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
version = "0.7.56"
version = "0.7.57"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
@@ -31,24 +31,23 @@ reqwest = { version = "^0.13", default-features = false, features = ["charset",
rustls = { version = "^0.23", features = ["aws-lc-rs"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1.0", features = [] }
solana-account-decoder-client-types = { version = "^4.0", features = ["zstd"] }
solana-account-decoder-client-types = { version = ">=4.1.0-rc.1", features = ["zstd"] }
solana-address-lookup-table-interface = { version = "^3.1", features = ["serde"] }
solana-compute-budget-interface = { version = "^3.0", features = ["borsh", "serde"] }
solana-rpc-client-api = { version = ">=4.0.0-rc.0", features = [] }
#solana-rpc-client-types = { version = "^4.0", features = [] }
solana-rpc-client-api = { version = ">=4.1.0-rc.1", features = [] }
solana-sdk = { version = "^4.0", features = ["full"] }
solana-sdk-ids = { version = "^3.1", features = [] }
solana-system-interface = { version = "^3.2", features = ["alloc", "serde", "std"] }
solana-transaction-status-client-types = { version = "^4.0", features = [] }
solana-transaction-status-client-types = { version = ">=4.1.0-rc.1", features = [] }
spl-associated-token-account-interface = { version = "^2.0", features = ["borsh"] }
spl-memo-interface = { version = "^2.1", features = [] }
spl-token-interface = { version = "^3.0", features = [] }
spl-token-2022-interface = { version = "^3.0", features = [] }
spl-token-2022-interface = { version = "^3.1", features = [] }
sqlx = { version = "^0.9", features = ["bigdecimal", "chrono", "default", "derive", "json", "runtime-tokio", "sqlite", "tls-rustls", "uuid"] }
tauri = { version = "^2.11", features = ["default", "tray-icon"] }
tauri-build = { version = "^2.6", features = [] }
tauri-plugin-tracing = { version = "^0.3", default-features = false, features = [] }
tempfile = { version = "^3", features = [] }
tempfile = { version = "^3.27", features = [] }
tokio = { version = "^1.52", features = ["full"] }
tokio-stream = { version = "^0.1", features = ["full"] }
tokio-tungstenite = { version = "^0.29", default-features = false, features = ["connect", "handshake", "rustls-tls-webpki-roots", "stream", "url"] }
@@ -56,10 +55,10 @@ tracing = { version = "^0.1", features = [] }
tracing-appender = { version = "^0.2", features = [] }
tracing-subscriber = { version = "^0.3", features = ["ansi", "env-filter", "chrono", "serde", "json"] }
ts-rs = { version = "^12.0", features = ["bigdecimal", "bson", "bytes", "chrono", "indexmap", "ordered-float", "serde_json", "tokio", "url", "uuid"] }
yellowstone-grpc-client = { version = "^13.0", features = [] }
yellowstone-grpc-proto = { version = "^12.2", features = [] }
yellowstone-grpc-client = { version = "^13.1", features = [] }
yellowstone-grpc-proto = { version = "^12.4", features = [] }
uuid = { version = "^1.23", features = ["v4", "serde"] }
zeroize = { version = "^1.8", features = ["derive", "serde", "std"] }
zeroize = { version = "^1.9", features = ["derive", "serde", "std"] }
[profile.dev]
incremental = true # Compile your binary in smaller steps.

View File

@@ -3,6 +3,101 @@
# khadhroony-bobobot
## État final validé `0.7.57` — `meteora_dlmm` full decode / full materialization
La tranche `0.7.57 meteora_dlmm` est clôturée côté decoder local maximal, coverage, tests synthétiques, matérialisation prudente et validation SQL sur base dédiée. Le programme traité est :
```text
LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo
```
Source IDL locale prioritaire :
```text
idls/meteora_dlmm.LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo.json
```
Surface couverte : `76` instructions IDL, `30` events Anchor et `12` accounts, avec correction locale du discriminant `75c73e67068e1fcb`, identifié comme `initialize_preset_parameter_v2` par les logs Anchor (`Instruction: InitializePresetParameterV2`) et par hash Anchor. Les entrées observées utiles ne restent pas en decoded-only : elles alimentent `trade`, `liquidity`, `lifecycle`, `fee`, `reward`, `admin` ou `orderbook` selon leur famille.
Validation locale finale rapportée :
```text
cargo test -p kb_lib -> 460 passed / 0 failed
cargo clippy -p kb_lib --all-targets -- -D warnings -> OK
769 replayed
0 decode skipped
769 ledger upserts
646 unsafe ledger rows
106 trades
664 liquidity
1107 lifecycle
0 tokenAccount
424 candle upserts
instructionObservations = 8062
resetDeleted = 9898
catalog = 169 tokens / 218 pools / 218 pairs
```
Règles métier verrouillées :
- les trades/candles DLMM proviennent uniquement des instructions swap fiables : `swap`, `swap2`, `swap_exact_out`, `swap_exact_out2`, `swap_with_price_impact`, `swap_with_price_impact2` ;
- `swap_event` et `swap2_evt` sont matérialisés comme `swap_log` lifecycle afin d'éviter tout double comptage trade/candle ;
- les positions, bins, pool create, preset parameters, pair status, token badge et operator/config alimentent lifecycle/admin selon contexte ;
- les limit orders alimentent `k_sol_orderbook_events` et ne produisent jamais trade/candle ;
- les fees DLMM utilisent `k_sol_fee_events` + `k_sol_fee_event_amounts`, avec recovery strictement allowlistée depuis inner SPL transfers ;
- les rewards DLMM utilisent une recovery strictement allowlistée, sans inventer de montant pour les initialisations/configurations ;
- les transactions failed restent décodées/auditables mais ne matérialisent aucune table métier.
Synthèse fee/reward finale observée :
```text
claim_fee 64 parents / 64 legs
claim_fee2 63 parents / 88 legs
claim_fee_event 127 parents / 187 legs
claim_fee2_event 78 parents / 118 legs
composition_fee_event 51 parents / 63 legs
withdraw_protocol_fee 14 parents / 20 legs
zap_protocol_fee 13 parents / 13 legs
claim_reward 15 parents / 1 scalar amount
claim_reward2 19 parents / 10 scalar amounts
claim_reward_event 34 parents / 25 scalar amounts
claim_reward2_event 19 parents / 10 scalar amounts
fund_reward 10 parents / 10 scalar amounts
fund_reward_event 10 parents / 10 scalar amounts
initialize_reward 9 parents / 0 amount, attendu car init/config
initialize_reward_event 9 parents / 0 amount, attendu car init/config
```
Checks de fermeture `0.7.57` :
- fallback `upstream_git` `meteora_dlmm` pour entrées couvertes localement : vide ;
- `instruction_audit` observé : `0` ;
- decoded `meteora_dlmm` sans coverage : vide ;
- successful non-materialized sans `skip*Reason` ou policy explicite : vide ;
- failed transaction avec business materialization : vide ;
- multi-target materialization : vide ;
- non-swap vers trade/candle : vide ;
- parent fee scalaire sans leg : vide ;
- fee amount legs orphelins : vide ;
- limit/orderbook vers trade/candle : vide ;
- duplicate logical coverage rows : vide.
Écart coverage restant et expliqué : `close_bin_array` affiche `16 observed / 14 materialized`, car `14` transactions sont OK et matérialisées, tandis que `2` transactions failed (`Custom 6015`) restent audit-only.
Documents de référence :
```text
docs/reports/METEORA_DLMM_EVENT_COVERAGE_REPORT.md
docs/VALIDATION_STATUS_0_7_57_FINAL.md
validation_sql/SQL_VALIDATION_METEORA_DLMM_0_7_57.sql
docs/prompts/PROMPT_0_7_58_DEMO4_PROGRAM_SURFACE_DISCOVERY.md
docs/prompts/PROMPT_0_7_59_SQLITE_DB_TRANSACTION_MERGER_BINARY.md
docs/prompts/PROMPT_0_7_60_METEORA_DAMM_NEXT_DEX.md
```
Prochaine étape recommandée : `0.7.58 demo4 / program surface discovery`, une fonctionnalité d'affichage et de scoring des contenus non encore pris en compte (`program_id`, discriminators, Anchor logs/events, upstream fallback), sans matérialisation automatique et sans promotion automatique dans les decoders.
## État final validé `0.7.56` — `meteora_dbc` + socle `fee_event_amounts`
@@ -88,7 +183,7 @@ validation_sql/SQL_VALIDATION_METEORA_DBC_0_7_56.sql
docs/prompts/PROMPT_0_7_57_METEORA_DLMM_FULL_DECODE_MATERIALIZATION.md
```
Prochaine tranche recommandée : `0.7.57 meteora_dlmm` en mode full decode / full materialization, sur le programme `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo`, avec vérification complète de l'IDL locale `idls/meteora_dlmm.LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo.json`.
La tranche suivante `0.7.57 meteora_dlmm` est désormais clôturée. La prochaine étape recommandée est `0.7.58 demo4 / program surface discovery`, puis `0.7.59` pour le binaire de consolidation de bases SQLite.
## État final validé `0.7.55` — `pump_fees`

View File

@@ -2,38 +2,45 @@
# Roadmap — khadhroony-bobobot
## État courant — clôture `0.7.56 meteora_dbc` et ouverture `0.7.57 meteora_dlmm full decode/materialization`
## État courant — clôture `0.7.57 meteora_dlmm` et plan intermédiaire `demo4` / consolidation DB
### `0.7.56 meteora_dbc` — clos
### `0.7.57 meteora_dlmm` — clos
- Program id : `dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN`.
- Source prioritaire : `idls/meteora_dbc.dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN.json`.
- Surface IDL : `28` instructions, `23` events Anchor, `9` accounts, `59` types.
- Build final : `cargo test -p kb_lib` -> `446 passed`; clippy `-D warnings` OK.
- Replay final DBC : `480 replayed`, `264 trades`, `1 liquidity`, `122 lifecycle`, `1056 candle upserts`, `instructionObservations=7167`, catalogue `86/60/60`.
- Fees DBC : `89` parents `k_sol_fee_events`, `96` legs `k_sol_fee_event_amounts`, aucun parent fee scalaire sans leg, aucun leg orphelin.
- Socle transversal ajouté : table `k_sol_fee_event_amounts`, helper générique parent fee + legs, backfill/normalisation safe, recovery `allowlisted_inner_spl_transfer` non globale.
- Program id : `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo`.
- Source prioritaire : `idls/meteora_dlmm.LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo.json`.
- Surface IDL : `76` instructions, `30` events Anchor, `12` accounts.
- Build final : `cargo test -p kb_lib` -> `460 passed`; clippy `-D warnings` OK.
- Replay final DLMM : `769 replayed`, `106 trades`, `664 liquidity`, `1107 lifecycle`, `424 candle upserts`, `instructionObservations=8062`, catalogue `169/218/218`.
- Dernier discriminant résiduel `75c73e67068e1fcb` : identifié comme `initialize_preset_parameter_v2`, matérialisé `4/4` en admin/config.
- `instruction_audit` observé : `0`; fallback `upstream_git` DLMM couvert localement : vide.
- `close_bin_array` : `16/14` coverage expliqué par `14` transactions OK matérialisées et `2` failed tx `Custom 6015` audit-only.
- Checks bloquants : decoded sans coverage, successful non-materialized, failed matérialisé, multi-target, non-swap trade/candle, parent fee sans leg, legs orphelins et coverage duplicates sont propres.
### Politique fee obligatoire à partir de `0.7.56`
### Politique de découverte automatique future
- Un parent `k_sol_fee_events` avec `fee_token_mint + fee_amount_raw` doit créer automatiquement un leg `k_sol_fee_event_amounts` d'index `0`.
- Un fee multi-mint/multi-leg ne doit pas agréger artificiellement le parent ; le parent reste sans montant scalaire, les legs portent les montants fiables.
- Les montants issus d'arguments `max`, de bornes ou de `u64::MAX` ne sont pas des fees exécutés.
- La recovery par CPI SPL inner transfer n'est pas globale : chaque futur décodeur doit déclarer explicitement sa policy et ses event kinds autorisés.
- Les `amount_source` connus à préserver : `parent_fee_event_amount`, `fee_event_amounts`, `inner_spl_transfer`, `lamport_balance_delta`, `allowlisted_inner_spl_transfer`.
- Les cas sans transfert exploitable doivent écrire une raison explicite : `fee_instruction_has_no_actual_transfer` ou `fee_instruction_has_only_zero_amount_transfers`.
La suite ne doit pas matérialiser automatiquement des contenus inconnus. La future `demo4` doit seulement afficher, scorer et regrouper les surfaces non encore prises en compte pour permettre une programmation manuelle ou semi-automatique contrôlée.
### Prochaine tranche immédiate
À observer sans promotion automatique :
- `program_id` inconnus ou non routés ;
- discriminators inconnus sur program id connu ;
- logs Anchor `Instruction: ...` non mappés ;
- `Program data` / Anchor events non mappés ;
- fallback `upstream_git.instruction_match` ;
- layouts accounts/data/inner instructions ;
- signatures samples et statut success/failed.
### Prochaines tranches recommandées
| Priorité | Tranche | Surface | Objectif |
|---:|---|---|---|
| 1 | `0.7.57` | `meteora_dlmm` | Full decode + full materialization : `76` instructions IDL, `30` events Anchor, swaps, exact-out, liquidity/bin/position, fees, rewards, admin/config, limit-order events et side effects sans double-count. |
| 2 | `0.7.58` | `meteora_damm_v1` | Parité upstream finale : pools, swaps, liquidity, lock, fees/admin. |
| 3 | `0.7.59` | `meteora_damm_v2` | Couverture complète : create/custom pools, swaps, liquidity, dynamic config, fees/admin. |
| 4 | `0.7.60` | `meteora_vault` | Vault deposit/withdraw/fee/accounting ; pas de candle directe. |
| 5 | `0.7.61+` | programmes transversaux | System/SPL/ATA/Compute/Memo/ALT : side effects et contexte, pas de trade direct. |
| 1 | `0.7.58` | `demo4_program_surface_discovery` | UI/queries de découverte des contenus non pris en compte : aucun auto-decode métier, aucune auto-materialization, aucune auto-promotion. |
| 2 | `0.7.59` | `sqlite_db_transaction_merger` | Nouveau module binaire utilisant `kb_lib` pour fusionner plusieurs bases SQLite de corpus existants dans une base output, sans rescanner/backfill les signatures déjà disponibles. |
| 3 | `0.7.60` | `meteora_damm_v1` | Clôture séparée DAMM v1 : pools, swaps, liquidity, lock, fees/admin, coverage et matérialisation complète. |
| 4 | `0.7.61` | `meteora_damm_v2` | Clôture séparée DAMM v2 : create/custom pools, swaps, liquidity, dynamic config, fees/admin. |
| 5 | `0.7.62` | `meteora_vault` | Vault deposit/withdraw/fee/accounting ; pas de candle directe. |
La tranche `0.7.57 meteora_dlmm` doit partir d'une base neuve, réutiliser `fee_event_amounts`, ajouter une validation dédiée `validation_sql/SQL_VALIDATION_METEORA_DLMM_0_7_57.sql`, et clôturer uniquement si toutes les entrées IDL locales sont couvertes, décodées ou explicitement non observées avec tests synthétiques.
Décision de planification : ne pas fusionner DAMM v1 et DAMM v2 dans une seule tranche. Ils peuvent partager des helpers, des tests et des conventions de matérialisation, mais les `program_id`, IDL, discriminants et corpus doivent rester séparés afin de garder les validations SQL lisibles et de réduire les risques de faux positif.
## 0.7.47-1FE5 — Décision de planification : ne plus viser “tous les events en une session”
@@ -62,11 +69,11 @@ Règles de planification :
| `https://github.com/all-in-one-blockchain/phoenix-onchain-mm` | Source Phoenix/MM complémentaire. |
| `https://docs.vybenetwork.com/docs/available-dexs-amms` | Source externe de découverte DEX/AMM, non vérifiante. |
### Plan révisé `0.7.53+` — une version par `program_id`
### Plan actif post-`0.7.57` — phasage par version
Règle de planification validée après `0.7.52` : **une version cible = un `program_id`**.
Règle active après la clôture DLMM : les tranches `0.7.58` et `0.7.59` sont des tranches doutillage, puis la règle **une version cible = un `program_id`** reprend à partir de `0.7.60`.
Exceptions : les comptes non-programmes (`platform_config`, token authority, comptes de configuration, comptes de pool, accounts de programme) ne créent pas de version decoder autonome. Ils restent des sources de contexte ou denrichissement. `SOLSCAN_ACCOUNT_SOURCES` reste un inventaire de découverte, pas une preuve de support local.
Les comptes non-programmes (`platform_config`, token authority, comptes de configuration, comptes de pool, accounts de programme) ne créent pas de version decoder autonome. `SOLSCAN_ACCOUNT_SOURCES` reste un inventaire de découverte, pas une preuve de support local.
| Version cible | Decoder / surface | Program id | Famille | Objectif de clôture |
|---|---|---|---|---|
@@ -74,87 +81,89 @@ Exceptions : les comptes non-programmes (`platform_config`, token authority, com
| `0.7.54` | `pump_fun` | `6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P` | Pump / launch-bonding | **Clos** : decoder maximal IDL/local, trades directs `buy/sell/buy_exact_sol_in`, v2/exact via `trade_event`, non-trades matérialisés selon contexte, validations Pump.fun propres. |
| `0.7.55` | `pump_fees` | `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` | Pump / fee | **Clos** : `29` instructions, `20` events Anchor, fee/reward/admin/lifecycle, `get_fees` decoded-only, failed tx audit-only, aucun trade/candle direct. |
| `0.7.56` | `meteora_dbc` | `dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN` | Meteora / DBC | Clos : `28` instructions et `23` events Anchor couverts, swaps `swap/swap2`, lifecycle/admin/fees, `k_sol_fee_event_amounts`, validations SQL propres. |
| `0.7.57` | `meteora_dlmm` | `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo` | Meteora / DLMM | Full decode + full materialization : `76` instructions IDL, `30` events Anchor, swaps, bins, positions, liquidity, fees/rewards/admin/limit-order, sans double-count. |
| `0.7.58` | `meteora_damm_v1` | `Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB` | Meteora / DAMM v1 | Parité upstream finale : pools, swaps, liquidity, lock, fees/admin. |
| `0.7.59` | `meteora_damm_v2` | `cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG` | Meteora / DAMM v2 | Couverture complète : create/custom pools, swaps, liquidity, dynamic config, fees/admin. |
| `0.7.60` | `meteora_vault` | `24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi` | Meteora / vault | Vault deposit/withdraw/fee/accounting ; pas de candle directe. |
| `0.7.61` | `system_program` | `11111111111111111111111111111111` | Système Solana | Create/assign/transfer account ; side effects de contexte, pas de trade. |
| `0.7.62` | `spl_token` | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` | SPL Token | Transfer, mint, burn, close account, sync native ; base transversale pour deltas. |
| `0.7.63` | `spl_token_2022` | `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb` | Token-2022 | Transfers/extensions Token-2022, mint/burn/close, comptes et side effects. |
| `0.7.64` | `associated_token_account` | `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL` | Système token | Création ATA, rattachement wallet/token/pool. |
| `0.7.65` | `compute_budget` | `ComputeBudget111111111111111111111111111111` | Contexte tx | Budget/prioritization fee ; utile scoring/MEV, pas de trade. |
| `0.7.66` | `memo` | `MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr` | Contexte tx | Mémo transactionnel et attribution éventuelle. |
| `0.7.67` | `address_lookup_table` | `AddressLookupTab1e1111111111111111111111111` | Contexte tx | Résolution/diagnostic ALT si nécessaire. |
| `0.7.68` | `mpl_token_metadata` | `metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s` | Metadata | Enrichissement token/NFT/mint metadata. |
| `0.7.69` | `mpl_core` | `CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d` | Metadata / asset | Contexte asset si présent dans corpus. |
| `0.7.70` | `bubblegum` | `BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY` | Compressed assets | Audit/contexte assets compressés, pas DEX. |
| `0.7.71` | `raydium_routing` | `routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS` | Router | Route/legs Raydium ; éviter le double-count avec DEX effectifs. |
| `0.7.72` | `jupiter_swap_v6` | `JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4` | Aggregator | Route attribution, legs, no duplicate trade/candle. |
| `0.7.73` | `jupiter_swap_v4` | `JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB` | Legacy aggregator | Audit/route only si corpus encore utile. |
| `0.7.74` | `dflow_aggregator_v4` | `DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH` | Aggregator | Route/intent/orderflow ; pas de double matérialisation. |
| `0.7.75` | `okx_dex` | `6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma` | Aggregator/router | Route attribution ; trades seulement si source exacte non doublonnée. |
| `0.7.76` | `onchain_labs_dex_v2` | `proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u` | Router/DEX candidat | Corpus dabord ; classifier route vs DEX effectif. |
| `0.7.77` | `titan_router` | `T1TANpTeScyeqVzzgNViGDNrkQ6qHz9KrSBS4aNXvGT` | Router | Audit route-only sauf preuve de trade direct non doublonné. |
| `0.7.78` | `sanctum_router` | `stkitrT1Uoy18Dk1fTrgPw8W6MVzoCfYoAFT4MLsmhq` | Router | Route/liquid staking context ; pas de candle DEX directe. |
| `0.7.79` | `orca_whirlpools` | `whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc` | DEX avec IDL/source | Swaps, pools, positions, liquidity, fees/rewards. |
| `0.7.80` | `stabble_stable_swap` | `swapNyd8XiQwJ6ianp9snpu4brUqFxadzvHebnAXjJZ` | DEX avec IDL/source | Stable swap ; deltas exacts, liquidity/admin. |
| `0.7.81` | `stabble_weighted_swap` | `swapFpHZwjELNnjvThjajtiVmkz3yPQEHjLtka2fwHW` | DEX avec IDL/source | Weighted swap, deltas exacts, liquidity/admin. |
| `0.7.82` | `stabble_clmm` | `6dMXqGZ3ga2dikrYS9ovDXgHGh5RUsb2RTUj6hrQXhk6` | DEX avec IDL/source | CLMM Stabble si corpus utile. |
| `0.7.83` | `bonkswap` | `BSwp6bEBihVLdqJRKGgzjcGLHkcTuzmSo1TQkHepzH8p` | DEX avec IDL/source | Swap/liquidity/non-trade. |
| `0.7.84` | `boop_fun` | `boop8hVGQGqehUK2iVEMEnMrL5RbjywRzHKBmBE7ry4` | Launch/DEX candidat | Launch/swap/migration selon corpus. |
| `0.7.85` | `byreal_clmm` | `REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2` | DEX avec IDL/source | CLMM ; corpus puis matérialisation contrôlée. |
| `0.7.86` | `fusionamm` | `fUSioN9YKKSa3CUC2YUc4tPkHJ5Y6XW1yz8y6F7qWz9` | DEX avec IDL/source | AMM ; swaps/liquidity si corpus. |
| `0.7.87` | `goosefx_v1` | `GAMMA7meSFWaBXF25oSUgmGRwaW6sCMFLmBNiMSdbHVT` | DEX avec IDL/source | DEX/AMM selon corpus. |
| `0.7.88` | `goosefx_v2` | `GFXsSL5sSaDfNFQUYsHekbWBW1TsFdjDYzACh62tEHxn` | DEX avec IDL/source | DEX/AMM selon corpus. |
| `0.7.89` | `guac_swap` | `Gswppe6ERWKpUTXvRPfXdzHhiCyJvLadVvXGfdpBqcE1` | DEX avec IDL/source | Swap/liquidity si corpus. |
| `0.7.90` | `hylo_exchange` | `HYEXCHtHkBagdStcJCp3xbbb9B7sdMdWXFNj6mdsG4hn` | DEX/source à classifier | Classer DEX/lending/stable selon IDL/corpus. |
| `0.7.91` | `printr` | `T8HsGYv7sMk3kTnyaRqZrbRPuntYzdh12evXBkprint` | Launch/DEX candidat | Corpus dabord ; surface launch/swap à confirmer. |
| `0.7.92` | `moonit` | `MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG` | Launch/DEX candidat | Launch/migration/swap si prouvé. |
| `0.7.93` | `metadao_amm_v0_5` | `AMMJdEiCCa8mdugg6JPF7gFirmmxisTfDJoSNSUi5zDJ` | DEX avec source | AMM futarchy ; corpus et price semantics. |
| `0.7.94` | `metadao_bid_wall` | `WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx` | Order/bid-wall | Order/bid-wall context ; pas de candle directe sans fill exact. |
| `0.7.95` | `metadao_launchpad` | `moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM` | Launch surface | Launch/ICO surface ; pas DEX effectif par défaut. |
| `0.7.96` | `vertigo` | `vrTGoBuy5rYSxAfV3jaRJWHH6nN9WK4NRExGxsk1bCJ` | DEX/source | Swap/launch selon corpus. |
| `0.7.97` | `virtuals` | `5U3EU2ubXtK84QcRjWVmYt9RaDyA8gKxdUrPFXmZyaki` | Launch/DEX candidat | Launch/AMM à confirmer. |
| `0.7.98` | `wavebreak` | `waveQX2yP3H1pVU8djGvEHmYg8uamQ84AuyGtpsrXTF` | DEX/source | Corpus et rôle exact. |
| `0.7.99` | `woofi` | `WooFif76YGRNjk1pA8wCsN67aQsD9f9iLsz4NcJ1AVb` | DEX/router | Swap/route selon corpus. |
| `0.7.100` | `pancake_swap` | `HpNfyc2Saw7RKkQd8nEL4khUcuPhQ7WwY1B2qjx8jxFq` | DEX/source | DEX Solana à confirmer par corpus. |
| `0.7.101` | `gavel` | `srAMMzfVHVAtgSJc8iH6CfKzuWuUTzLHVCE81QU1rgi` | Source upstream | Corpus dabord ; rôle exact à classer. |
| `0.7.102` | `heaven` | `HEAVENoP2qxoeuF8Dj2oT1GHEnu49U5mJYkdeC8BAX2o` | Launch/DEX candidat | Launch/DEX selon corpus. |
| `0.7.103` | `lifinity_v2` | `2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c` | DEX legacy/actif | Support selon corpus. |
| `0.7.104` | `moonshot` | `MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG` | Source upstream/Solscan | À dédupliquer avec `moonit` si même program id. |
| `0.7.105` | `openbook_v2` | `opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb` | Orderbook | Audit/orderbook complet ; trade/candle seulement si fills exacts. |
| `0.7.106` | `phoenix_v1` | `PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY` | Orderbook | Audit-only complet avant toute matérialisation fill. |
| `0.7.107` | `alphaq` | `ALPHAQmeA7bjrVuccPsYPiCvsi428SNwte66Srvs4pHA` | Solscan/no IDL | Probe par Demo3 + corpus ; décider support ou abandon. |
| `0.7.108` | `aquifer` | `AQU1FRd7papthgdrwPTTq5JacJh8YtwEXaBfKU3bTz45` | Solscan/no IDL | Probe uniquement. |
| `0.7.109` | `bisonfi` | `BiSoNHVpsVZW2F7rx2eQ59yQwKxzU5NvBcmKshCSUypi` | Solscan/no IDL | Probe uniquement. |
| `0.7.110` | `dexlab` | `DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N` | Solscan/no IDL | Vérifier support partiel / corpus. |
| `0.7.111` | `fluxbeam` | `FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X` | Solscan/no IDL | Vérifier support partiel / corpus. |
| `0.7.112` | `goonfi` | `goonERTdGsjnkZqWuVjs73BZ3Pb9qoCUdBUL17BnS5j` | Solscan/no IDL | Probe. |
| `0.7.113` | `goonfi_v2` | `goonuddtQRrWqqn5nFyczVKaie28f3kDkHWkHtURSLE` | Solscan/no IDL | Probe ; adresse à revérifier si erreur de taille. |
| `0.7.114` | `humidifi` | `9H6tua7jkLhdm3w8BvgpTn5LZNU7g4ZynDmCiNN3q6Rp` | Solscan/no IDL | Probe. |
| `0.7.115` | `obric_v2` | `obriQD1zbpyLz95G5n7nJe6a4DPjpFwa5XYPoNm113y` | Solscan/no IDL | Probe. |
| `0.7.116` | `ondo_global_market` | `XzTT4XB8m7sLD2xi6snefSasaswsKCxx5Tifjondogm` | Solscan IDL | Rôle marché/tokenized assets à confirmer. |
| `0.7.117` | `scorch` | `SCoRcH8c2dpjvcJD6FiPbCSQyQgu3PcUAWj2Xxx3mqn` | Solscan/no IDL | Probe. |
| `0.7.118` | `solfi` | `SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe` | Solscan/no IDL | Probe. |
| `0.7.119` | `solfi_v2` | `SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF` | Solscan/no IDL | Probe. |
| `0.7.120` | `zerofi` | `ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY` | Solscan/no IDL | Probe. |
| `0.7.121` | `zora` | `zoRabwLGd5zXaV7Gxacppw8tcceXEiTrSKyNLSaSTUc` | Solscan/no IDL | Probe, pas de promotion sans corpus. |
| `0.7.122` | `1dex` | `DEXYosS6oEGvk8uCDayvwEZz4qEyDJRf9nFgYCaqPMTm` | Solscan/no IDL | Probe. |
| `0.7.123` | `aldrin_amm` | `AMM55ShdkoGRB5jVYPjWziwk8m5MpwyDgsMWHaMSQWH6` | Legacy/no IDL | Historique/probe. |
| `0.7.124` | `aldrin_amm_v2` | `CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4` | Legacy/no IDL | Historique/probe. |
| `0.7.125` | `crema_finance` | `CLMM9tUoggJu2wagPkkqs9eFG4BWhVBZWkP1qv3Sp7tR` | Legacy/no IDL | Historique/probe. |
| `0.7.126` | `cropper_finance` | `CTMAxxk34HjKWxQ3QLZK1HpaLXmBveao3ESePXbiyfzh` | Legacy/no IDL | Historique/probe. |
| `0.7.127` | `cropper_whirlpool` | `H8W3ctz92svYg6mkn1UtGfu2aQr2fnUFHM1RhScEtQDt` | Legacy/no IDL | Historique/probe. |
| `0.7.128` | `mercurial_stable_swap` | `MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky` | Legacy/no IDL | Historique stable swap ; deltas exacts si support. |
| `0.7.129` | `saber_stable_swap` | `SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ` | Legacy/no IDL | Historique stable swap. |
| `0.7.130` | `saros_amm` | `SSwapUtytfBdBn1b9NUGG6foMVPtcWgpRU32HToDUZr` | Legacy/no IDL | Historique/probe. |
| `0.7.131` | `step_finance_swap` | `SSwpMgqNDsyV7mAgN9ady4bDVu5ySjmmXejXvy2vLt1` | Legacy/no IDL | Historique/probe. |
| `0.7.132` | `stepn_dooar_swap` | `Dooar9JkhdZ7J3LHN3A7YCuoGRUggXhQaG4kijfLGU2j` | Legacy/no IDL | Historique/probe. |
| `0.7.133` | `raydium_amm_v2_legacy` | `RVKd61ztZW9GUwhRbbLoYVRE5Xf1B2tVscKqwZqXgEr` | Raydium legacy/no IDL | Historique Raydium ; corpus dabord. |
| `0.7.134` | `raydium_amm_v3_legacy` | `27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv` | Raydium legacy/no IDL | Historique Raydium ; ne pas confondre avec CLMM moderne. |
| `0.7.135` | `raydium_pool_v4_json_audit` | aucun `program_id` prouvé par le fichier seul | Audit source annexe | Vérifier `sol-parser-sdk/idls/raydium_pool_v4.json` après les surfaces documentées ; patch AMM v4 si amélioration, sinon clôture no-op. |
| `0.7.136` | cleanup `SOLSCAN_ACCOUNT_SOURCES` | n/a | Nettoyage registry/constants | Retirer doublons/promotions ; les programmes validés deviennent constantes/support matrix, les comptes non-programmes restent contexte ou sont supprimés. |
| `0.7.137` | base neuve multi-programmes | n/a | Validation consolidée | Replay consolidé, coverage global, zéro faux trade/candle, diagnostics bloquants à zéro. |
| `0.7.57` | `meteora_dlmm` | `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo` | Meteora / DLMM | **Clos** : full decode + full materialization ; `76` instructions IDL, `30` events Anchor, swaps, bins, positions, liquidity, fees/rewards/admin/limit-order, sans double-count. |
| `0.7.58` | `demo4_program_surface_discovery` | n/a | Outillage / discovery | Afficher les contenus non pris en compte : program ids, discriminators, Anchor logs/events, upstream fallbacks, layouts et samples ; aucune matérialisation automatique. |
| `0.7.59` | `sqlite_db_transaction_merger` | n/a | Outillage / consolidation DB | Ajouter un binaire utilisant `kb_lib` pour fusionner plusieurs `.db` de corpus existants vers une base output sans RPC/backfill. |
| `0.7.60` | `meteora_damm_v1` | `Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB` | Meteora / DAMM v1 | Parité upstream finale : pools, swaps, liquidity, lock, fees/admin. |
| `0.7.61` | `meteora_damm_v2` | `cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG` | Meteora / DAMM v2 | Couverture complète : create/custom pools, swaps, liquidity, dynamic config, fees/admin. |
| `0.7.62` | `meteora_vault` | `24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi` | Meteora / vault | Vault deposit/withdraw/fee/accounting ; pas de candle directe. |
| `0.7.63` | `system_program` | `11111111111111111111111111111111` | Système Solana | Create/assign/transfer account ; side effects de contexte, pas de trade. |
| `0.7.64` | `spl_token` | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` | SPL Token | Transfer, mint, burn, close account, sync native ; base transversale pour deltas. |
| `0.7.65` | `spl_token_2022` | `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb` | Token-2022 | Transfers/extensions Token-2022, mint/burn/close, comptes et side effects. |
| `0.7.66` | `associated_token_account` | `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL` | Système token | Création ATA, rattachement wallet/token/pool. |
| `0.7.67` | `compute_budget` | `ComputeBudget111111111111111111111111111111` | Contexte tx | Budget/prioritization fee ; utile scoring/MEV, pas de trade. |
| `0.7.68` | `memo` | `MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr` | Contexte tx | Mémo transactionnel et attribution éventuelle. |
| `0.7.69` | `address_lookup_table` | `AddressLookupTab1e1111111111111111111111111` | Contexte tx | Résolution/diagnostic ALT si nécessaire. |
| `0.7.70` | `mpl_token_metadata` | `metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s` | Metadata | Enrichissement token/NFT/mint metadata. |
| `0.7.71` | `mpl_core` | `CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d` | Metadata / asset | Contexte asset si présent dans corpus. |
| `0.7.72` | `bubblegum` | `BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY` | Compressed assets | Audit/contexte assets compressés, pas DEX. |
| `0.7.73` | `raydium_routing` | `routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS` | Router | Route/legs Raydium ; éviter le double-count avec DEX effectifs. |
| `0.7.74` | `jupiter_swap_v6` | `JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4` | Aggregator | Route attribution, legs, no duplicate trade/candle. |
| `0.7.75` | `jupiter_swap_v4` | `JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB` | Legacy aggregator | Audit/route only si corpus encore utile. |
| `0.7.76` | `dflow_aggregator_v4` | `DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH` | Aggregator | Route/intent/orderflow ; pas de double matérialisation. |
| `0.7.77` | `okx_dex` | `6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma` | Aggregator/router | Route attribution ; trades seulement si source exacte non doublonnée. |
| `0.7.78` | `onchain_labs_dex_v2` | `proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u` | Router/DEX candidat | Corpus dabord ; classifier route vs DEX effectif. |
| `0.7.79` | `titan_router` | `T1TANpTeScyeqVzzgNViGDNrkQ6qHz9KrSBS4aNXvGT` | Router | Audit route-only sauf preuve de trade direct non doublonné. |
| `0.7.80` | `sanctum_router` | `stkitrT1Uoy18Dk1fTrgPw8W6MVzoCfYoAFT4MLsmhq` | Router | Route/liquid staking context ; pas de candle DEX directe. |
| `0.7.81` | `orca_whirlpools` | `whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc` | DEX avec IDL/source | Swaps, pools, positions, liquidity, fees/rewards. |
| `0.7.82` | `stabble_stable_swap` | `swapNyd8XiQwJ6ianp9snpu4brUqFxadzvHebnAXjJZ` | DEX avec IDL/source | Stable swap ; deltas exacts, liquidity/admin. |
| `0.7.83` | `stabble_weighted_swap` | `swapFpHZwjELNnjvThjajtiVmkz3yPQEHjLtka2fwHW` | DEX avec IDL/source | Weighted swap, deltas exacts, liquidity/admin. |
| `0.7.84` | `stabble_clmm` | `6dMXqGZ3ga2dikrYS9ovDXgHGh5RUsb2RTUj6hrQXhk6` | DEX avec IDL/source | CLMM Stabble si corpus utile. |
| `0.7.85` | `bonkswap` | `BSwp6bEBihVLdqJRKGgzjcGLHkcTuzmSo1TQkHepzH8p` | DEX avec IDL/source | Swap/liquidity/non-trade. |
| `0.7.86` | `boop_fun` | `boop8hVGQGqehUK2iVEMEnMrL5RbjywRzHKBmBE7ry4` | Launch/DEX candidat | Launch/swap/migration selon corpus. |
| `0.7.87` | `byreal_clmm` | `REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2` | DEX avec IDL/source | CLMM ; corpus puis matérialisation contrôlée. |
| `0.7.88` | `fusionamm` | `fUSioN9YKKSa3CUC2YUc4tPkHJ5Y6XW1yz8y6F7qWz9` | DEX avec IDL/source | AMM ; swaps/liquidity si corpus. |
| `0.7.89` | `goosefx_v1` | `GAMMA7meSFWaBXF25oSUgmGRwaW6sCMFLmBNiMSdbHVT` | DEX avec IDL/source | DEX/AMM selon corpus. |
| `0.7.90` | `goosefx_v2` | `GFXsSL5sSaDfNFQUYsHekbWBW1TsFdjDYzACh62tEHxn` | DEX avec IDL/source | DEX/AMM selon corpus. |
| `0.7.91` | `guac_swap` | `Gswppe6ERWKpUTXvRPfXdzHhiCyJvLadVvXGfdpBqcE1` | DEX avec IDL/source | Swap/liquidity si corpus. |
| `0.7.92` | `hylo_exchange` | `HYEXCHtHkBagdStcJCp3xbbb9B7sdMdWXFNj6mdsG4hn` | DEX/source à classifier | Classer DEX/lending/stable selon IDL/corpus. |
| `0.7.93` | `printr` | `T8HsGYv7sMk3kTnyaRqZrbRPuntYzdh12evXBkprint` | Launch/DEX candidat | Corpus dabord ; surface launch/swap à confirmer. |
| `0.7.94` | `moonit` | `MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG` | Launch/DEX candidat | Launch/migration/swap si prouvé. |
| `0.7.95` | `metadao_amm_v0_5` | `AMMJdEiCCa8mdugg6JPF7gFirmmxisTfDJoSNSUi5zDJ` | DEX avec source | AMM futarchy ; corpus et price semantics. |
| `0.7.96` | `metadao_bid_wall` | `WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx` | Order/bid-wall | Order/bid-wall context ; pas de candle directe sans fill exact. |
| `0.7.97` | `metadao_launchpad` | `moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM` | Launch surface | Launch/ICO surface ; pas DEX effectif par défaut. |
| `0.7.98` | `vertigo` | `vrTGoBuy5rYSxAfV3jaRJWHH6nN9WK4NRExGxsk1bCJ` | DEX/source | Swap/launch selon corpus. |
| `0.7.99` | `virtuals` | `5U3EU2ubXtK84QcRjWVmYt9RaDyA8gKxdUrPFXmZyaki` | Launch/DEX candidat | Launch/AMM à confirmer. |
| `0.7.100` | `wavebreak` | `waveQX2yP3H1pVU8djGvEHmYg8uamQ84AuyGtpsrXTF` | DEX/source | Corpus et rôle exact. |
| `0.7.101` | `woofi` | `WooFif76YGRNjk1pA8wCsN67aQsD9f9iLsz4NcJ1AVb` | DEX/router | Swap/route selon corpus. |
| `0.7.102` | `pancake_swap` | `HpNfyc2Saw7RKkQd8nEL4khUcuPhQ7WwY1B2qjx8jxFq` | DEX/source | DEX Solana à confirmer par corpus. |
| `0.7.103` | `gavel` | `srAMMzfVHVAtgSJc8iH6CfKzuWuUTzLHVCE81QU1rgi` | Source upstream | Corpus dabord ; rôle exact à classer. |
| `0.7.104` | `heaven` | `HEAVENoP2qxoeuF8Dj2oT1GHEnu49U5mJYkdeC8BAX2o` | Launch/DEX candidat | Launch/DEX selon corpus. |
| `0.7.105` | `lifinity_v2` | `2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c` | DEX legacy/actif | Support selon corpus. |
| `0.7.106` | `moonshot` | `MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG` | Source upstream/Solscan | À dédupliquer avec `moonit` si même program id. |
| `0.7.107` | `openbook_v2` | `opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb` | Orderbook | Audit/orderbook complet ; trade/candle seulement si fills exacts. |
| `0.7.108` | `phoenix_v1` | `PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY` | Orderbook | Audit-only complet avant toute matérialisation fill. |
| `0.7.109` | `alphaq` | `ALPHAQmeA7bjrVuccPsYPiCvsi428SNwte66Srvs4pHA` | Solscan/no IDL | Probe par Demo3 + corpus ; décider support ou abandon. |
| `0.7.110` | `aquifer` | `AQU1FRd7papthgdrwPTTq5JacJh8YtwEXaBfKU3bTz45` | Solscan/no IDL | Probe uniquement. |
| `0.7.111` | `bisonfi` | `BiSoNHVpsVZW2F7rx2eQ59yQwKxzU5NvBcmKshCSUypi` | Solscan/no IDL | Probe uniquement. |
| `0.7.112` | `dexlab` | `DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N` | Solscan/no IDL | Vérifier support partiel / corpus. |
| `0.7.113` | `fluxbeam` | `FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X` | Solscan/no IDL | Vérifier support partiel / corpus. |
| `0.7.114` | `goonfi` | `goonERTdGsjnkZqWuVjs73BZ3Pb9qoCUdBUL17BnS5j` | Solscan/no IDL | Probe. |
| `0.7.115` | `goonfi_v2` | `goonuddtQRrWqqn5nFyczVKaie28f3kDkHWkHtURSLE` | Solscan/no IDL | Probe ; adresse à revérifier si erreur de taille. |
| `0.7.116` | `humidifi` | `9H6tua7jkLhdm3w8BvgpTn5LZNU7g4ZynDmCiNN3q6Rp` | Solscan/no IDL | Probe. |
| `0.7.117` | `obric_v2` | `obriQD1zbpyLz95G5n7nJe6a4DPjpFwa5XYPoNm113y` | Solscan/no IDL | Probe. |
| `0.7.118` | `ondo_global_market` | `XzTT4XB8m7sLD2xi6snefSasaswsKCxx5Tifjondogm` | Solscan IDL | Rôle marché/tokenized assets à confirmer. |
| `0.7.119` | `scorch` | `SCoRcH8c2dpjvcJD6FiPbCSQyQgu3PcUAWj2Xxx3mqn` | Solscan/no IDL | Probe. |
| `0.7.120` | `solfi` | `SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe` | Solscan/no IDL | Probe. |
| `0.7.121` | `solfi_v2` | `SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF` | Solscan/no IDL | Probe. |
| `0.7.122` | `zerofi` | `ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY` | Solscan/no IDL | Probe. |
| `0.7.123` | `zora` | `zoRabwLGd5zXaV7Gxacppw8tcceXEiTrSKyNLSaSTUc` | Solscan/no IDL | Probe, pas de promotion sans corpus. |
| `0.7.124` | `1dex` | `DEXYosS6oEGvk8uCDayvwEZz4qEyDJRf9nFgYCaqPMTm` | Solscan/no IDL | Probe. |
| `0.7.125` | `aldrin_amm` | `AMM55ShdkoGRB5jVYPjWziwk8m5MpwyDgsMWHaMSQWH6` | Legacy/no IDL | Historique/probe. |
| `0.7.126` | `aldrin_amm_v2` | `CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4` | Legacy/no IDL | Historique/probe. |
| `0.7.127` | `crema_finance` | `CLMM9tUoggJu2wagPkkqs9eFG4BWhVBZWkP1qv3Sp7tR` | Legacy/no IDL | Historique/probe. |
| `0.7.128` | `cropper_finance` | `CTMAxxk34HjKWxQ3QLZK1HpaLXmBveao3ESePXbiyfzh` | Legacy/no IDL | Historique/probe. |
| `0.7.129` | `cropper_whirlpool` | `H8W3ctz92svYg6mkn1UtGfu2aQr2fnUFHM1RhScEtQDt` | Legacy/no IDL | Historique/probe. |
| `0.7.130` | `mercurial_stable_swap` | `MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky` | Legacy/no IDL | Historique stable swap ; deltas exacts si support. |
| `0.7.131` | `saber_stable_swap` | `SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ` | Legacy/no IDL | Historique stable swap. |
| `0.7.132` | `saros_amm` | `SSwapUtytfBdBn1b9NUGG6foMVPtcWgpRU32HToDUZr` | Legacy/no IDL | Historique/probe. |
| `0.7.133` | `step_finance_swap` | `SSwpMgqNDsyV7mAgN9ady4bDVu5ySjmmXejXvy2vLt1` | Legacy/no IDL | Historique/probe. |
| `0.7.134` | `stepn_dooar_swap` | `Dooar9JkhdZ7J3LHN3A7YCuoGRUggXhQaG4kijfLGU2j` | Legacy/no IDL | Historique/probe. |
| `0.7.135` | `raydium_amm_v2_legacy` | `RVKd61ztZW9GUwhRbbLoYVRE5Xf1B2tVscKqwZqXgEr` | Raydium legacy/no IDL | Historique Raydium ; corpus dabord. |
| `0.7.136` | `raydium_amm_v3_legacy` | `27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv` | Raydium legacy/no IDL | Historique Raydium ; ne pas confondre avec CLMM moderne. |
| `0.7.137` | `raydium_pool_v4_json_audit` | aucun `program_id` prouvé par le fichier seul | Audit source annexe | Vérifier `sol-parser-sdk/idls/raydium_pool_v4.json` après les surfaces documentées ; patch AMM v4 si amélioration, sinon clôture no-op. |
| `0.7.138` | cleanup `SOLSCAN_ACCOUNT_SOURCES` | n/a | Nettoyage registry/constants | Retirer doublons/promotions ; les programmes validés deviennent constantes/support matrix, les comptes non-programmes restent contexte ou sont supprimés. |
| `0.7.139` | base neuve multi-programmes | n/a | Validation consolidée | Replay consolidé, coverage global, zéro faux trade/candle, diagnostics bloquants à zéro. |
Ce plan remplace les regroupements larges qui mélangeaient plusieurs DEX ou plusieurs `program_id` dans une seule tranche. `raydium_pool_v4.json` est explicitement repoussé vers la fin : il ne bloque plus `0.7.53`.
@@ -1422,130 +1431,108 @@ Objectif : reprendre Raydium Stable comme tranche Raydium dédiée après AMM v4
Réalisé : decoder legacy 1 octet, surface locale `00..0d`, matérialisation lifecycle/liquidity/admin/fee/orderbook selon contexte, swaps `swap_base_in/out` matérialisés uniquement depuis deltas vault exacts (`stable_swap_vault_balance_delta`), transactions failed decoded-only, invariants trade/candle propres.
### 6.085. Versions `0.7.53` à `0.7.137` — phasage par `program_id`
Objectif : reprendre la suite après `0.7.52 raydium_stable_swap` avec une règle stricte : **une version = un `program_id`**.
### 6.085. Versions `0.7.53` à `0.7.139` — phasage actif post-DLMM
Les comptes non-programmes ne créent pas de tranche decoder autonome. `SOLSCAN_ACCOUNT_SOURCES` reste un inventaire de découverte et sera nettoyé après validation des surfaces.
Objectif : maintenir un phasage lisible après la clôture `0.7.57 meteora_dlmm`.
#### Bloc Pump
Décisions actives :
| Version | Decoder / surface | Program id | Objectif |
|---:|---|---|---|
| `0.7.53` | `pump_swap` | `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA` | Clos : `buy/sell/buy_exact_quote_in` depuis sources exactes, non-trades spécialisés, events Anchor audit-only. |
| `0.7.54` | `pump_fun` | `6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P` | **Clos** : decoder maximal local, trades directs et `trade_event` canonique, non-trades selon contexte, validations propres. |
| `0.7.55` | `pump_fees` | `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` | Couvrir fee accounting/claim/config ; aucun trade/candle direct. |
- `0.7.58` est réservé à `demo4_program_surface_discovery` : affichage et revue de surfaces non prises en compte, sans matérialisation automatique.
- `0.7.59` est réservé au binaire de consolidation de bases SQLite : fusion de corpus transactionnels déjà présents, sans RPC/backfill.
- `0.7.60` reprend le travail DEX avec `meteora_damm_v1`.
- `0.7.61` cible `meteora_damm_v2`.
- `0.7.62` cible `meteora_vault`.
- Après `0.7.62`, le décalage de deux versions est assumé et documenté.
#### Bloc Meteora
| Version | Decoder / surface | Program id | Objectif |
|---:|---|---|---|
| `0.7.56` | `meteora_dbc` | `dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN` | Compléter toutes les instructions/events DBC : launch/bonding, swap exploitable, migration, fees/admin/config. |
| `0.7.57` | `meteora_dlmm` | `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo` | Full decode + full materialization : `76` instructions IDL, `30` events Anchor, swaps, bins, positions, liquidity, fees/rewards/admin/limit-order, sans double-count. |
| `0.7.58` | `meteora_damm_v1` | `Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB` | Parité upstream finale : pools, swaps, liquidity, lock, fees/admin. |
| `0.7.59` | `meteora_damm_v2` | `cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG` | Couverture compte : create/custom pools, swaps, liquidity, dynamic config, fees/admin. |
| `0.7.60` | `meteora_vault` | `24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi` | Vault deposit/withdraw/fee/accounting ; pas de candle directe. |
#### Bloc programmes système / contexte transactionnel
| Version | Decoder / surface | Program id | Objectif |
|---:|---|---|---|
| `0.7.61` | `system_program` | `11111111111111111111111111111111` | Create/assign/transfer account ; side effects de contexte, pas de trade. |
| `0.7.62` | `spl_token` | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` | Transfer, mint, burn, close account, sync native ; base transversale pour deltas. |
| `0.7.63` | `spl_token_2022` | `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb` | Transfers/extensions Token-2022, mint/burn/close, comptes et side effects. |
| `0.7.64` | `associated_token_account` | `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL` | Création ATA, rattachement wallet/token/pool. |
| `0.7.65` | `compute_budget` | `ComputeBudget111111111111111111111111111111` | Budget/prioritization fee ; utile scoring/MEV, pas de trade. |
| `0.7.66` | `memo` | `MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr` | Mémo transactionnel et attribution éventuelle. |
| `0.7.67` | `address_lookup_table` | `AddressLookupTab1e1111111111111111111111111` | Résolution/diagnostic ALT si nécessaire. |
| `0.7.68` | `mpl_token_metadata` | `metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s` | Enrichissement token/NFT/mint metadata. |
| `0.7.69` | `mpl_core` | `CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d` | Contexte asset si présent dans corpus. |
| `0.7.70` | `bubblegum` | `BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY` | Audit/contexte assets compressés, pas DEX. |
#### Bloc routers / aggregators
| Version | Decoder / surface | Program id | Objectif |
|---:|---|---|---|
| `0.7.71` | `raydium_routing` | `routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS` | Route/legs Raydium ; éviter le double-count avec AMM/CLMM/CPMM/Stable. |
| `0.7.72` | `jupiter_swap_v6` | `JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4` | Route attribution, legs, no duplicate trade/candle. |
| `0.7.73` | `jupiter_swap_v4` | `JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB` | Audit/route only si corpus encore utile. |
| `0.7.74` | `dflow_aggregator_v4` | `DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH` | Route/intent/orderflow ; pas de double matérialisation. |
| `0.7.75` | `okx_dex` | `6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma` | Route attribution ; trades seulement si source exacte non doublonnée. |
| `0.7.76` | `onchain_labs_dex_v2` | `proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u` | Corpus dabord ; classifier route vs DEX effectif. |
| `0.7.77` | `titan_router` | `T1TANpTeScyeqVzzgNViGDNrkQ6qHz9KrSBS4aNXvGT` | Audit route-only sauf preuve de trade direct non doublonné. |
| `0.7.78` | `sanctum_router` | `stkitrT1Uoy18Dk1fTrgPw8W6MVzoCfYoAFT4MLsmhq` | Route/liquid staking context ; pas de candle DEX directe. |
#### Bloc DEX avec IDL/code/source exploitable
| Version | Decoder / surface | Program id | Objectif |
|---:|---|---|---|
| `0.7.79` | `orca_whirlpools` | `whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc` | Swaps, pools, positions, liquidity, fees/rewards. |
| `0.7.80` | `stabble_stable_swap` | `swapNyd8XiQwJ6ianp9snpu4brUqFxadzvHebnAXjJZ` | Stable swap ; deltas exacts, liquidity/admin. |
| `0.7.81` | `stabble_weighted_swap` | `swapFpHZwjELNnjvThjajtiVmkz3yPQEHjLtka2fwHW` | Weighted swap, deltas exacts, liquidity/admin. |
| `0.7.82` | `stabble_clmm` | `6dMXqGZ3ga2dikrYS9ovDXgHGh5RUsb2RTUj6hrQXhk6` | CLMM Stabble si corpus utile. |
| `0.7.83` | `bonkswap` | `BSwp6bEBihVLdqJRKGgzjcGLHkcTuzmSo1TQkHepzH8p` | Swap/liquidity/non-trade. |
| `0.7.84` | `boop_fun` | `boop8hVGQGqehUK2iVEMEnMrL5RbjywRzHKBmBE7ry4` | Launch/swap/migration selon corpus. |
| `0.7.85` | `byreal_clmm` | `REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2` | CLMM ; corpus puis matérialisation contrôlée. |
| `0.7.86` | `fusionamm` | `fUSioN9YKKSa3CUC2YUc4tPkHJ5Y6XW1yz8y6F7qWz9` | AMM ; swaps/liquidity si corpus. |
| `0.7.87` | `goosefx_v1` | `GAMMA7meSFWaBXF25oSUgmGRwaW6sCMFLmBNiMSdbHVT` | DEX/AMM selon corpus. |
| `0.7.88` | `goosefx_v2` | `GFXsSL5sSaDfNFQUYsHekbWBW1TsFdjDYzACh62tEHxn` | DEX/AMM selon corpus. |
| `0.7.89` | `guac_swap` | `Gswppe6ERWKpUTXvRPfXdzHhiCyJvLadVvXGfdpBqcE1` | Swap/liquidity si corpus. |
| `0.7.90` | `hylo_exchange` | `HYEXCHtHkBagdStcJCp3xbbb9B7sdMdWXFNj6mdsG4hn` | Classer DEX/lending/stable selon IDL/corpus. |
| `0.7.91` | `printr` | `T8HsGYv7sMk3kTnyaRqZrbRPuntYzdh12evXBkprint` | Corpus dabord ; surface launch/swap à confirmer. |
| `0.7.92` | `moonit` | `MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG` | Launch/migration/swap si prouvé. |
| `0.7.93` | `metadao_amm_v0_5` | `AMMJdEiCCa8mdugg6JPF7gFirmmxisTfDJoSNSUi5zDJ` | AMM futarchy ; corpus et price semantics. |
| `0.7.94` | `metadao_bid_wall` | `WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx` | Order/bid-wall context ; pas de candle directe sans fill exact. |
| `0.7.95` | `metadao_launchpad` | `moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM` | Launch/ICO surface ; pas DEX effectif par défaut. |
| `0.7.96` | `vertigo` | `vrTGoBuy5rYSxAfV3jaRJWHH6nN9WK4NRExGxsk1bCJ` | Swap/launch selon corpus. |
| `0.7.97` | `virtuals` | `5U3EU2ubXtK84QcRjWVmYt9RaDyA8gKxdUrPFXmZyaki` | Launch/AMM à confirmer. |
| `0.7.98` | `wavebreak` | `waveQX2yP3H1pVU8djGvEHmYg8uamQ84AuyGtpsrXTF` | Corpus et rôle exact. |
| `0.7.99` | `woofi` | `WooFif76YGRNjk1pA8wCsN67aQsD9f9iLsz4NcJ1AVb` | Swap/route selon corpus. |
| `0.7.100` | `pancake_swap` | `HpNfyc2Saw7RKkQd8nEL4khUcuPhQ7WwY1B2qjx8jxFq` | DEX Solana à confirmer par corpus. |
| `0.7.101` | `gavel` | `srAMMzfVHVAtgSJc8iH6CfKzuWuUTzLHVCE81QU1rgi` | Corpus dabord ; rôle exact à classer. |
| `0.7.102` | `heaven` | `HEAVENoP2qxoeuF8Dj2oT1GHEnu49U5mJYkdeC8BAX2o` | Launch/DEX selon corpus. |
| `0.7.103` | `lifinity_v2` | `2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c` | DEX legacy/actif à confirmer. |
| `0.7.104` | `moonshot` | `MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG` | À dédupliquer avec `moonit` si même program id. |
| `0.7.105` | `openbook_v2` | `opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb` | Audit/orderbook complet ; trade/candle seulement si fills exacts. |
| `0.7.106` | `phoenix_v1` | `PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY` | Audit-only complet avant toute matérialisation fill. |
#### Bloc probes / DEX sans IDL fiable / historiques
| Version | Decoder / surface | Program id | Objectif |
|---:|---|---|---|
| `0.7.107` | `alphaq` | `ALPHAQmeA7bjrVuccPsYPiCvsi428SNwte66Srvs4pHA` | Demo3 + corpus ; décider support ou abandon. |
| `0.7.108` | `aquifer` | `AQU1FRd7papthgdrwPTTq5JacJh8YtwEXaBfKU3bTz45` | Probe uniquement. |
| `0.7.109` | `bisonfi` | `BiSoNHVpsVZW2F7rx2eQ59yQwKxzU5NvBcmKshCSUypi` | Probe uniquement. |
| `0.7.110` | `dexlab` | `DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N` | Vérifier support partiel / corpus. |
| `0.7.111` | `fluxbeam` | `FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X` | Vérifier support partiel / corpus. |
| `0.7.112` | `goonfi` | `goonERTdGsjnkZqWuVjs73BZ3Pb9qoCUdBUL17BnS5j` | Probe. |
| `0.7.113` | `goonfi_v2` | `goonuddtQRrWqqn5nFyczVKaie28f3kDkHWkHtURSLE` | Probe ; adresse à revérifier si erreur de taille. |
| `0.7.114` | `humidifi` | `9H6tua7jkLhdm3w8BvgpTn5LZNU7g4ZynDmCiNN3q6Rp` | Probe. |
| `0.7.115` | `obric_v2` | `obriQD1zbpyLz95G5n7nJe6a4DPjpFwa5XYPoNm113y` | Probe. |
| `0.7.116` | `ondo_global_market` | `XzTT4XB8m7sLD2xi6snefSasaswsKCxx5Tifjondogm` | Rôle marché/tokenized assets à confirmer. |
| `0.7.117` | `scorch` | `SCoRcH8c2dpjvcJD6FiPbCSQyQgu3PcUAWj2Xxx3mqn` | Probe. |
| `0.7.118` | `solfi` | `SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe` | Probe. |
| `0.7.119` | `solfi_v2` | `SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF` | Probe. |
| `0.7.120` | `zerofi` | `ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY` | Probe. |
| `0.7.121` | `zora` | `zoRabwLGd5zXaV7Gxacppw8tcceXEiTrSKyNLSaSTUc` | Probe, pas de promotion sans corpus. |
| `0.7.122` | `1dex` | `DEXYosS6oEGvk8uCDayvwEZz4qEyDJRf9nFgYCaqPMTm` | Probe. |
| `0.7.123` | `aldrin_amm` | `AMM55ShdkoGRB5jVYPjWziwk8m5MpwyDgsMWHaMSQWH6` | Historique/probe. |
| `0.7.124` | `aldrin_amm_v2` | `CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4` | Historique/probe. |
| `0.7.125` | `crema_finance` | `CLMM9tUoggJu2wagPkkqs9eFG4BWhVBZWkP1qv3Sp7tR` | Historique/probe. |
| `0.7.126` | `cropper_finance` | `CTMAxxk34HjKWxQ3QLZK1HpaLXmBveao3ESePXbiyfzh` | Historique/probe. |
| `0.7.127` | `cropper_whirlpool` | `H8W3ctz92svYg6mkn1UtGfu2aQr2fnUFHM1RhScEtQDt` | Historique/probe. |
| `0.7.128` | `mercurial_stable_swap` | `MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky` | Historique stable swap ; deltas exacts si support. |
| `0.7.129` | `saber_stable_swap` | `SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ` | Historique stable swap. |
| `0.7.130` | `saros_amm` | `SSwapUtytfBdBn1b9NUGG6foMVPtcWgpRU32HToDUZr` | Historique/probe. |
| `0.7.131` | `step_finance_swap` | `SSwpMgqNDsyV7mAgN9ady4bDVu5ySjmmXejXvy2vLt1` | Historique/probe. |
| `0.7.132` | `stepn_dooar_swap` | `Dooar9JkhdZ7J3LHN3A7YCuoGRUggXhQaG4kijfLGU2j` | Historique/probe. |
| `0.7.133` | `raydium_amm_v2_legacy` | `RVKd61ztZW9GUwhRbbLoYVRE5Xf1B2tVscKqwZqXgEr` | Historique Raydium ; corpus dabord. |
| `0.7.134` | `raydium_amm_v3_legacy` | `27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv` | Historique Raydium ; ne pas confondre avec CLMM moderne. |
| `0.7.135` | `raydium_pool_v4_json_audit` | aucun `program_id` prouvé par le fichier seul | Vérifier `sol-parser-sdk/idls/raydium_pool_v4.json` après les surfaces documentées ; patch AMM v4 si amélioration, sinon clôture no-op. |
#### Nettoyage / consolidation
| Version | Scope | Objectif |
|---:|---|---|
| `0.7.136` | cleanup `SOLSCAN_ACCOUNT_SOURCES` | Retirer les doublons/promotions : les vrais programmes validés deviennent constantes + support matrix ; les comptes non-programmes restent contexte ou sont supprimés. |
| `0.7.137` | base neuve multi-programmes | Replay consolidé, coverage global, zéro faux trade/candle, diagnostics bloquants à zéro. |
| Version cible | Decoder / surface | Program id | Famille | Objectif de clôture |
|---|---|---|---|---|
| `0.7.53` | `pump_swap` | `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA` | Pump / AMM | **Clos** : `buy/sell/buy_exact_quote_in` matérialisés seulement depuis sources exactes ; events Anchor audit-only ; tests synthétiques IDL ; SQL global. |
| `0.7.54` | `pump_fun` | `6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P` | Pump / launch-bonding | **Clos** : decoder maximal IDL/local, trades directs `buy/sell/buy_exact_sol_in`, v2/exact via `trade_event`, non-trades matérialisés selon contexte, validations Pump.fun propres. |
| `0.7.55` | `pump_fees` | `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` | Pump / fee | **Clos** : `29` instructions, `20` events Anchor, fee/reward/admin/lifecycle, `get_fees` decoded-only, failed tx audit-only, aucun trade/candle direct. |
| `0.7.56` | `meteora_dbc` | `dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN` | Meteora / DBC | Clos : `28` instructions et `23` events Anchor couverts, swaps `swap/swap2`, lifecycle/admin/fees, `k_sol_fee_event_amounts`, validations SQL propres. |
| `0.7.57` | `meteora_dlmm` | `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo` | Meteora / DLMM | **Clos** : full decode + full materialization ; `76` instructions IDL, `30` events Anchor, swaps, bins, positions, liquidity, fees/rewards/admin/limit-order, sans double-count. |
| `0.7.58` | `demo4_program_surface_discovery` | n/a | Outillage / discovery | Afficher les contenus non pris en compte : program ids, discriminators, Anchor logs/events, upstream fallbacks, layouts et samples ; aucune matérialisation automatique. |
| `0.7.59` | `sqlite_db_transaction_merger` | n/a | Outillage / consolidation DB | Ajouter un binaire utilisant `kb_lib` pour fusionner plusieurs `.db` de corpus existants vers une base output sans RPC/backfill. |
| `0.7.60` | `meteora_damm_v1` | `Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB` | Meteora / DAMM v1 | Parité upstream finale : pools, swaps, liquidity, lock, fees/admin. |
| `0.7.61` | `meteora_damm_v2` | `cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG` | Meteora / DAMM v2 | Couverture complète : create/custom pools, swaps, liquidity, dynamic config, fees/admin. |
| `0.7.62` | `meteora_vault` | `24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi` | Meteora / vault | Vault deposit/withdraw/fee/accounting ; pas de candle directe. |
| `0.7.63` | `system_program` | `11111111111111111111111111111111` | Système Solana | Create/assign/transfer account ; side effects de contexte, pas de trade. |
| `0.7.64` | `spl_token` | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` | SPL Token | Transfer, mint, burn, close account, sync native ; base transversale pour deltas. |
| `0.7.65` | `spl_token_2022` | `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb` | Token-2022 | Transfers/extensions Token-2022, mint/burn/close, comptes et side effects. |
| `0.7.66` | `associated_token_account` | `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL` | Système token | Création ATA, rattachement wallet/token/pool. |
| `0.7.67` | `compute_budget` | `ComputeBudget111111111111111111111111111111` | Contexte tx | Budget/prioritization fee ; utile scoring/MEV, pas de trade. |
| `0.7.68` | `memo` | `MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr` | Contexte tx | Mémo transactionnel et attribution éventuelle. |
| `0.7.69` | `address_lookup_table` | `AddressLookupTab1e1111111111111111111111111` | Contexte tx | Résolution/diagnostic ALT si nécessaire. |
| `0.7.70` | `mpl_token_metadata` | `metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s` | Metadata | Enrichissement token/NFT/mint metadata. |
| `0.7.71` | `mpl_core` | `CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d` | Metadata / asset | Contexte asset si présent dans corpus. |
| `0.7.72` | `bubblegum` | `BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY` | Compressed assets | Audit/contexte assets compressés, pas DEX. |
| `0.7.73` | `raydium_routing` | `routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS` | Router | Route/legs Raydium ; éviter le double-count avec DEX effectifs. |
| `0.7.74` | `jupiter_swap_v6` | `JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4` | Aggregator | Route attribution, legs, no duplicate trade/candle. |
| `0.7.75` | `jupiter_swap_v4` | `JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB` | Legacy aggregator | Audit/route only si corpus encore utile. |
| `0.7.76` | `dflow_aggregator_v4` | `DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH` | Aggregator | Route/intent/orderflow ; pas de double matérialisation. |
| `0.7.77` | `okx_dex` | `6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma` | Aggregator/router | Route attribution ; trades seulement si source exacte non doublonnée. |
| `0.7.78` | `onchain_labs_dex_v2` | `proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u` | Router/DEX candidat | Corpus dabord ; classifier route vs DEX effectif. |
| `0.7.79` | `titan_router` | `T1TANpTeScyeqVzzgNViGDNrkQ6qHz9KrSBS4aNXvGT` | Router | Audit route-only sauf preuve de trade direct non doublonné. |
| `0.7.80` | `sanctum_router` | `stkitrT1Uoy18Dk1fTrgPw8W6MVzoCfYoAFT4MLsmhq` | Router | Route/liquid staking context ; pas de candle DEX directe. |
| `0.7.81` | `orca_whirlpools` | `whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc` | DEX avec IDL/source | Swaps, pools, positions, liquidity, fees/rewards. |
| `0.7.82` | `stabble_stable_swap` | `swapNyd8XiQwJ6ianp9snpu4brUqFxadzvHebnAXjJZ` | DEX avec IDL/source | Stable swap ; deltas exacts, liquidity/admin. |
| `0.7.83` | `stabble_weighted_swap` | `swapFpHZwjELNnjvThjajtiVmkz3yPQEHjLtka2fwHW` | DEX avec IDL/source | Weighted swap, deltas exacts, liquidity/admin. |
| `0.7.84` | `stabble_clmm` | `6dMXqGZ3ga2dikrYS9ovDXgHGh5RUsb2RTUj6hrQXhk6` | DEX avec IDL/source | CLMM Stabble si corpus utile. |
| `0.7.85` | `bonkswap` | `BSwp6bEBihVLdqJRKGgzjcGLHkcTuzmSo1TQkHepzH8p` | DEX avec IDL/source | Swap/liquidity/non-trade. |
| `0.7.86` | `boop_fun` | `boop8hVGQGqehUK2iVEMEnMrL5RbjywRzHKBmBE7ry4` | Launch/DEX candidat | Launch/swap/migration selon corpus. |
| `0.7.87` | `byreal_clmm` | `REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2` | DEX avec IDL/source | CLMM ; corpus puis matérialisation contrôlée. |
| `0.7.88` | `fusionamm` | `fUSioN9YKKSa3CUC2YUc4tPkHJ5Y6XW1yz8y6F7qWz9` | DEX avec IDL/source | AMM ; swaps/liquidity si corpus. |
| `0.7.89` | `goosefx_v1` | `GAMMA7meSFWaBXF25oSUgmGRwaW6sCMFLmBNiMSdbHVT` | DEX avec IDL/source | DEX/AMM selon corpus. |
| `0.7.90` | `goosefx_v2` | `GFXsSL5sSaDfNFQUYsHekbWBW1TsFdjDYzACh62tEHxn` | DEX avec IDL/source | DEX/AMM selon corpus. |
| `0.7.91` | `guac_swap` | `Gswppe6ERWKpUTXvRPfXdzHhiCyJvLadVvXGfdpBqcE1` | DEX avec IDL/source | Swap/liquidity si corpus. |
| `0.7.92` | `hylo_exchange` | `HYEXCHtHkBagdStcJCp3xbbb9B7sdMdWXFNj6mdsG4hn` | DEX/source à classifier | Classer DEX/lending/stable selon IDL/corpus. |
| `0.7.93` | `printr` | `T8HsGYv7sMk3kTnyaRqZrbRPuntYzdh12evXBkprint` | Launch/DEX candidat | Corpus dabord ; surface launch/swap à confirmer. |
| `0.7.94` | `moonit` | `MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG` | Launch/DEX candidat | Launch/migration/swap si prouvé. |
| `0.7.95` | `metadao_amm_v0_5` | `AMMJdEiCCa8mdugg6JPF7gFirmmxisTfDJoSNSUi5zDJ` | DEX avec source | AMM futarchy ; corpus et price semantics. |
| `0.7.96` | `metadao_bid_wall` | `WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx` | Order/bid-wall | Order/bid-wall context ; pas de candle directe sans fill exact. |
| `0.7.97` | `metadao_launchpad` | `moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM` | Launch surface | Launch/ICO surface ; pas DEX effectif par défaut. |
| `0.7.98` | `vertigo` | `vrTGoBuy5rYSxAfV3jaRJWHH6nN9WK4NRExGxsk1bCJ` | DEX/source | Swap/launch selon corpus. |
| `0.7.99` | `virtuals` | `5U3EU2ubXtK84QcRjWVmYt9RaDyA8gKxdUrPFXmZyaki` | Launch/DEX candidat | Launch/AMM à confirmer. |
| `0.7.100` | `wavebreak` | `waveQX2yP3H1pVU8djGvEHmYg8uamQ84AuyGtpsrXTF` | DEX/source | Corpus et rôle exact. |
| `0.7.101` | `woofi` | `WooFif76YGRNjk1pA8wCsN67aQsD9f9iLsz4NcJ1AVb` | DEX/router | Swap/route selon corpus. |
| `0.7.102` | `pancake_swap` | `HpNfyc2Saw7RKkQd8nEL4khUcuPhQ7WwY1B2qjx8jxFq` | DEX/source | DEX Solana à confirmer par corpus. |
| `0.7.103` | `gavel` | `srAMMzfVHVAtgSJc8iH6CfKzuWuUTzLHVCE81QU1rgi` | Source upstream | Corpus dabord ; rôle exact à classer. |
| `0.7.104` | `heaven` | `HEAVENoP2qxoeuF8Dj2oT1GHEnu49U5mJYkdeC8BAX2o` | Launch/DEX candidat | Launch/DEX selon corpus. |
| `0.7.105` | `lifinity_v2` | `2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c` | DEX legacy/actif | Support selon corpus. |
| `0.7.106` | `moonshot` | `MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG` | Source upstream/Solscan | À dédupliquer avec `moonit` si même program id. |
| `0.7.107` | `openbook_v2` | `opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb` | Orderbook | Audit/orderbook complet ; trade/candle seulement si fills exacts. |
| `0.7.108` | `phoenix_v1` | `PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY` | Orderbook | Audit-only complet avant toute matérialisation fill. |
| `0.7.109` | `alphaq` | `ALPHAQmeA7bjrVuccPsYPiCvsi428SNwte66Srvs4pHA` | Solscan/no IDL | Probe par Demo3 + corpus ; décider support ou abandon. |
| `0.7.110` | `aquifer` | `AQU1FRd7papthgdrwPTTq5JacJh8YtwEXaBfKU3bTz45` | Solscan/no IDL | Probe uniquement. |
| `0.7.111` | `bisonfi` | `BiSoNHVpsVZW2F7rx2eQ59yQwKxzU5NvBcmKshCSUypi` | Solscan/no IDL | Probe uniquement. |
| `0.7.112` | `dexlab` | `DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N` | Solscan/no IDL | Vérifier support partiel / corpus. |
| `0.7.113` | `fluxbeam` | `FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X` | Solscan/no IDL | Vérifier support partiel / corpus. |
| `0.7.114` | `goonfi` | `goonERTdGsjnkZqWuVjs73BZ3Pb9qoCUdBUL17BnS5j` | Solscan/no IDL | Probe. |
| `0.7.115` | `goonfi_v2` | `goonuddtQRrWqqn5nFyczVKaie28f3kDkHWkHtURSLE` | Solscan/no IDL | Probe ; adresse à revérifier si erreur de taille. |
| `0.7.116` | `humidifi` | `9H6tua7jkLhdm3w8BvgpTn5LZNU7g4ZynDmCiNN3q6Rp` | Solscan/no IDL | Probe. |
| `0.7.117` | `obric_v2` | `obriQD1zbpyLz95G5n7nJe6a4DPjpFwa5XYPoNm113y` | Solscan/no IDL | Probe. |
| `0.7.118` | `ondo_global_market` | `XzTT4XB8m7sLD2xi6snefSasaswsKCxx5Tifjondogm` | Solscan IDL | Rôle marché/tokenized assets à confirmer. |
| `0.7.119` | `scorch` | `SCoRcH8c2dpjvcJD6FiPbCSQyQgu3PcUAWj2Xxx3mqn` | Solscan/no IDL | Probe. |
| `0.7.120` | `solfi` | `SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe` | Solscan/no IDL | Probe. |
| `0.7.121` | `solfi_v2` | `SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF` | Solscan/no IDL | Probe. |
| `0.7.122` | `zerofi` | `ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY` | Solscan/no IDL | Probe. |
| `0.7.123` | `zora` | `zoRabwLGd5zXaV7Gxacppw8tcceXEiTrSKyNLSaSTUc` | Solscan/no IDL | Probe, pas de promotion sans corpus. |
| `0.7.124` | `1dex` | `DEXYosS6oEGvk8uCDayvwEZz4qEyDJRf9nFgYCaqPMTm` | Solscan/no IDL | Probe. |
| `0.7.125` | `aldrin_amm` | `AMM55ShdkoGRB5jVYPjWziwk8m5MpwyDgsMWHaMSQWH6` | Legacy/no IDL | Historique/probe. |
| `0.7.126` | `aldrin_amm_v2` | `CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4` | Legacy/no IDL | Historique/probe. |
| `0.7.127` | `crema_finance` | `CLMM9tUoggJu2wagPkkqs9eFG4BWhVBZWkP1qv3Sp7tR` | Legacy/no IDL | Historique/probe. |
| `0.7.128` | `cropper_finance` | `CTMAxxk34HjKWxQ3QLZK1HpaLXmBveao3ESePXbiyfzh` | Legacy/no IDL | Historique/probe. |
| `0.7.129` | `cropper_whirlpool` | `H8W3ctz92svYg6mkn1UtGfu2aQr2fnUFHM1RhScEtQDt` | Legacy/no IDL | Historique/probe. |
| `0.7.130` | `mercurial_stable_swap` | `MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky` | Legacy/no IDL | Historique stable swap ; deltas exacts si support. |
| `0.7.131` | `saber_stable_swap` | `SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ` | Legacy/no IDL | Historique stable swap. |
| `0.7.132` | `saros_amm` | `SSwapUtytfBdBn1b9NUGG6foMVPtcWgpRU32HToDUZr` | Legacy/no IDL | Historique/probe. |
| `0.7.133` | `step_finance_swap` | `SSwpMgqNDsyV7mAgN9ady4bDVu5ySjmmXejXvy2vLt1` | Legacy/no IDL | Historique/probe. |
| `0.7.134` | `stepn_dooar_swap` | `Dooar9JkhdZ7J3LHN3A7YCuoGRUggXhQaG4kijfLGU2j` | Legacy/no IDL | Historique/probe. |
| `0.7.135` | `raydium_amm_v2_legacy` | `RVKd61ztZW9GUwhRbbLoYVRE5Xf1B2tVscKqwZqXgEr` | Raydium legacy/no IDL | Historique Raydium ; corpus dabord. |
| `0.7.136` | `raydium_amm_v3_legacy` | `27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv` | Raydium legacy/no IDL | Historique Raydium ; ne pas confondre avec CLMM moderne. |
| `0.7.137` | `raydium_pool_v4_json_audit` | aucun `program_id` prouvé par le fichier seul | Audit source annexe | Vérifier `sol-parser-sdk/idls/raydium_pool_v4.json` après les surfaces documentées ; patch AMM v4 si amélioration, sinon clôture no-op. |
| `0.7.138` | cleanup `SOLSCAN_ACCOUNT_SOURCES` | n/a | Nettoyage registry/constants | Retirer doublons/promotions ; les programmes validés deviennent constantes/support matrix, les comptes non-programmes restent contexte ou sont supprimés. |
| `0.7.139` | base neuve multi-programmes | n/a | Validation consolidée | Replay consolidé, coverage global, zéro faux trade/candle, diagnostics bloquants à zéro. |
Garde-fous constants : pas de faux trade, pas de fausse candle, pas de `program_id` fictif, pas de promotion sans corpus transactionnel local, pas de double matérialisation router/leg, pas de logique métier DEX profonde dans `kb_demo_app`.
@@ -1737,8 +1724,8 @@ Préconditions considérées acquises avant cette reprise :
Ordre de travail recommandé pour la suite :
1. `0.7.44` : ledger de décodage/replay et skip sûr — acquis ;
2. `0.7.45` : `meteora_dlmm` — clos ;
3. `0.7.46` : `meteora_damm_v1` — clos côté corpus local ;
2. `0.7.45` : `meteora_dlmm` historique — clos ;
3. `0.7.46` : `meteora_damm_v1` historique — clos côté corpus local ;
4. `0.7.47` : Upstream Git Registry / DEX discovery preparation — acquis ;
5. `0.7.48-pre` : event coverage + DB model checkpoint — clos ;
6. `0.7.48` : `raydium_cpmm` — clos ;
@@ -1746,10 +1733,16 @@ Ordre de travail recommandé pour la suite :
8. `0.7.50-pre-r2` : `raydium_launchpad` clos + re-vérification CPMM/CLMM ;
9. `0.7.51` : `raydium_amm_v4` — clos ;
10. `0.7.52` : `raydium_stable_swap` — clos ;
11. `0.7.53` : `pump_swap` / `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA` — clos ;
12. `0.7.54` : `pump_fun` / `6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P` ;
13. `0.7.55` : `pump_fees` / `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` ;
14. `0.7.56+` : appliquer le phasage strict “une version = un `program_id`” défini en section `6.085`.
11. `0.7.53` : `pump_swap` — clos ;
12. `0.7.54` : `pump_fun` — clos ;
13. `0.7.55` : `pump_fees` — clos ;
14. `0.7.56` : `meteora_dbc` — clos ;
15. `0.7.57` : `meteora_dlmm` — clos ;
16. `0.7.58` : `demo4_program_surface_discovery` — à faire ;
17. `0.7.59` : `sqlite_db_transaction_merger` — à faire ;
18. `0.7.60` : `meteora_damm_v1` — prochaine tranche DEX ;
19. `0.7.61` : `meteora_damm_v2` — après DAMM v1 ;
20. `0.7.62+` : appliquer le phasage actif défini en section `6.085`.
Garde-fous constants :

View File

@@ -1,9 +1,15 @@
<!-- file: docs/DEX_DECODER_MATRIX.md -->
# DEX Decoder Matrix — `khadhroony-bobobot` `0.7.56 meteora_dbc closed`
# DEX Decoder Matrix — `khadhroony-bobobot` `0.7.57 meteora_dlmm closed`
## Note `0.7.57 closed` — Meteora DLMM clôturé et suite discovery
La tranche `0.7.57` ferme `meteora_dlmm` / `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo`. Elle remplace la couverture partielle historique `0.7.45` par un decoder local maximal aligné sur l'IDL locale, avec correction du discriminant `75c73e67068e1fcb` en `initialize_preset_parameter_v2`. Les résidus observés utiles ne restent plus en decoded-only.
La prochaine étape n'est pas un nouveau decoder direct, mais `0.7.58 demo4_program_surface_discovery` : affichage et scoring des contenus inconnus/non pris en compte, sans auto-materialization ni auto-promotion.
## Note `0.7.56 closed` — Meteora DBC clôturé, DLMM ensuite
La tranche `0.7.56` ferme `meteora_dbc` / `dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN` depuis l'IDL locale `idls/meteora_dbc.dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN.json`.
@@ -81,7 +87,7 @@ Cette matrice complète `kb_lib/src/dex_support_matrix.rs`. Elle documente **ce
| 8 | `pump_fun` | `supported / 0.7.54 closed` | Surface launch/bonding/migration Pump.fun couverte localement ; trades directs et `trade_event` canonique validés. | Ne rouvrir que pour bug prouvé ou changement externe. |
| 9 | `pump_fees` | `supported / 0.7.55 closed` | Surface fee/config/accounting couverte localement : `29` instructions, `20` events Anchor, fee/reward/admin/lifecycle, tests synthétiques Anchor IDL non observés, failed tx audit-only. | Aucun trade/candle direct ; conserver les deux discriminators Solscan hors IDL comme futures surfaces non observées. |
| 10 | `meteora_dbc` | `supported / closed 0.7.56` | Decoder local maximal : `28` instructions, `23` events Anchor, swaps `swap/swap2`, lifecycle/admin/fees, `k_sol_fee_event_amounts`, validation SQL propre. | Ne pas rouvrir sauf bug prouvé ; préserver la policy fee parent+legs et la recovery allowlistée. |
| 11 | `meteora_dlmm` | `next / 0.7.57 full decode` | Couverture partielle historique validée en `0.7.45`; IDL locale `76` instructions / `30` events Anchor. | Reprendre depuis base neuve : full decode, full materialization fiable, fees/rewards via `fee_event_amounts`, no double-count avec events Anchor. |
| 11 | `meteora_dlmm` | `supported / closed 0.7.57` | Decoder local maximal : `76` instructions IDL, `30` events Anchor, `12` accounts, correction `initialize_preset_parameter_v2`, swaps/exact-out, liquidity, bins, positions, lifecycle, fees/rewards, admin/config et orderbook. | Ne pas rouvrir sauf bug prouvé ; `swap_event/swap2_evt` restent lifecycle `swap_log`, pas trade ; préserver recovery fee/reward allowlistée. |
| 12 | `meteora_damm_v1` | `supported / 0.7.58 parity` | Couverture `0.7.46` : swap, create_pool, add/remove liquidity, claim_fee, create_lock_escrow, lock_liquidity. | Vérifier les surfaces upstream non observées ; améliorer rattachement pool/pair pour remove_liquidity non matérialisés ; revalidation stricte. |
| 13 | `meteora_damm_v2` | `partial / 0.7.59 planned` | `swap`, `instruction_audit`, registry/discriminants et corpus Demo3 existent. | Couvrir tous les events Carbon/source : create pool, liquidity, fees, dynamic config, admin ; déterminer actionability des swaps ; matérialiser si montants fiables. |
| 14 | `phoenix_v1` | `audit-only / 0.7.60 planned` | Decoder local audit-only ; `log_audit`, order place/cancel, withdraw ; parsing strict `0x0f`; events `Reduce`, `Place`, `TimeInForce` observés ; `trade_count=0`. | Terminer tous les events Git : `Fill`, `FillSummary`, `Fee`, `Evict`, `ExpiredOrder`, etc. ; ajouter counts/flags audit ; seulement ensuite étudier trade materialization. |
@@ -346,8 +352,14 @@ La tranche a été validée sur base SQLite dédiée : tous les discriminants `0
Validation finale : `446` tests, clippy OK, `480 replayed`, `264 trades`, `1 liquidity`, `122 lifecycle`, `1056 candles`, `89` parents fee DBC, `96` fee amount legs, invariants SQL propres.
## 0.7.57 — Meteora DLMM prochain
## 0.7.57 — Meteora DLMM clôturé
| Decoder | Program id | Statut | Source locale | Couverture attendue | Décision métier |
| Decoder | Program id | Statut | Source locale | Couverture finale | Décision métier |
|---|---|---|---|---|---|
| `meteora_dlmm` | `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo` | `next / full decode + full materialization` | `idls/meteora_dlmm.LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo.json` | `76` instructions IDL + `30` events Anchor IDL | Swaps/exact-out, liquidity/bin/position, fees/rewards/admin/config, limit order events ; no duplicate trade/candle ; `fee_event_amounts` obligatoire pour fees. |
| `meteora_dlmm` | `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo` | `supported / closed 0.7.57` | `idls/meteora_dlmm.LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo.json` | `76` instructions IDL, `30` events Anchor, `12` accounts, `initialize_preset_parameter_v2` ajouté depuis corpus local | Les 6 swaps instructionnels produisent seuls les trades/candles ; Anchor `swap_event/swap2_evt` = lifecycle `swap_log`; liquidity/bin/position/lifecycle/admin/orderbook/fee/reward matérialisés selon cible ; failed tx audit-only. |
Validation finale : `460` tests, clippy OK, `769 replayed`, `106 trades`, `664 liquidity`, `1107 lifecycle`, `424 candles`, `8062` instruction observations, catalogue `169/218/218`. Checks SQL bloquants propres.
### 0.7.57 pre.001 — Meteora DLMM
`meteora_dlmm` passe de tranche `next` à tranche ouverte `pre.001` : lIDL locale complète est inventoriée, tous les discriminants instruction/event sont classifiés localement, et les familles non prouvées restent decoded-only/audit-safe avec `skip*Reason`.

View File

@@ -1,6 +1,6 @@
<!-- file: docs/DEX_EVENT_COVERAGE_MATRIX.md -->
# DEX Event Coverage Matrix — `khadhroony-bobobot` `0.7.54 pump_fun closed`
# DEX Event Coverage Matrix — `khadhroony-bobobot` `0.7.57 meteora_dlmm closed`
Cette matrice complète `docs/DEX_DECODER_MATRIX.md` avec une lecture par familles d'événements. Elle ne remplace pas la preuve locale : une entrée Git/IDL reste un indice tant qu'elle n'est pas observée dans le corpus local puis validée par replay et SQL.
@@ -326,17 +326,28 @@ Programme : `dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN`. Source locale priorit
Validation finale DBC : `89` parents fee, `96` legs fee amount, aucun parent scalaire sans leg, aucun leg orphelin, aucun decoded event local sans coverage, aucun failed tx matérialisé, aucun multi-target, aucun non-swap vers trade/candle.
## 0.7.57 — Meteora DLMM à reprendre
## `0.7.57``meteora_dlmm` final coverage
Programme : `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo`. Source locale : `idls/meteora_dlmm.LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo.json`.
Programme : `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo`. Source locale : `idls/meteora_dlmm.LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo.json`. Surface finale : `76` instructions IDL, `30` events Anchor, `12` accounts, plus correction locale `initialize_preset_parameter_v2` (`75c73e67068e1fcb`) observée dans le corpus.
| Groupe | Entrées IDL | Famille | Cible DB attendue | Décision à valider |
Validation finale : `460` tests, clippy OK, `769` replayed, `106` trades, `664` liquidity, `1107` lifecycle, `424` candle upserts, `8062` instruction observations. Les checks decoded sans coverage, successful non-materialized, failed tx materialized, multi-target, non-swap trade/candle, fee scalar sans leg, orphan fee legs et coverage duplicates sont vides.
| Famille | Entrées DLMM | Statut `0.7.57` | Cible DB | Justification / règle |
|---|---|---|---|---|
| swaps | `swap`, `swap2`, `swap_exact_out*`, `swap_with_price_impact*`, `Swap`, `Swap2Evt` | swap | `k_sol_trade_events` + candles | Montants exécutés uniquement ; éviter double-count instruction/event. |
| pools / bins | `initialize_*lb_pair*`, `initialize_bin_array*`, `close_bin_array`, `LbPairCreate` | lifecycle | `k_sol_pool_lifecycle_events`, catalog/pair | Catalog si comptes pool/mints/config fiables. |
| liquidity / positions | `add_liquidity*`, `remove_liquidity*`, `remove_all_liquidity`, `rebalance_liquidity`, position create/close/update events | liquidity/lifecycle | `k_sol_liquidity_events`, `k_sol_pool_lifecycle_events` | Montants x/y/bin/liquidity fiables ; sinon skip reason. |
| fees | `claim_fee*`, `withdraw_protocol_fee`, `zap_protocol_fee`, `ClaimFee*`, `CompositionFee` | fee | `k_sol_fee_events` + `k_sol_fee_event_amounts` | Utiliser le socle fee parent+legs ; policy recovery explicite, non globale. |
| rewards | `initialize_reward`, `fund_reward`, `claim_reward*`, `withdraw_ineligible_reward`, reward events | reward | `k_sol_reward_events` | Montant/mint/récompense fiable uniquement. |
| admin/config | fee parameters, pair status, activation, operator, token badge, preset parameter | admin_config | `k_sol_pool_admin_events` ou decoded-only | Matérialiser si acteur/compte cible fiable. |
| limit/order events | place/cancel/close limit order events | orderbook/audit | `k_sol_orderbook_events` si modèle fiable | Pas de candle directe sans fill exact. |
| `swap` | `swap`, `swap2`, `swap_exact_out`, `swap_exact_out2`, `swap_with_price_impact`, `swap_with_price_impact2` | `materialized` | `k_sol_trade_events` + candles | Seules ces instructions produisent trade/candle direct quand montants/mints/pool sont fiables. |
| `swap_log` | `swap_event`, `swap2_evt` | `materialized` | `k_sol_pool_lifecycle_events` | Logs Anchor informatifs matérialisés sans double-count trade/candle. |
| `pool_create` | `create_pool`, `lb_pair_create_event`, `initialize_*_lb_pair*`, `initialize_permission_lb_pair` | `materialized` si observé, sinon `upstream_git_mapped_unverified` | `k_sol_pool_lifecycle_events` | Pool lifecycle ; pas de trade direct. |
| `liquidity_add` | `add_liquidity*`, `add_liquidity_event` | `materialized` | `k_sol_liquidity_events` | Montants/side effects liquidity ; aucun trade/candle. |
| `liquidity_remove` | `remove_liquidity*`, `remove_all_liquidity`, `remove_liquidity_event` | `materialized` | `k_sol_liquidity_events` | Retrait liquidity ; failed tx audit-only. |
| `liquidity_change` | `rebalance_liquidity`, `rebalancing_event` | `materialized` | `k_sol_liquidity_events` | Changement de liquidité/bin ; pas de trade. |
| `position_open` | `initialize_position*`, `position_create_event` | `materialized` | `k_sol_pool_lifecycle_events` | Position/bin lifecycle, pas liquidity artificielle quand l'opération est structurelle. |
| `position_close` | `close_position*`, `position_close_event` | `materialized` | `k_sol_pool_lifecycle_events` | Fermeture position lifecycle. |
| `position_update` | `increase_position_length*`, `decrease_position_length*`, `update_position_operator*`, position update events | `materialized` si observé | `k_sol_pool_lifecycle_events` | Mise à jour de position ; pas admin générique. |
| `bin/oracle lifecycle` | `initialize_bin_array*`, `close_bin_array`, `go_to_a_bin*`, `increase_oracle_length`, `migrate_bin_array` | `materialized` si observé | `k_sol_pool_lifecycle_events` | `close_bin_array` a `16/14` car `2` failed tx `Custom 6015`, comportement attendu. |
| `fee` | `claim_fee*`, `claim_fee*_event`, `composition_fee_event`, `withdraw_protocol_fee`, `zap_protocol_fee` | `materialized` | `k_sol_fee_events` + `k_sol_fee_event_amounts` | Recovery inner SPL transfer strictement allowlistée ; legs multi-mint conservés sans agrégation artificielle. |
| `reward` | `claim_reward*`, `claim_reward*_event`, `fund_reward*`, `initialize_reward*`, `withdraw_ineligible_reward*` | `materialized` si observé | `k_sol_reward_events` | Claims/funding avec amounts récupérés quand transfert réel ; `initialize_reward*` sans amount est normal. |
| `admin/config` | `update_base_fee_parameters`, `fee_parameter_update_event`, `set_pair_status*`, `set_activation_point`, `set_pre_activation*`, preset/token badge/operator/config, `initialize_preset_parameter_v2` | `materialized` si observé | `k_sol_pool_admin_events` | Admin/config non financier ; pas trade/candle. |
| `orderbook` | `place_limit_order*`, `cancel_limit_order*`, `close_limit_order*` | `materialized` | `k_sol_orderbook_events` | Limit/orderbook events sans trade/candle. |
| `account metadata` | `lb_pair`, `bin_array`, `position_v2`, `oracle`, `operator`, `token_badge`, etc. | `upstream_git_mapped_unverified` / non observé | `k_sol_dex_decoded_events_only` | Accounts IDL listés pour coverage, pas de matérialisation métier directe. |
| `instruction_audit` | résidu inconnu | `closed` | `k_sol_pool_admin_events` seulement si observé | Résidu final observé = `0`; `75c73e67068e1fcb` a été promu en `initialize_preset_parameter_v2`. |

View File

@@ -0,0 +1,73 @@
# Validation status — 0.7.57 Meteora DLMM final
## Build
```text
cargo test -p kb_lib -> 460 passed / 0 failed
cargo clippy -p kb_lib --all-targets -- -D warnings -> OK
```
## Replay
Recommended replay settings used:
```text
skipDexDecode=no
forceDexDecode=yes
deferInstructionObservations=yes
```
Final replay summary:
```text
769 replayed
0 decode skipped
769 ledger upserts
646 unsafe ledger rows
106 trades
664 liquidity
1107 lifecycle
0 tokenAccount
424 candle upserts
instructionObservations = 8062
resetDeleted = 9898
catalog = 169 tokens / 218 pools / 218 pairs
```
## Final SQL gates
| Gate | Result |
|---|---:|
| Upstream fallback for local DLMM coverage | empty |
| Local `instruction_audit` observed | `0` |
| Decoded DLMM without coverage | empty |
| Successful non-materialized without explicit skip/policy | empty |
| Failed transaction business materialization | empty |
| Multi-target materialization | empty |
| Non-swap trade/candle safety | empty |
| Fee parent scalar without amount leg | empty |
| Orphan fee amount legs | empty |
| Reward/fee collision | clean separation |
| Limit/orderbook trade/candle double-count | empty |
| Logical duplicate coverage rows | empty |
## Final materialization counters
```text
trade events = 106
liquidity events = 664
lifecycle events = 1107
candles = 424
```
## Important decisions
- `swap_event` and `swap2_evt` are materialized as lifecycle `swap_log`, not as trades.
- `initialize_preset_parameter_v2` was added from corpus evidence for discriminator `75c73e67068e1fcb`.
- `close_bin_array` has `16` observations but only `14` materializations because `2` transactions failed with `Custom 6015`.
- `initialize_reward` and `initialize_reward_event` remain without amounts because they are configuration/init events, not transfers.
- No observed useful DLMM event remains decoded-only.
## Closure decision
`0.7.57 meteora_dlmm` is considered closed for the validated corpus. Future changes require new corpus evidence.

View File

@@ -0,0 +1,245 @@
<!-- file: docs/prompts/prompt_name_0_7_58_demo4_program_surface_discovery.md -->
# Prompt — 0.7.58 Demo4 Program Surface Discovery
## Contexte
Nous reprenons le workspace Rust/Tauri `khadhroony-bobobot` après la clôture de `0.7.57 meteora_dlmm`.
État validé :
```text
0.7.57 meteora_dlmm clos
cargo test -p kb_lib -> 460 passed
cargo clippy -p kb_lib --all-targets -- -D warnings -> OK
replay -> 769 replayed, 106 trades, 664 liquidity, 1107 lifecycle, 424 candles
instructionObservations = 8062
catalogue = 169 tokens / 218 pools / 218 pairs
```
Contraintes projet à respecter :
- Rust 2024.
- Code async-first.
- Pas de `?`, `unwrap`, `expect` dans le code applicatif.
- Pas de `anyhow`, pas de `thiserror`.
- Pas de `mod.rs`.
- Pas de `pub mod` ; utiliser `mod` privé + `pub use`.
- Imports uniquement pour les traits quand cest possible.
- `#![deny(unreachable_pub)]` et `#![warn(missing_docs)]` doivent rester propres.
- Tests offline.
- Les nouvelles requêtes DB doivent mettre à jour les re-exports dans `kb_lib/src/db.rs` puis `kb_lib/src/lib.rs`.
- Les structs DB doivent aller dans `kb_lib/src/db/entities/*.rs` et `kb_lib/src/db/dtos/*.rs`, pas dans les fichiers de requêtes.
## Objectif
Ajouter `Demo4` dans `kb_demo_app` et les fonctions de lecture associées dans `kb_lib` pour afficher les surfaces de programmes non encore prises en compte par le système.
Cette fonctionnalité est strictement un cockpit de découverte et de revue.
Elle ne doit pas :
- matérialiser automatiquement des contenus inconnus ;
- promouvoir automatiquement des discriminators inconnus dans les décodeurs officiels ;
- marquer automatiquement un `program_id` comme supporté ;
- créer automatiquement des lignes `trade`, `candle`, `fee`, `liquidity`, `reward`, `admin` ou `orderbook` depuis un contenu inconnu.
Elle doit :
- afficher les surfaces inconnues ou non supportées ;
- les regrouper de manière exploitable ;
- montrer les preuves et signatures samples ;
- aider à produire des prompts/patchs futurs validés manuellement.
## Comportement attendu côté utilisateur
Créer une nouvelle page ou fenêtre, selon le pattern existant :
```text
kb_demo_app/src/demo4.html
kb_demo_app/src/demo4.ts
```
ou léquivalent si lapplication utilise un autre routage.
Demo4 doit proposer des vues pour :
1. discriminators inconnus sur des decoders connus ;
2. `program_id` connus contenant des instructions non mappées par le decoder spécialisé ;
3. `upstream_git.instruction_match` groupés par `upstreamDecoderCode`, `upstreamEntryName`, `upstreamDiscriminatorHex` ;
4. `program_id` observés dans les transactions mais absents de la matrice de support ;
5. logs Anchor `Program log: Instruction: ...` non mappés localement ;
6. events Anchor `Program data:` non mappés localement ;
7. layouts de comptes récurrents sur instructions inconnues ;
8. répartition success/failed pour chaque surface candidate.
Filtres souhaités :
```text
program_id
candidate_decoder_code
discriminator_hex
instruction_name / nom log Anchor
upstream decoder
upstream entry name
success only / failed only / both
minimum observation count
minimum tx count
from slot / to slot
signature contains
show only unknown
show only upstream fallback
show only high-confidence candidates
```
Colonnes souhaitées :
```text
candidate_kind
program_id
candidate_decoder
known/unknown status
discriminator_hex
log_instruction_name
anchor_event_discriminator
account_count
layout_hash
observed_count
tx_count
success_count
failed_count
first_slot
last_slot
sample_signature
confidence_score
confidence_reason
status
```
Le panneau de détail doit afficher :
```text
sample signatures
accounts_json
data_json / data prefix
logs de transaction
inner instructions
parsed_json si disponible
decoded_payload_json si disponible
entrées upstream registry correspondantes
preuve de hash Anchor si un nom dinstruction est disponible
```
## Design `kb_lib`
Ajouter des requêtes read-only et des DTOs. Fichiers suggérés :
```text
kb_lib/src/program_surface_discovery.rs
kb_lib/src/db/dtos/program_surface_candidate_summary.rs
kb_lib/src/db/dtos/program_surface_candidate_sample.rs
kb_lib/src/db/queries/program_surface_discovery.rs
```
Si un statut persistant est nécessaire, ajouter :
```text
kb_lib/src/db/entities/program_surface_candidate.rs
kb_lib/src/db/queries/program_surface_candidates.rs
```
Ne pas placer les DTO/entities dans les fichiers de requêtes.
Statuts candidats suggérés :
```text
new
reviewed
accepted
rejected
promoted
ignored
```
Si une table persistante est ajoutée, la migration doit être explicite, idempotente et testée.
## Scoring de découverte
Le scoring est indicatif uniquement. Il ne doit jamais déclencher une matérialisation.
Signaux de confiance possibles :
| Signal | Effet |
|---|---:|
| `program_id` connu, discriminator inconnu | +20 |
| log Anchor `Instruction: X` trouvé | +30 |
| `sha256("global:x")` correspond au discriminator | +50 |
| observations success répétées | +10 |
| account count/layout stable | +10 |
| inner instruction family cohérente avec laction | +10 |
| uniquement des transactions failed | -20 |
| nom très générique comme `swap`, `claim`, `initialize` sans autre preuve | -15 |
| `program_id` inconnu et observation unique | -25 |
## Helper Anchor
Ajouter un helper qui normalise les noms dinstructions Anchor depuis les logs :
```text
Program log: Instruction: InitializePresetParameterV2
-> initialize_preset_parameter_v2
-> sha256("global:initialize_preset_parameter_v2")[0..8]
```
Ce helper doit seulement produire une preuve/candidate record. Il ne doit pas modifier les mappings de decoder.
## Export Markdown
Demo4 doit permettre dexporter un groupe candidat en Markdown pour une future session dimplémentation.
Lexport doit inclure :
```text
candidate decoder
program_id
discriminator_hex
candidate name
confidence score
preuves/evidence
sample signatures
account layout
logs
inner instruction summary
proposed target table éventuelle
warning explicite : non implémenté, non matérialisé
```
## Tests
Ajouter des tests offline pour :
- normalisation des noms Anchor depuis logs ;
- calcul de discriminators Anchor pour des noms samples ;
- groupement par program/discriminator/layout ;
- ordre de score ;
- sérialisation/désérialisation des DTOs si utilisés ;
- validation des paramètres de commandes UI.
Aucun test ne doit dépendre de RPC ou du réseau.
## Validation
```bash
cargo fmt
cargo test -p kb_lib program_surface
cargo test -p kb_lib
cargo clippy -p kb_lib --all-targets -- -D warnings
```
Pour Tauri/frontend, lancer la commande de build/test existante du projet si elle existe.
## Résultat attendu
Lutilisateur peut ouvrir Demo4 et voir immédiatement les surfaces unsupported/upstream/unknown présentes dans la base SQLite courante, sans rescanner la blockchain.
Demo4 est un cockpit de découverte uniquement. Il ne change pas la matérialisation métier.

View File

@@ -0,0 +1,245 @@
<!-- file: docs/prompts/PROMPT_0_7_58_demo4_program_surface_discovery.md -->
# Prompt — 0.7.58 Demo4 Program Surface Discovery
## Contexte
Nous reprenons le workspace Rust/Tauri `khadhroony-bobobot` après la clôture de `0.7.57 meteora_dlmm`.
État validé :
```text
0.7.57 meteora_dlmm clos
cargo test -p kb_lib -> 460 passed
cargo clippy -p kb_lib --all-targets -- -D warnings -> OK
replay -> 769 replayed, 106 trades, 664 liquidity, 1107 lifecycle, 424 candles
instructionObservations = 8062
catalogue = 169 tokens / 218 pools / 218 pairs
```
Contraintes projet à respecter :
- Rust 2024.
- Code async-first.
- Pas de `?`, `unwrap`, `expect` dans le code applicatif.
- Pas de `anyhow`, pas de `thiserror`.
- Pas de `mod.rs`.
- Pas de `pub mod` ; utiliser `mod` privé + `pub use`.
- Imports uniquement pour les traits quand cest possible.
- `#![deny(unreachable_pub)]` et `#![warn(missing_docs)]` doivent rester propres.
- Tests offline.
- Les nouvelles requêtes DB doivent mettre à jour les re-exports dans `kb_lib/src/db.rs` puis `kb_lib/src/lib.rs`.
- Les structs DB doivent aller dans `kb_lib/src/db/entities/*.rs` et `kb_lib/src/db/dtos/*.rs`, pas dans les fichiers de requêtes.
## Objectif
Ajouter `Demo4` dans `kb_demo_app` et les fonctions de lecture associées dans `kb_lib` pour afficher les surfaces de programmes non encore prises en compte par le système.
Cette fonctionnalité est strictement un cockpit de découverte et de revue.
Elle ne doit pas :
- matérialiser automatiquement des contenus inconnus ;
- promouvoir automatiquement des discriminators inconnus dans les décodeurs officiels ;
- marquer automatiquement un `program_id` comme supporté ;
- créer automatiquement des lignes `trade`, `candle`, `fee`, `liquidity`, `reward`, `admin` ou `orderbook` depuis un contenu inconnu.
Elle doit :
- afficher les surfaces inconnues ou non supportées ;
- les regrouper de manière exploitable ;
- montrer les preuves et signatures samples ;
- aider à produire des prompts/patchs futurs validés manuellement.
## Comportement attendu côté utilisateur
Créer une nouvelle page ou fenêtre, selon le pattern existant :
```text
kb_demo_app/src/demo4.html
kb_demo_app/src/demo4.ts
```
ou léquivalent si lapplication utilise un autre routage.
Demo4 doit proposer des vues pour :
1. discriminators inconnus sur des decoders connus ;
2. `program_id` connus contenant des instructions non mappées par le decoder spécialisé ;
3. `upstream_git.instruction_match` groupés par `upstreamDecoderCode`, `upstreamEntryName`, `upstreamDiscriminatorHex` ;
4. `program_id` observés dans les transactions mais absents de la matrice de support ;
5. logs Anchor `Program log: Instruction: ...` non mappés localement ;
6. events Anchor `Program data:` non mappés localement ;
7. layouts de comptes récurrents sur instructions inconnues ;
8. répartition success/failed pour chaque surface candidate.
Filtres souhaités :
```text
program_id
candidate_decoder_code
discriminator_hex
instruction_name / nom log Anchor
upstream decoder
upstream entry name
success only / failed only / both
minimum observation count
minimum tx count
from slot / to slot
signature contains
show only unknown
show only upstream fallback
show only high-confidence candidates
```
Colonnes souhaitées :
```text
candidate_kind
program_id
candidate_decoder
known/unknown status
discriminator_hex
log_instruction_name
anchor_event_discriminator
account_count
layout_hash
observed_count
tx_count
success_count
failed_count
first_slot
last_slot
sample_signature
confidence_score
confidence_reason
status
```
Le panneau de détail doit afficher :
```text
sample signatures
accounts_json
data_json / data prefix
logs de transaction
inner instructions
parsed_json si disponible
decoded_payload_json si disponible
entrées upstream registry correspondantes
preuve de hash Anchor si un nom dinstruction est disponible
```
## Design `kb_lib`
Ajouter des requêtes read-only et des DTOs. Fichiers suggérés :
```text
kb_lib/src/program_surface_discovery.rs
kb_lib/src/db/dtos/program_surface_candidate_summary.rs
kb_lib/src/db/dtos/program_surface_candidate_sample.rs
kb_lib/src/db/queries/program_surface_discovery.rs
```
Si un statut persistant est nécessaire, ajouter :
```text
kb_lib/src/db/entities/program_surface_candidate.rs
kb_lib/src/db/queries/program_surface_candidates.rs
```
Ne pas placer les DTO/entities dans les fichiers de requêtes.
Statuts candidats suggérés :
```text
new
reviewed
accepted
rejected
promoted
ignored
```
Si une table persistante est ajoutée, la migration doit être explicite, idempotente et testée.
## Scoring de découverte
Le scoring est indicatif uniquement. Il ne doit jamais déclencher une matérialisation.
Signaux de confiance possibles :
| Signal | Effet |
|---|---:|
| `program_id` connu, discriminator inconnu | +20 |
| log Anchor `Instruction: X` trouvé | +30 |
| `sha256("global:x")` correspond au discriminator | +50 |
| observations success répétées | +10 |
| account count/layout stable | +10 |
| inner instruction family cohérente avec laction | +10 |
| uniquement des transactions failed | -20 |
| nom très générique comme `swap`, `claim`, `initialize` sans autre preuve | -15 |
| `program_id` inconnu et observation unique | -25 |
## Helper Anchor
Ajouter un helper qui normalise les noms dinstructions Anchor depuis les logs :
```text
Program log: Instruction: InitializePresetParameterV2
-> initialize_preset_parameter_v2
-> sha256("global:initialize_preset_parameter_v2")[0..8]
```
Ce helper doit seulement produire une preuve/candidate record. Il ne doit pas modifier les mappings de decoder.
## Export Markdown
Demo4 doit permettre dexporter un groupe candidat en Markdown pour une future session dimplémentation.
Lexport doit inclure :
```text
candidate decoder
program_id
discriminator_hex
candidate name
confidence score
preuves/evidence
sample signatures
account layout
logs
inner instruction summary
proposed target table éventuelle
warning explicite : non implémenté, non matérialisé
```
## Tests
Ajouter des tests offline pour :
- normalisation des noms Anchor depuis logs ;
- calcul de discriminators Anchor pour des noms samples ;
- groupement par program/discriminator/layout ;
- ordre de score ;
- sérialisation/désérialisation des DTOs si utilisés ;
- validation des paramètres de commandes UI.
Aucun test ne doit dépendre de RPC ou du réseau.
## Validation
```bash
cargo fmt
cargo test -p kb_lib program_surface
cargo test -p kb_lib
cargo clippy -p kb_lib --all-targets -- -D warnings
```
Pour Tauri/frontend, lancer la commande de build/test existante du projet si elle existe.
## Résultat attendu
Lutilisateur peut ouvrir Demo4 et voir immédiatement les surfaces unsupported/upstream/unknown présentes dans la base SQLite courante, sans rescanner la blockchain.
Demo4 est un cockpit de découverte uniquement. Il ne change pas la matérialisation métier.

View File

@@ -0,0 +1,249 @@
<!-- file: docs/prompts/prompt_name_0_7_59_sqlite_db_transaction_merger_binary.md -->
# Prompt — 0.7.59 Binaire de fusion de bases SQLite transactionnelles
## Contexte
Nous reprenons le workspace Rust `khadhroony-bobobot` après la clôture de `0.7.57 meteora_dlmm` et après/ou en parallèle de `0.7.58 demo4_program_surface_discovery`.
Lutilisateur possède plusieurs bases SQLite dédiées construites pendant les tranches Raydium, Pump et Meteora. Lobjectif est déviter de rescanner/backfiller des signatures déjà présentes dans ces bases.
## Objectif
Ajouter un module binaire qui utilise `kb_lib` pour fusionner des données de corpus transactionnel depuis plusieurs fichiers SQLite `.db` vers une base SQLite de sortie unique.
Le merger doit copier suffisamment de données brutes transaction/instruction pour permettre un replay local dans la base consolidée, sans refaire les appels RPC.
Ce binaire est un outil de consolidation de corpus. Ce nest pas un scanner blockchain.
## Forme recommandée
Créer un nouveau package workspace :
```text
kb_tools/
Cargo.toml
src/bin/kb_db_merge.rs
```
Le binaire doit dépendre de `kb_lib` et réutiliser autant que possible les fonctions de schéma/setup/query existantes.
Alternative acceptable si le workspace préfère ce pattern :
```text
kb_lib/src/bin/kb_db_merge.rs
```
Préférer `kb_tools` si cela évite de mélanger du code CLI-only dans `kb_lib`.
## Interface CLI
Commande suggérée :
```bash
cargo run -p kb_tools --bin kb_db_merge -- \
--output ./merged_corpus.db \
--input ./raydium.db \
--input ./pump.db \
--input ./meteora.db \
--mode raw-corpus \
--dry-run
```
Options requises ou utiles :
```text
--output <path>
--input <path> répétable
--mode <mode>
--dry-run optionnel
--replace-output optionnel ; sinon refuser si output existe
--source-label optionnel ou inféré depuis le nom de fichier
--limit optionnel pour tests
```
Modes initiaux :
```text
raw-corpus
full-copy-safe
signatures-only
```
## Mode `raw-corpus`
Mode par défaut recommandé.
Copier les données brutes canonique nécessaires au replay local sans RPC :
```text
k_sol_chain_transactions
k_sol_chain_instructions
```
Si le schéma contient dautres tables brutes/contextuelles strictement nécessaires, les inspecter avant de les ajouter. Ne copier que les tables sûres, sans duplication sémantique.
La base output doit ensuite pouvoir exécuter le replay/décodage local depuis les transactions persistées.
## Mode `full-copy-safe`
Mode optionnel pour plus tard.
Copier aussi des lignes décodées/matérialisées seulement si les foreign keys, versions de decoder et versions de schéma sont cohérentes.
Ne pas limplémenter comme mode par défaut. Il est plus dangereux car les lignes matérialisées dépendent de la version du code.
## Mode `signatures-only`
Copier uniquement les signatures/slots dans une table de staging afin quun outil futur décide quoi importer/rejouer.
## Politique de déduplication
Identité primaire :
```text
signature
```
Règles :
- Une même signature dans plusieurs inputs doit produire une seule transaction dans loutput.
- Si `meta_json`, `transaction_json`, `slot` ou `err_json` diffèrent pour une même signature, enregistrer un conflit ; ne pas écraser silencieusement.
- Préférer la ligne brute la plus complète uniquement si le conflit est explicable et journalisé.
- Conserver la provenance dans des tables daudit de merge.
Tables suggérées :
```text
k_sol_db_merge_sources
k_sol_db_merge_transactions
k_sol_db_merge_conflicts
```
Champs source suggérés :
```text
source_id
source_path
source_label
source_schema_version
source_created_at
input_transaction_count
copied_transaction_count
skipped_duplicate_count
conflict_count
```
Pour chaque signature copiée :
```text
signature
slot
source_id
source_transaction_id
copied_at
copy_status
conflict_status
```
## Règles de sécurité
- Ne pas appeler RPC.
- Ne pas backfiller.
- Ne pas décoder DEX pendant le merge.
- Ne pas matérialiser trades/candles pendant le merge.
- Ne pas supposer une compatibilité de schéma sans inspection.
- Ne pas utiliser `ATTACH` avec des chemins non validés/échappés.
- Pas de `?`, `unwrap`, `expect` dans le code applicatif.
- Les erreurs doivent être explicites, typées projet, et lisibles.
- Le mode dry-run doit indiquer exactement ce qui serait copié, ignoré ou marqué conflictuel.
## Compatibilité de schéma
Le binaire doit inspecter `sqlite_master` et `PRAGMA table_info(...)` dans chaque DB input.
Tables requises pour `raw-corpus` :
```text
k_sol_chain_transactions
k_sol_chain_instructions
```
Vérifier les colonnes requises par nom. Si une colonne optionnelle manque, lindiquer clairement et continuer seulement si la copie reste suffisante pour un replay local fiable.
Si le schéma est incompatible, échouer proprement avec un résumé court.
## Stratégie de copie
Algorithme recommandé :
1. Ouvrir/créer la base output et initialiser le schéma courant via `kb_lib`.
2. Pour chaque input DB :
- inspecter le schéma ;
- enregistrer la source ;
- itérer les transactions par slot/signature ;
- si la signature est absente de loutput, insérer la transaction et ses instructions enfants ;
- si la signature existe, comparer les champs canoniques et ignorer ou enregistrer un conflit ;
- préserver le mapping `source_transaction_id -> output_transaction_id` pour les instructions.
3. Commit par batches.
4. Imprimer un résumé final.
La taille de batch doit être configurable ou conservatrice.
## SQL de validation
Ajouter :
```text
validation_sql/SQL_VALIDATION_DB_MERGE_0_7_59.sql
```
Checks minimaux :
```sql
-- duplicate signatures in output should be empty
SELECT signature, COUNT(*)
FROM k_sol_chain_transactions
GROUP BY signature
HAVING COUNT(*) > 1;
-- orphan instructions should be empty
SELECT ins.id
FROM k_sol_chain_instructions ins
LEFT JOIN k_sol_chain_transactions tx ON tx.id = ins.transaction_id
WHERE tx.id IS NULL;
-- conflicts summary
SELECT *
FROM k_sol_db_merge_conflicts
ORDER BY id DESC
LIMIT 100;
```
## Tests
Ajouter des tests offline avec bases SQLite temporaires :
1. merge dune DB vers un output vide ;
2. merge de deux DBs avec signatures disjointes ;
3. merge de deux DBs avec signature identique et contenu identique ;
4. signature dupliquée avec conflit slot/meta et ligne conflict enregistrée ;
5. instructions recopiées avec le nouveau `transaction_id` output ;
6. dry-run sans écriture des lignes output ;
7. table requise manquante -> erreur propre.
## Documentation
Mettre à jour :
```text
README.md
ROADMAP.md
CHANGELOG.md
validation_sql/SQL_VALIDATION_DB_MERGE_0_7_59.sql
docs/reports/SQLITE_DB_TRANSACTION_MERGER_0_7_59.md
```
## Résultat attendu
Lutilisateur peut consolider ses anciennes bases Raydium/Pump/Meteora dans une base output et relancer un replay local depuis les lignes brutes existantes, sans refaire les backfills RPC de signatures.

View File

@@ -0,0 +1,249 @@
<!-- file: docs/prompts/PROMPT_0_7_59_sqlite_db_transaction_merger_binary.md -->
# Prompt — 0.7.59 Binaire de fusion de bases SQLite transactionnelles
## Contexte
Nous reprenons le workspace Rust `khadhroony-bobobot` après la clôture de `0.7.57 meteora_dlmm` et après/ou en parallèle de `0.7.58 demo4_program_surface_discovery`.
Lutilisateur possède plusieurs bases SQLite dédiées construites pendant les tranches Raydium, Pump et Meteora. Lobjectif est déviter de rescanner/backfiller des signatures déjà présentes dans ces bases.
## Objectif
Ajouter un module binaire qui utilise `kb_lib` pour fusionner des données de corpus transactionnel depuis plusieurs fichiers SQLite `.db` vers une base SQLite de sortie unique.
Le merger doit copier suffisamment de données brutes transaction/instruction pour permettre un replay local dans la base consolidée, sans refaire les appels RPC.
Ce binaire est un outil de consolidation de corpus. Ce nest pas un scanner blockchain.
## Forme recommandée
Créer un nouveau package workspace :
```text
kb_tools/
Cargo.toml
src/bin/kb_db_merge.rs
```
Le binaire doit dépendre de `kb_lib` et réutiliser autant que possible les fonctions de schéma/setup/query existantes.
Alternative acceptable si le workspace préfère ce pattern :
```text
kb_lib/src/bin/kb_db_merge.rs
```
Préférer `kb_tools` si cela évite de mélanger du code CLI-only dans `kb_lib`.
## Interface CLI
Commande suggérée :
```bash
cargo run -p kb_tools --bin kb_db_merge -- \
--output ./merged_corpus.db \
--input ./raydium.db \
--input ./pump.db \
--input ./meteora.db \
--mode raw-corpus \
--dry-run
```
Options requises ou utiles :
```text
--output <path>
--input <path> répétable
--mode <mode>
--dry-run optionnel
--replace-output optionnel ; sinon refuser si output existe
--source-label optionnel ou inféré depuis le nom de fichier
--limit optionnel pour tests
```
Modes initiaux :
```text
raw-corpus
full-copy-safe
signatures-only
```
## Mode `raw-corpus`
Mode par défaut recommandé.
Copier les données brutes canonique nécessaires au replay local sans RPC :
```text
k_sol_chain_transactions
k_sol_chain_instructions
```
Si le schéma contient dautres tables brutes/contextuelles strictement nécessaires, les inspecter avant de les ajouter. Ne copier que les tables sûres, sans duplication sémantique.
La base output doit ensuite pouvoir exécuter le replay/décodage local depuis les transactions persistées.
## Mode `full-copy-safe`
Mode optionnel pour plus tard.
Copier aussi des lignes décodées/matérialisées seulement si les foreign keys, versions de decoder et versions de schéma sont cohérentes.
Ne pas limplémenter comme mode par défaut. Il est plus dangereux car les lignes matérialisées dépendent de la version du code.
## Mode `signatures-only`
Copier uniquement les signatures/slots dans une table de staging afin quun outil futur décide quoi importer/rejouer.
## Politique de déduplication
Identité primaire :
```text
signature
```
Règles :
- Une même signature dans plusieurs inputs doit produire une seule transaction dans loutput.
- Si `meta_json`, `transaction_json`, `slot` ou `err_json` diffèrent pour une même signature, enregistrer un conflit ; ne pas écraser silencieusement.
- Préférer la ligne brute la plus complète uniquement si le conflit est explicable et journalisé.
- Conserver la provenance dans des tables daudit de merge.
Tables suggérées :
```text
k_sol_db_merge_sources
k_sol_db_merge_transactions
k_sol_db_merge_conflicts
```
Champs source suggérés :
```text
source_id
source_path
source_label
source_schema_version
source_created_at
input_transaction_count
copied_transaction_count
skipped_duplicate_count
conflict_count
```
Pour chaque signature copiée :
```text
signature
slot
source_id
source_transaction_id
copied_at
copy_status
conflict_status
```
## Règles de sécurité
- Ne pas appeler RPC.
- Ne pas backfiller.
- Ne pas décoder DEX pendant le merge.
- Ne pas matérialiser trades/candles pendant le merge.
- Ne pas supposer une compatibilité de schéma sans inspection.
- Ne pas utiliser `ATTACH` avec des chemins non validés/échappés.
- Pas de `?`, `unwrap`, `expect` dans le code applicatif.
- Les erreurs doivent être explicites, typées projet, et lisibles.
- Le mode dry-run doit indiquer exactement ce qui serait copié, ignoré ou marqué conflictuel.
## Compatibilité de schéma
Le binaire doit inspecter `sqlite_master` et `PRAGMA table_info(...)` dans chaque DB input.
Tables requises pour `raw-corpus` :
```text
k_sol_chain_transactions
k_sol_chain_instructions
```
Vérifier les colonnes requises par nom. Si une colonne optionnelle manque, lindiquer clairement et continuer seulement si la copie reste suffisante pour un replay local fiable.
Si le schéma est incompatible, échouer proprement avec un résumé court.
## Stratégie de copie
Algorithme recommandé :
1. Ouvrir/créer la base output et initialiser le schéma courant via `kb_lib`.
2. Pour chaque input DB :
- inspecter le schéma ;
- enregistrer la source ;
- itérer les transactions par slot/signature ;
- si la signature est absente de loutput, insérer la transaction et ses instructions enfants ;
- si la signature existe, comparer les champs canoniques et ignorer ou enregistrer un conflit ;
- préserver le mapping `source_transaction_id -> output_transaction_id` pour les instructions.
3. Commit par batches.
4. Imprimer un résumé final.
La taille de batch doit être configurable ou conservatrice.
## SQL de validation
Ajouter :
```text
validation_sql/SQL_VALIDATION_DB_MERGE_0_7_59.sql
```
Checks minimaux :
```sql
-- duplicate signatures in output should be empty
SELECT signature, COUNT(*)
FROM k_sol_chain_transactions
GROUP BY signature
HAVING COUNT(*) > 1;
-- orphan instructions should be empty
SELECT ins.id
FROM k_sol_chain_instructions ins
LEFT JOIN k_sol_chain_transactions tx ON tx.id = ins.transaction_id
WHERE tx.id IS NULL;
-- conflicts summary
SELECT *
FROM k_sol_db_merge_conflicts
ORDER BY id DESC
LIMIT 100;
```
## Tests
Ajouter des tests offline avec bases SQLite temporaires :
1. merge dune DB vers un output vide ;
2. merge de deux DBs avec signatures disjointes ;
3. merge de deux DBs avec signature identique et contenu identique ;
4. signature dupliquée avec conflit slot/meta et ligne conflict enregistrée ;
5. instructions recopiées avec le nouveau `transaction_id` output ;
6. dry-run sans écriture des lignes output ;
7. table requise manquante -> erreur propre.
## Documentation
Mettre à jour :
```text
README.md
ROADMAP.md
CHANGELOG.md
validation_sql/SQL_VALIDATION_DB_MERGE_0_7_59.sql
docs/reports/SQLITE_DB_TRANSACTION_MERGER_0_7_59.md
```
## Résultat attendu
Lutilisateur peut consolider ses anciennes bases Raydium/Pump/Meteora dans une base output et relancer un replay local depuis les lignes brutes existantes, sans refaire les backfills RPC de signatures.

View File

@@ -0,0 +1,198 @@
<!-- file: docs/prompts/prompt_name_0_7_60_meteora_damm_next_dex.md -->
# Prompt — 0.7.60 Meteora DAMM v1 puis DAMM v2
## Décision
Ne pas fusionner Meteora DAMM v1 et Meteora DAMM v2 dans une seule tranche dimplémentation.
Utiliser des versions séparées :
```text
0.7.60 -> meteora_damm_v1
0.7.61 -> meteora_damm_v2
```
Raison :
- `program_id` différents ;
- surfaces IDL/discriminators différentes ;
- corpus historiques et besoins de validation différents ;
- clôture SQL plus lisible ;
- risque réduit de faux positifs cross-version ;
- rollback plus simple si une version contient une hypothèse incorrecte.
Partage autorisé :
- helpers communs privés ;
- patterns communs de récupération fee/reward amounts ;
- conventions communes de coverage/tests ;
- structure commune de rapport ;
- template SQL commun.
Ne pas partager la classification de decoder dune manière qui masque les discriminators version-spécifiques ou les layouts propres à chaque programme.
## Contexte
`0.7.57 meteora_dlmm` est clos. Les tranches intermédiaires planifiées sont :
```text
0.7.58 -> demo4_program_surface_discovery
0.7.59 -> sqlite_db_transaction_merger
```
La prochaine tranche DEX doit reprendre la famille Meteora après DLMM/DBC, en commençant par DAMM v1.
Surfaces Meteora connues :
```text
meteora_dbc -> clos 0.7.56
meteora_dlmm -> clos 0.7.57
meteora_damm_v1 -> 0.7.60
meteora_damm_v2 -> 0.7.61
meteora_vault -> plus tard, surface séparée
```
## Cible 0.7.60 — meteora_damm_v1
Program id depuis la roadmap :
```text
Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB
```
Objectif principal :
```text
Full decode + full materialization pour toutes les instructions/events DAMM v1 disponibles depuis les IDL locales et les sources upstream.
```
Familles attendues :
```text
swap
pool_create
add_liquidity
remove_liquidity
lock/unlock liquidity si présent
fee/admin/config
lifecycle/migration si présent
```
Règles :
- Pas de decoded-only pour une entrée utile observée si elle peut être matérialisée sûrement.
- Ne pas créer de trade/candle depuis des non-swaps.
- Ne pas matérialiser les transactions failed.
- Ne pas agréger artificiellement les fees multi-mint.
- Utiliser `k_sol_fee_event_amounts` pour les legs fee fiables.
- Utiliser la récupération inner SPL transfer seulement avec allowlist explicite et tests.
- Conserver toutes les entrées IDL/upstream non observées dans la coverage avec `0/0` et proof status clair.
## Sources à vérifier
Inspecter dabord le répertoire local :
```text
idls/
```
Puis comparer avec :
```text
Carbon decoders
Pinax/substreams-solana-idls
0xfnzero sol-parser-sdk / solana-streamer
ancien code local et rapports historiques du projet
samples Solscan / backfills Demo3
```
Ne pas considérer les anciens résultats `0.7.36` ou `0.7.46` comme clôture finale. Revalider depuis le schéma courant et les règles actuelles de matérialisation.
## Plan de travail
1. Créer ou rafraîchir les entrées coverage `meteora_damm_v1`.
2. Vérifier le `program_id` et la surface IDL locale.
3. Étendre lenum/local classifier des discriminators.
4. Ajouter le naming dans `instruction_observation_index`.
5. Décoder toutes les instructions et tous les events Anchor disponibles.
6. Matérialiser swaps, liquidity, lifecycle, fee, reward/admin/orderbook si applicable.
7. Ajouter des tests synthétiques pour chaque instruction/event IDL, y compris non observé.
8. Ajouter le SQL de validation.
9. Rejouer sur base fraîche avec :
```text
skipDexDecode=no
forceDexDecode=yes
deferInstructionObservations=yes
```
10. Clore seulement si les gates SQL sont propres.
## Gates SQL obligatoires
Reprendre le pattern DLMM :
```text
fallback upstream pour entrées localement couvertes -> vide
instruction_name null/blank -> vide
decoded sans coverage -> vide
successful non-materialized without skip/policy -> vide
failed tx materialization -> vide
multi-target materialization -> vide
non-swap trade/candle -> vide
fee scalar parent without leg -> vide
orphan fee legs -> vide
coverage logical duplicates -> vide
watchlist sans backlog meteora_damm_v1
```
## Cible 0.7.61 — meteora_damm_v2
Program id depuis la roadmap :
```text
cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG
```
Nouvrir cette tranche quaprès clôture DAMM v1.
La tranche v2 peut réutiliser le template et des helpers privés, mais doit conserver ses propres éléments :
```text
decoder code
coverage entries
instruction enum
event mapping
synthetic tests
SQL validation
rapport
```
## Livrables attendus pour 0.7.60
Fichiers probables :
```text
kb_lib/src/dex/meteora_damm_v1.rs
kb_lib/src/dex_event_coverage.rs
kb_lib/src/dex_event_classification.rs
kb_lib/src/instruction_observation_index.rs
kb_lib/src/non_trade_event_materialization.rs
validation_sql/SQL_VALIDATION_METEORA_DAMM_V1_0_7_60.sql
docs/reports/METEORA_DAMM_V1_EVENT_COVERAGE_REPORT.md
```
Mettre à jour les re-exports si des modules ou requêtes sont ajoutés :
```text
kb_lib/src/dex.rs
kb_lib/src/lib.rs
kb_lib/src/db.rs
```
## Note finale
Si la découverte corpus montre que DAMM v1 et v2 partagent une logique basse-niveau précise, créer des helpers privés communs.
Ne pas fusionner les deux protocoles dans un seul decoder, une seule coverage ou un seul rapport de clôture.

View File

@@ -0,0 +1,198 @@
<!-- file: docs/prompts/PROMPT_0_7_60_meteora_damm_next_dex.md -->
# Prompt — 0.7.60 Meteora DAMM v1 puis DAMM v2
## Décision
Ne pas fusionner Meteora DAMM v1 et Meteora DAMM v2 dans une seule tranche dimplémentation.
Utiliser des versions séparées :
```text
0.7.60 -> meteora_damm_v1
0.7.61 -> meteora_damm_v2
```
Raison :
- `program_id` différents ;
- surfaces IDL/discriminators différentes ;
- corpus historiques et besoins de validation différents ;
- clôture SQL plus lisible ;
- risque réduit de faux positifs cross-version ;
- rollback plus simple si une version contient une hypothèse incorrecte.
Partage autorisé :
- helpers communs privés ;
- patterns communs de récupération fee/reward amounts ;
- conventions communes de coverage/tests ;
- structure commune de rapport ;
- template SQL commun.
Ne pas partager la classification de decoder dune manière qui masque les discriminators version-spécifiques ou les layouts propres à chaque programme.
## Contexte
`0.7.57 meteora_dlmm` est clos. Les tranches intermédiaires planifiées sont :
```text
0.7.58 -> demo4_program_surface_discovery
0.7.59 -> sqlite_db_transaction_merger
```
La prochaine tranche DEX doit reprendre la famille Meteora après DLMM/DBC, en commençant par DAMM v1.
Surfaces Meteora connues :
```text
meteora_dbc -> clos 0.7.56
meteora_dlmm -> clos 0.7.57
meteora_damm_v1 -> 0.7.60
meteora_damm_v2 -> 0.7.61
meteora_vault -> plus tard, surface séparée
```
## Cible 0.7.60 — meteora_damm_v1
Program id depuis la roadmap :
```text
Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB
```
Objectif principal :
```text
Full decode + full materialization pour toutes les instructions/events DAMM v1 disponibles depuis les IDL locales et les sources upstream.
```
Familles attendues :
```text
swap
pool_create
add_liquidity
remove_liquidity
lock/unlock liquidity si présent
fee/admin/config
lifecycle/migration si présent
```
Règles :
- Pas de decoded-only pour une entrée utile observée si elle peut être matérialisée sûrement.
- Ne pas créer de trade/candle depuis des non-swaps.
- Ne pas matérialiser les transactions failed.
- Ne pas agréger artificiellement les fees multi-mint.
- Utiliser `k_sol_fee_event_amounts` pour les legs fee fiables.
- Utiliser la récupération inner SPL transfer seulement avec allowlist explicite et tests.
- Conserver toutes les entrées IDL/upstream non observées dans la coverage avec `0/0` et proof status clair.
## Sources à vérifier
Inspecter dabord le répertoire local :
```text
idls/
```
Puis comparer avec :
```text
Carbon decoders
Pinax/substreams-solana-idls
0xfnzero sol-parser-sdk / solana-streamer
ancien code local et rapports historiques du projet
samples Solscan / backfills Demo3
```
Ne pas considérer les anciens résultats `0.7.36` ou `0.7.46` comme clôture finale. Revalider depuis le schéma courant et les règles actuelles de matérialisation.
## Plan de travail
1. Créer ou rafraîchir les entrées coverage `meteora_damm_v1`.
2. Vérifier le `program_id` et la surface IDL locale.
3. Étendre lenum/local classifier des discriminators.
4. Ajouter le naming dans `instruction_observation_index`.
5. Décoder toutes les instructions et tous les events Anchor disponibles.
6. Matérialiser swaps, liquidity, lifecycle, fee, reward/admin/orderbook si applicable.
7. Ajouter des tests synthétiques pour chaque instruction/event IDL, y compris non observé.
8. Ajouter le SQL de validation.
9. Rejouer sur base fraîche avec :
```text
skipDexDecode=no
forceDexDecode=yes
deferInstructionObservations=yes
```
10. Clore seulement si les gates SQL sont propres.
## Gates SQL obligatoires
Reprendre le pattern DLMM :
```text
fallback upstream pour entrées localement couvertes -> vide
instruction_name null/blank -> vide
decoded sans coverage -> vide
successful non-materialized without skip/policy -> vide
failed tx materialization -> vide
multi-target materialization -> vide
non-swap trade/candle -> vide
fee scalar parent without leg -> vide
orphan fee legs -> vide
coverage logical duplicates -> vide
watchlist sans backlog meteora_damm_v1
```
## Cible 0.7.61 — meteora_damm_v2
Program id depuis la roadmap :
```text
cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG
```
Nouvrir cette tranche quaprès clôture DAMM v1.
La tranche v2 peut réutiliser le template et des helpers privés, mais doit conserver ses propres éléments :
```text
decoder code
coverage entries
instruction enum
event mapping
synthetic tests
SQL validation
rapport
```
## Livrables attendus pour 0.7.60
Fichiers probables :
```text
kb_lib/src/dex/meteora_damm_v1.rs
kb_lib/src/dex_event_coverage.rs
kb_lib/src/dex_event_classification.rs
kb_lib/src/instruction_observation_index.rs
kb_lib/src/non_trade_event_materialization.rs
validation_sql/SQL_VALIDATION_METEORA_DAMM_V1_0_7_60.sql
docs/reports/METEORA_DAMM_V1_EVENT_COVERAGE_REPORT.md
```
Mettre à jour les re-exports si des modules ou requêtes sont ajoutés :
```text
kb_lib/src/dex.rs
kb_lib/src/lib.rs
kb_lib/src/db.rs
```
## Note finale
Si la découverte corpus montre que DAMM v1 et v2 partagent une logique basse-niveau précise, créer des helpers privés communs.
Ne pas fusionner les deux protocoles dans un seul decoder, une seule coverage ou un seul rapport de clôture.

View File

@@ -0,0 +1,182 @@
# Meteora DLMM event coverage report — 0.7.57 final
## Scope
Version `0.7.57` closes `meteora_dlmm` from the local IDL and the dedicated local corpus.
Target program:
```text
LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo
```
Local IDL:
```text
idls/meteora_dlmm.LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo.json
```
Final inventoried surface:
```text
76 IDL instructions
30 Anchor events
12 accounts
```
The local corpus additionally identified discriminator `75c73e67068e1fcb` as `initialize_preset_parameter_v2`. This discriminator was not present in the local IDL under that spelling, but the transaction logs contain `Instruction: InitializePresetParameterV2`, the account layout is stable, and the instruction creates a 36-byte DLMM-owned preset parameter account through the system program.
## Final validation summary
```text
cargo test -p kb_lib -> 460 passed / 0 failed
cargo clippy -p kb_lib --all-targets -- -D warnings -> OK
769 replayed
0 decode skipped
769 ledger upserts
646 unsafe ledger rows
106 trades
664 liquidity
1107 lifecycle
0 tokenAccount
424 candle upserts
instructionObservations = 8062
resetDeleted = 9898
catalog = 169 tokens / 218 pools / 218 pairs
```
## Closure checks
All blocking checks are clean:
```text
upstream_git fallback for locally covered meteora_dlmm entries -> empty
local instruction_audit observed -> 0
DLMM decoded events without coverage -> empty
successful non-materialized DLMM without explicit skip/policy -> empty
failed transaction business materialization -> empty
multi-target materialization -> empty
non-swap trade/candle safety -> empty
fee parent scalar without fee amount leg -> empty
orphan fee amount legs -> empty
limit/orderbook trade/candle double-count -> empty
logical duplicate coverage rows -> empty
```
The only coverage difference left is explained and non-blocking:
```text
close_bin_array observed 16 / materialized 14
14 successful transactions -> lifecycle materialized
2 failed transactions Custom 6015 -> decoded/audit only, no business materialization
```
## Materialization policy
### Trades and candles
Only instruction-level DLMM swaps can produce trades/candles:
```text
meteora_dlmm.swap
meteora_dlmm.swap2
meteora_dlmm.swap_exact_out
meteora_dlmm.swap_exact_out2
meteora_dlmm.swap_with_price_impact
meteora_dlmm.swap_with_price_impact2
```
Anchor `swap_event` and `swap2_evt` are materialized as lifecycle `swap_log` rows and never as trades/candles. This prevents double-counting when an instruction swap and an Anchor log describe the same user-visible swap.
### Liquidity and lifecycle
The following families are materialized when transactions are successful and context is reliable:
```text
add_liquidity*
remove_liquidity*
remove_all_liquidity
rebalance_liquidity
initialize_bin_array*
close_bin_array
go_to_a_bin*
initialize_position*
close_position*
increase_position_length*
decrease_position_length*
update_position_operator*
lb_pair_create_event
position_create_event
position_close_event
```
Position and bin lifecycle events are routed to `k_sol_pool_lifecycle_events` when the operation is structural rather than a direct liquidity amount delta.
### Fees
Fee parents are written to `k_sol_fee_events`. Amount legs are written to `k_sol_fee_event_amounts`.
Final observed amount-leg summary:
```text
claim_fee 64 parents / 64 legs
claim_fee2 63 parents / 88 legs
claim_fee_event 127 parents / 187 legs
claim_fee2_event 78 parents / 118 legs
composition_fee_event 51 parents / 63 legs
withdraw_protocol_fee 14 parents / 20 legs
zap_protocol_fee 13 parents / 13 legs
```
The inner SPL transfer recovery is explicitly allowlisted for DLMM fee event kinds. It is not inherited globally by future decoders.
### Rewards
Reward parents are written to `k_sol_reward_events`. Amounts are recovered only when a reliable inner SPL transfer or decoded amount is present.
Final observed scalar amount summary:
```text
claim_reward 15 parents / 1 scalar amount
claim_reward2 19 parents / 10 scalar amounts
claim_reward_event 34 parents / 25 scalar amounts
claim_reward2_event 19 parents / 10 scalar amounts
fund_reward 10 parents / 10 scalar amounts
fund_reward_event 10 parents / 10 scalar amounts
initialize_reward 9 parents / 0 amount
initialize_reward_event 9 parents / 0 amount
```
`initialize_reward` and `initialize_reward_event` remain without amount because they are configuration/initialization events, not executed reward transfers. No amount is fabricated from limits, bounds or configuration fields.
### Admin/config and orderbook
Admin/config events materialize to `k_sol_pool_admin_events`. Limit-order operations materialize to `k_sol_orderbook_events` and never to trade/candle tables.
Important promoted local corpus entry:
```text
75c73e67068e1fcb -> meteora_dlmm.initialize_preset_parameter_v2 -> k_sol_pool_admin_events
```
## Remaining non-observed surfaces
Non-observed IDL/account rows remain in coverage with `observed_count = 0` and `materialized_count = 0`. They are not blockers.
Examples:
```text
update_reward_duration
update_reward_funder
withdraw_ineligible_reward
migrate_position
account metadata rows
for_idl_type_generation_do_not_call
anchor_self_cpi_log registry row
```
## Decision
`0.7.57 meteora_dlmm` is closed. Do not add further DLMM Rust patches unless a new corpus proves a successful transaction that is decoded but not materialized and lacks an explicit skip/policy reason.
Next recommended work is not another DLMM patch: implement `0.7.58 demo4_program_surface_discovery` to display unknown or unsupported program surfaces without automatic materialization or automatic decoder promotion.

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1126,7 +1126,7 @@ impl DexDecodeService {
event.instruction_id,
"meteora_dlmm",
event.program_id.clone(),
"meteora_dlmm.swap",
event.event_kind.as_str(),
event.pool_account.clone(),
None,
event.token_a_mint.clone(),
@@ -4273,6 +4273,18 @@ fn candidate_meteora_audit_pool_account(
return accounts.get(index).cloned();
}
fn is_meteora_dlmm_instruction_swap_event_kind(event_kind: &str) -> bool {
return matches!(
event_kind,
"meteora_dlmm.swap"
| "meteora_dlmm.swap2"
| "meteora_dlmm.swap_exact_out"
| "meteora_dlmm.swap_exact_out2"
| "meteora_dlmm.swap_with_price_impact"
| "meteora_dlmm.swap_with_price_impact2"
);
}
fn is_meteora_dlmm_anchor_swap_log_replaced_by_decoded_swap(
protocol_name: &str,
instruction: &crate::ChainInstructionDto,
@@ -4294,7 +4306,7 @@ fn is_meteora_dlmm_anchor_swap_log_replaced_by_decoded_swap(
}
for decoded_event in decoded_events {
if decoded_event.protocol_name == "meteora_dlmm"
&& decoded_event.event_kind == "meteora_dlmm.swap"
&& is_meteora_dlmm_instruction_swap_event_kind(decoded_event.event_kind.as_str())
{
return true;
}

View File

@@ -170,7 +170,12 @@ pub(crate) fn dex_detection_route(
("meteora_dlmm", "meteora_dlmm.create_pool") => {
return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDlmmPool);
},
("meteora_dlmm", "meteora_dlmm.swap") => {
("meteora_dlmm", "meteora_dlmm.swap")
| ("meteora_dlmm", "meteora_dlmm.swap2")
| ("meteora_dlmm", "meteora_dlmm.swap_exact_out")
| ("meteora_dlmm", "meteora_dlmm.swap_exact_out2")
| ("meteora_dlmm", "meteora_dlmm.swap_with_price_impact")
| ("meteora_dlmm", "meteora_dlmm.swap_with_price_impact2") => {
return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDlmmPool);
},
("meteora_damm_v1", "meteora_damm_v1.create_pool") => {

View File

@@ -323,6 +323,9 @@ pub fn is_dex_informational_event_kind(event_kind: &str) -> bool {
/// Returns true when the event kind represents a swap-like event.
pub fn is_dex_trade_event_kind(event_kind: &str) -> bool {
if is_meteora_dlmm_anchor_swap_log_event_kind(event_kind) {
return false;
}
if event_kind == "raydium_launchpad.trade_event" {
return true;
}
@@ -376,8 +379,126 @@ pub fn is_dex_candle_candidate_event_kind(event_kind: &str) -> bool {
return is_dex_trade_event_kind(event_kind);
}
fn is_meteora_dlmm_anchor_swap_log_event_kind(event_kind: &str) -> bool {
return matches!(event_kind, "meteora_dlmm.swap_event" | "meteora_dlmm.swap2_evt");
}
fn is_meteora_dlmm_position_lifecycle_event_kind(event_kind: &str) -> bool {
if !event_kind.starts_with("meteora_dlmm.") {
return false;
}
return matches!(
event_kind,
"meteora_dlmm.initialize_position"
| "meteora_dlmm.initialize_position2"
| "meteora_dlmm.initialize_position_by_operator"
| "meteora_dlmm.initialize_position_pda"
| "meteora_dlmm.position_create_event"
| "meteora_dlmm.close_position"
| "meteora_dlmm.close_position2"
| "meteora_dlmm.close_position_if_empty"
| "meteora_dlmm.position_close_event"
| "meteora_dlmm.decrease_position_length"
| "meteora_dlmm.decrease_position_length_event"
| "meteora_dlmm.increase_position_length"
| "meteora_dlmm.increase_position_length2"
| "meteora_dlmm.increase_position_length_event"
| "meteora_dlmm.update_position_operator"
| "meteora_dlmm.update_position_operator_event"
| "meteora_dlmm.update_position_lock_release_point_event"
| "meteora_dlmm.set_position_permissionless_operation_bits_evt"
);
}
fn is_meteora_dlmm_pool_lifecycle_event_kind(event_kind: &str) -> bool {
if !event_kind.starts_with("meteora_dlmm.") {
return false;
}
if is_meteora_dlmm_position_lifecycle_event_kind(event_kind) {
return true;
}
return matches!(
event_kind,
"meteora_dlmm.create_pool"
| "meteora_dlmm.swap_event"
| "meteora_dlmm.swap2_evt"
| "meteora_dlmm.lb_pair_create_event"
| "meteora_dlmm.initialize_lb_pair"
| "meteora_dlmm.initialize_lb_pair2"
| "meteora_dlmm.initialize_permission_lb_pair"
| "meteora_dlmm.initialize_customizable_permissionless_lb_pair"
| "meteora_dlmm.initialize_customizable_permissionless_lb_pair2"
| "meteora_dlmm.initialize_bin_array"
| "meteora_dlmm.initialize_bin_array_bitmap_extension"
| "meteora_dlmm.close_bin_array"
| "meteora_dlmm.go_to_a_bin"
| "meteora_dlmm.go_to_a_bin_event"
| "meteora_dlmm.increase_oracle_length"
| "meteora_dlmm.increase_observation_event"
| "meteora_dlmm.migrate_bin_array"
| "meteora_dlmm.migrate_position"
);
}
fn is_meteora_dlmm_admin_event_kind(event_kind: &str) -> bool {
if !event_kind.starts_with("meteora_dlmm.") {
return false;
}
if is_meteora_dlmm_pool_lifecycle_event_kind(event_kind) {
return false;
}
return matches!(
event_kind,
"meteora_dlmm.instruction_audit"
| "meteora_dlmm.close_claim_fee_operator_account"
| "meteora_dlmm.close_claim_protocol_fee_operator"
| "meteora_dlmm.close_operator_account"
| "meteora_dlmm.close_preset_parameter"
| "meteora_dlmm.close_preset_parameter2"
| "meteora_dlmm.close_token_badge"
| "meteora_dlmm.create_claim_protocol_fee_operator"
| "meteora_dlmm.create_operator_account"
| "meteora_dlmm.dynamic_fee_parameter_update_event"
| "meteora_dlmm.fee_parameter_update_event"
| "meteora_dlmm.initialize_preset_parameter"
| "meteora_dlmm.initialize_preset_parameter2"
| "meteora_dlmm.initialize_preset_parameter_v2"
| "meteora_dlmm.initialize_token_badge"
| "meteora_dlmm.set_activation_point"
| "meteora_dlmm.set_pair_status"
| "meteora_dlmm.set_pair_status_permissionless"
| "meteora_dlmm.set_permissionless_operation_bits"
| "meteora_dlmm.set_pre_activation_duration"
| "meteora_dlmm.set_pre_activation_swap_address"
| "meteora_dlmm.update_base_fee_parameters"
| "meteora_dlmm.update_dynamic_fee_parameters"
| "meteora_dlmm.update_fees_and_reward2"
| "meteora_dlmm.update_fees_and_rewards"
| "meteora_dlmm.update_reward_duration"
| "meteora_dlmm.update_reward_duration_event"
| "meteora_dlmm.update_reward_funder"
| "meteora_dlmm.update_reward_funder_event"
);
}
/// Returns true for liquidity lifecycle changes that must not become candles.
pub fn is_dex_liquidity_event_kind(event_kind: &str) -> bool {
if event_kind.starts_with("meteora_dlmm.")
&& (event_kind.contains("withdraw_protocol_fee")
|| event_kind.contains("withdraw_ineligible_reward"))
{
return false;
}
if is_meteora_dlmm_position_lifecycle_event_kind(event_kind) {
return false;
}
if event_kind.starts_with("meteora_dlmm.")
&& (event_kind == "meteora_dlmm.rebalance_liquidity"
|| event_kind == "meteora_dlmm.rebalancing_event"
|| event_kind == "meteora_dlmm.remove_all_liquidity")
{
return true;
}
if event_kind.starts_with("meteora_dbc.")
&& (event_kind.contains("fee")
|| event_kind.contains("surplus")
@@ -461,6 +582,15 @@ pub fn is_dex_liquidity_add_event_kind(event_kind: &str) -> bool {
/// Returns true for liquidity remove-like DEX events.
pub fn is_dex_liquidity_remove_event_kind(event_kind: &str) -> bool {
if event_kind.starts_with("meteora_dlmm.")
&& (event_kind.contains("withdraw_protocol_fee")
|| event_kind.contains("withdraw_ineligible_reward"))
{
return false;
}
if event_kind == "meteora_dlmm.remove_all_liquidity" {
return true;
}
if event_kind.contains(".withdraw_pnl") {
return false;
}
@@ -509,6 +639,17 @@ pub fn is_dex_position_close_event_kind(event_kind: &str) -> bool {
/// Returns true for fee collection events.
pub fn is_dex_fee_event_kind(event_kind: &str) -> bool {
if event_kind.starts_with("meteora_dlmm.") && event_kind.contains("claim_fee_operator") {
return false;
}
if event_kind.starts_with("meteora_dlmm.")
&& (event_kind.contains("claim_fee")
|| event_kind.contains("composition_fee")
|| event_kind.contains("withdraw_protocol_fee")
|| event_kind.contains("zap_protocol_fee"))
{
return true;
}
if event_kind.starts_with("meteora_dbc.")
&& (event_kind.contains("fee")
|| event_kind.contains("surplus")
@@ -580,6 +721,13 @@ pub fn is_dex_fee_event_kind(event_kind: &str) -> bool {
/// Returns true for reward or incentive events.
pub fn is_dex_reward_event_kind(event_kind: &str) -> bool {
if event_kind.starts_with("meteora_dlmm.")
&& (event_kind.contains("update_fees_and_reward")
|| event_kind.contains("update_reward_duration")
|| event_kind.contains("update_reward_funder"))
{
return false;
}
if event_kind.starts_with("pump_fees.")
&& (event_kind.contains("social_fee_pda_claimed")
|| event_kind.contains("claim_social_fee_pda"))
@@ -633,6 +781,15 @@ pub fn is_dex_orderbook_event_kind(event_kind: &str) -> bool {
if event_kind.contains(".open_limit_order") {
return true;
}
if event_kind.contains(".place_limit_order") {
return true;
}
if event_kind.contains(".cancel_limit_order") {
return true;
}
if event_kind.contains("limit_order_evt") {
return true;
}
if event_kind.contains(".increase_limit_order") {
return true;
}
@@ -650,6 +807,9 @@ pub fn is_dex_orderbook_event_kind(event_kind: &str) -> bool {
/// Returns true for pool, pair, launch, mint, burn or migration lifecycle events.
pub fn is_dex_pool_lifecycle_event_kind(event_kind: &str) -> bool {
if is_meteora_dlmm_pool_lifecycle_event_kind(event_kind) {
return true;
}
if event_kind.starts_with("pump_fees.")
&& (event_kind.contains("create_fee_sharing_config")
|| event_kind.contains("create_social_fee_pda")
@@ -772,6 +932,25 @@ pub fn is_dex_migration_event_kind(event_kind: &str) -> bool {
/// Returns true for pool creation or initialization events.
pub fn is_dex_pool_creation_event_kind(event_kind: &str) -> bool {
if event_kind.starts_with("meteora_dlmm.") {
if event_kind.contains(".initialize_position") {
return false;
}
if event_kind.contains(".initialize_bin_array") {
return true;
}
if event_kind.contains(".initialize_customizable_permissionless_lb_pair")
|| event_kind.contains(".initialize_permission_lb_pair")
|| event_kind.contains(".initialize_lb_pair")
|| event_kind.contains(".lb_pair_create")
|| event_kind.contains(".create_pool")
{
return true;
}
if event_kind.contains(".initialize") {
return false;
}
}
if event_kind == "meteora_dbc.evt_initialize_pool_event"
|| event_kind.contains(".initialize_virtual_pool")
{
@@ -859,6 +1038,12 @@ pub fn is_dex_token_account_close_event_kind(event_kind: &str) -> bool {
/// Returns true for admin, configuration or permission changes.
pub fn is_dex_admin_event_kind(event_kind: &str) -> bool {
if is_meteora_dlmm_pool_lifecycle_event_kind(event_kind) {
return false;
}
if is_meteora_dlmm_admin_event_kind(event_kind) {
return true;
}
if event_kind.starts_with("meteora_dbc.")
&& (event_kind.contains("config")
|| event_kind.contains("operator")
@@ -1391,6 +1576,42 @@ mod tests {
);
}
#[test]
fn classifies_meteora_dlmm_fee_reward_admin_without_multi_target_overlap() {
assert!(super::is_dex_fee_event_kind("meteora_dlmm.withdraw_protocol_fee"));
assert!(!super::is_dex_liquidity_event_kind("meteora_dlmm.withdraw_protocol_fee"));
assert!(!super::is_dex_liquidity_remove_event_kind("meteora_dlmm.withdraw_protocol_fee"));
assert!(!super::is_dex_fee_event_kind("meteora_dlmm.close_claim_fee_operator_account"));
assert!(super::is_dex_admin_event_kind("meteora_dlmm.close_claim_fee_operator_account"));
assert!(super::is_dex_reward_event_kind("meteora_dlmm.initialize_reward"));
assert!(!super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.initialize_reward"));
assert!(!super::is_dex_reward_event_kind("meteora_dlmm.update_fees_and_rewards"));
assert!(super::is_dex_admin_event_kind("meteora_dlmm.update_fees_and_rewards"));
assert!(!super::is_dex_reward_event_kind("meteora_dlmm.update_reward_duration_event"));
assert!(super::is_dex_admin_event_kind("meteora_dlmm.update_reward_duration_event"));
assert!(super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.position_create_event"));
assert!(!super::is_dex_liquidity_event_kind("meteora_dlmm.position_create_event"));
assert!(super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.increase_position_length"));
assert!(!super::is_dex_liquidity_event_kind("meteora_dlmm.increase_position_length"));
assert!(!super::is_dex_admin_event_kind("meteora_dlmm.update_position_operator"));
assert!(super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.close_bin_array"));
assert!(super::is_dex_admin_event_kind("meteora_dlmm.create_operator_account"));
assert!(super::is_dex_admin_event_kind(
"meteora_dlmm.initialize_preset_parameter_v2"
));
assert!(super::is_dex_admin_event_kind("meteora_dlmm.instruction_audit"));
assert!(!super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.instruction_audit"));
assert!(!super::is_dex_fee_event_kind("meteora_dlmm.instruction_audit"));
assert!(!super::is_dex_reward_event_kind("meteora_dlmm.instruction_audit"));
assert!(super::is_dex_liquidity_event_kind("meteora_dlmm.rebalancing_event"));
assert!(super::is_dex_liquidity_event_kind("meteora_dlmm.remove_all_liquidity"));
assert!(!super::is_dex_trade_event_kind("meteora_dlmm.swap_event"));
assert!(!super::is_dex_candle_candidate_event_kind("meteora_dlmm.swap_event"));
assert!(super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.swap_event"));
assert!(!super::is_dex_trade_event_kind("meteora_dlmm.swap2_evt"));
assert!(!super::is_dex_candle_candidate_event_kind("meteora_dlmm.swap2_evt"));
assert!(super::is_dex_pool_lifecycle_event_kind("meteora_dlmm.swap2_evt"));
}
#[test]
fn enriched_payload_keeps_existing_fields() {
let payload_json = serde_json::json!({
@@ -1514,6 +1735,10 @@ mod tests {
super::classify_dex_event_category_code("meteora_dlmm.remove_liquidity"),
"liquidity"
);
assert_eq!(
super::classify_dex_event_category_code("meteora_dlmm.initialize_position"),
"pool_lifecycle"
);
assert_eq!(
super::classify_dex_event_lifecycle_kind_code("meteora_dlmm.initialize_position"),
"position_open"

File diff suppressed because it is too large Load Diff

View File

@@ -200,6 +200,90 @@ fn resolve_instruction_name(
Some(discriminator_hex) => discriminator_hex,
None => return None,
};
if program_id == crate::METEORA_DLMM_PROGRAM_ID || decoder_code == Some("meteora_dlmm") {
let name = match discriminator_hex {
"e445a52e51cb9a1d" => "anchor_self_cpi_log",
"b59d59438fb63448" => "add_liquidity",
"e4a24e1c46db7473" => "add_liquidity2",
"0703967f94283dc8" => "add_liquidity_by_strategy",
"03dd95da6f8d76d5" => "add_liquidity_by_strategy2",
"2905eeaf64e106cd" => "add_liquidity_by_strategy_one_side",
"1c8cee63e7a21595" => "add_liquidity_by_weight",
"d13b3f5b6fc899e4" => "add_liquidity_by_weight2",
"5e9b6797465fdca5" => "add_liquidity_one_side",
"a1c26754ab47fa9a" => "add_liquidity_one_side_precise",
"2133a3c975627de7" => "add_liquidity_one_side_precise2",
"849c841f4328e861" => "cancel_limit_order",
"a9204f8988e84689" => "claim_fee",
"70bf65ab1c907fbb" => "claim_fee2",
"955fb5f25e5a9ea2" => "claim_reward",
"be037f77b2579db7" => "claim_reward2",
"44ae5850b5cc13e0" => "close_bin_array",
"b8d5581fb3658224" => "close_claim_fee_operator_account",
"397c249b7ef95dab" => "close_limit_order_if_empty",
"ab09d54a7817031d" => "close_operator_account",
"7b86510031446262" => "close_position",
"ae5a2373ba2893e2" => "close_position2",
"3b7cd4765b986e9d" => "close_position_if_empty",
"04949164861ab53d" => "close_preset_parameter",
"27195f6b7411731c" => "close_preset_parameter2",
"6c92566eb3fe0a68" => "close_token_badge",
"dd40f695f099e5a3" => "create_operator_account",
"c2db882019606925" => "decrease_position_length",
"b46945505f32496c" => "for_idl_type_generation_do_not_call",
"bc32f9a55d97263f" => "fund_reward",
"9248aee028fd54ae" => "go_to_a_bin",
"be3d7d57674f9ead" => "increase_oracle_length",
"505375d3420d2195" => "increase_position_length",
"ffd2cc477389e171" => "increase_position_length2",
"235613b94ed44bd3" => "initialize_bin_array",
"2f9de2b40cf02147" => "initialize_bin_array_bitmap_extension",
"2e2729876fb7c840" => "initialize_customizable_permissionless_lb_pair",
"f349817e3313f16b" => "initialize_customizable_permissionless_lb_pair2",
"2d9aedd2dd0fa65c" => "initialize_lb_pair",
"493b2478ed536cc6" => "initialize_lb_pair2",
"6c66d555fb033515" => "initialize_permission_lb_pair",
"dbc0ea47bebf6650" => "initialize_position",
"8f13f291d50f6873" => "initialize_position2",
"fbbdbef475fe2394" => "initialize_position_by_operator",
"2e527d92558de499" => "initialize_position_pda",
"42bc47d3626d0eba" => "initialize_preset_parameter",
"75c73e67068e1fcb" => "initialize_preset_parameter_v2",
"5f87c0c4f281e644" => "initialize_reward",
"fd4dcd5f1be059df" => "initialize_token_badge",
"6cb021ba92e501c5" => "place_limit_order",
"5c04b0c177b95309" => "rebalance_liquidity",
"0a333d2370691855" => "remove_all_liquidity",
"5055d14818ceb16c" => "remove_liquidity",
"e6d7527ff165e392" => "remove_liquidity2",
"1a526698f04a691a" => "remove_liquidity_by_range",
"cc02c391359191cd" => "remove_liquidity_by_range2",
"5bf90fa51a81fe7d" => "set_activation_point",
"43f8e7899a95d9ae" => "set_pair_status",
"4e3b98d346b72ed0" => "set_pair_status_permissionless",
"543acb8ba351beba" => "set_permissionless_operation_bits",
"a53dc9f4829f1664" => "set_pre_activation_duration",
"398b2f7bd850df0a" => "set_pre_activation_swap_address",
"f8c69e91e17587c8" => "swap",
"414b3f4ceb5b5b88" => "swap2",
"fa49652126cf4bb8" => "swap_exact_out",
"2bd7f784893cf351" => "swap_exact_out2",
"38ade6d0ade49ccd" => "swap_with_price_impact",
"4a62c0d6b1334b33" => "swap_with_price_impact2",
"4ba8dfa110c3032f" => "update_base_fee_parameters",
"5ca12ef6ffbd1616" => "update_dynamic_fee_parameters",
"208eb89a6741b858" => "update_fees_and_reward2",
"9ae6fa0decd14bdf" => "update_fees_and_rewards",
"cab8678fb4bf74d9" => "update_position_operator",
"8aaec4a9d5ebfe6b" => "update_reward_duration",
"d31c3020d7a02317" => "update_reward_funder",
"94ce2ac3f7316708" => "withdraw_ineligible_reward",
"9ec99ebd215da267" => "withdraw_protocol_fee",
"d59bbb2238b65bf0" => "zap_protocol_fee",
_ => "instruction_audit",
};
return Some(format!("meteora_dlmm.{}", name));
}
if program_id == crate::METEORA_DBC_PROGRAM_ID || decoder_code == Some("meteora_dbc") {
let name = match discriminator_hex {
"e445a52e51cb9a1d" => "meteora_dbc.anchor_self_cpi_log",
@@ -578,6 +662,43 @@ fn option_i64_key(value: std::option::Option<i64>) -> std::string::String {
#[cfg(test)]
mod tests {
#[test]
fn resolves_meteora_dlmm_instruction_observation_names() {
let anchor_name = super::resolve_instruction_name(
crate::METEORA_DLMM_PROGRAM_ID,
Some("meteora_dlmm"),
Some("e445a52e51cb9a1d"),
);
assert_eq!(anchor_name.as_deref(), Some("meteora_dlmm.anchor_self_cpi_log"));
let swap_exact_out2_name = super::resolve_instruction_name(
crate::METEORA_DLMM_PROGRAM_ID,
Some("meteora_dlmm"),
Some("2bd7f784893cf351"),
);
assert_eq!(swap_exact_out2_name.as_deref(), Some("meteora_dlmm.swap_exact_out2"));
let close_position_name = super::resolve_instruction_name(
crate::METEORA_DLMM_PROGRAM_ID,
Some("meteora_dlmm"),
Some("7b86510031446262"),
);
assert_eq!(close_position_name.as_deref(), Some("meteora_dlmm.close_position"));
let preset_v2_name = super::resolve_instruction_name(
crate::METEORA_DLMM_PROGRAM_ID,
Some("meteora_dlmm"),
Some("75c73e67068e1fcb"),
);
assert_eq!(
preset_v2_name.as_deref(),
Some("meteora_dlmm.initialize_preset_parameter_v2")
);
let unknown_name = super::resolve_instruction_name(
crate::METEORA_DLMM_PROGRAM_ID,
Some("meteora_dlmm"),
Some("ffffffffffffffff"),
);
assert_eq!(unknown_name.as_deref(), Some("meteora_dlmm.instruction_audit"));
}
#[test]
fn resolves_meteora_dbc_instruction_observation_names() {
let anchor_name = super::resolve_instruction_name(

View File

@@ -71,10 +71,14 @@ fn should_skip_non_trade_event_due_to_explicit_reason(
{
return true;
}
if crate::is_dex_orderbook_event_kind(decoded_event.event_kind.as_str())
&& payload_has_non_empty_text(payload, "skipOrderbookReason")
{
return true;
}
return false;
}
fn should_attempt_meteora_dbc_explicit_skip_materialization(
decoded_event: &crate::DexDecodedEventDto,
payload: &serde_json::Value,
@@ -121,7 +125,6 @@ fn is_meteora_dbc_instruction_admin_materialization_candidate(event_kind: &str)
);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FeeAmountRecoveryPolicy {
Disabled,
@@ -151,11 +154,43 @@ fn fee_amount_recovery_policy_for_event_kind(event_kind: &str) -> FeeAmountRecov
| "raydium_launchpad.claim_platform_fee_from_vault"
| "raydium_launchpad.collect_fee"
| "raydium_stable_swap.withdraw_pnl"
| "raydium_stable_swap.withdraw_srm" => FeeAmountRecoveryPolicy::InnerSplTransfer,
| "raydium_stable_swap.withdraw_srm"
| "meteora_dlmm.claim_fee"
| "meteora_dlmm.claim_fee2"
| "meteora_dlmm.claim_fee_event"
| "meteora_dlmm.claim_fee2_event"
| "meteora_dlmm.composition_fee_event"
| "meteora_dlmm.withdraw_protocol_fee"
| "meteora_dlmm.zap_protocol_fee" => FeeAmountRecoveryPolicy::InnerSplTransfer,
_ => FeeAmountRecoveryPolicy::Disabled,
};
}
fn reward_amount_recovery_policy_for_event_kind(event_kind: &str) -> FeeAmountRecoveryPolicy {
return match event_kind {
"meteora_dlmm.claim_reward"
| "meteora_dlmm.claim_reward2"
| "meteora_dlmm.claim_reward_event"
| "meteora_dlmm.claim_reward2_event"
| "meteora_dlmm.fund_reward"
| "meteora_dlmm.fund_reward_event"
| "meteora_dlmm.initialize_reward"
| "meteora_dlmm.initialize_reward_event"
| "meteora_dlmm.withdraw_ineligible_reward"
| "meteora_dlmm.withdraw_ineligible_reward_event" => {
FeeAmountRecoveryPolicy::InnerSplTransfer
},
_ => FeeAmountRecoveryPolicy::Disabled,
};
}
fn optional_text_is_missing(value: &std::option::Option<std::string::String>) -> bool {
match value.as_ref() {
Some(value) => return value.trim().is_empty(),
None => return true,
}
}
fn payload_has_non_empty_text(payload: &serde_json::Value, key: &str) -> bool {
let value = match payload.get(key) {
Some(value) => value,
@@ -240,7 +275,10 @@ impl NonTradeEventMaterializationService {
continue;
}
if should_skip_non_trade_event_due_to_explicit_reason(decoded_event, &payload)
&& !should_attempt_meteora_dbc_explicit_skip_materialization(decoded_event, &payload)
&& !should_attempt_meteora_dbc_explicit_skip_materialization(
decoded_event,
&payload,
)
{
tracing::debug!(
event_kind = %decoded_event.event_kind,
@@ -534,10 +572,44 @@ impl NonTradeEventMaterializationService {
],
);
let mut payload_json = decoded_event.payload_json.clone();
let mut fee_amount_legs: std::vec::Vec<MeteoraDbcActualAmount> = std::vec::Vec::new();
if is_meteora_dbc_instruction_fee_materialization_candidate(decoded_event.event_kind.as_str()) {
let mut fee_amount_legs: std::vec::Vec<MeteoraDbcActualAmount> =
fee_amount_legs_from_payload(payload);
if !fee_amount_legs.is_empty() {
match fee_amount_legs.as_slice() {
[first_leg] => {
fee_token_mint = Some(first_leg.token_mint.clone());
fee_amount_raw = Some(first_leg.amount_raw.clone());
},
_ => {
fee_token_mint = None;
fee_amount_raw = None;
},
}
let updated_payload =
decoded_payload_with_materialized_fee_legs(payload, &fee_amount_legs);
payload_json = updated_payload.to_string();
let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
self.database.as_ref(),
decoded_event_id,
payload_json.as_str(),
)
.await;
if let Err(error) = update_result {
return Err(error);
}
}
if fee_amount_legs.is_empty()
&& is_meteora_dbc_instruction_fee_materialization_candidate(
decoded_event.event_kind.as_str(),
)
{
let inferred_result = self
.resolve_meteora_dbc_actual_fee_amounts(transaction, transaction_id, decoded_event, payload)
.resolve_meteora_dbc_actual_fee_amounts(
transaction,
transaction_id,
decoded_event,
payload,
)
.await;
let inferred = match inferred_result {
Ok(inferred) => inferred,
@@ -555,7 +627,8 @@ impl NonTradeEventMaterializationService {
fee_amount_raw = None;
},
}
let updated_payload = decoded_payload_with_materialized_fee_legs(payload, &fee_amount_legs);
let updated_payload =
decoded_payload_with_materialized_fee_legs(payload, &fee_amount_legs);
payload_json = updated_payload.to_string();
let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
self.database.as_ref(),
@@ -597,11 +670,14 @@ impl NonTradeEventMaterializationService {
return Ok(false);
}
}
let recovery_policy = fee_amount_recovery_policy_for_event_kind(decoded_event.event_kind.as_str());
let recovery_policy =
fee_amount_recovery_policy_for_event_kind(decoded_event.event_kind.as_str());
if fee_amount_legs.is_empty()
&& fee_amount_raw.is_none()
&& recovery_policy != FeeAmountRecoveryPolicy::Disabled
&& !is_meteora_dbc_instruction_fee_materialization_candidate(decoded_event.event_kind.as_str())
&& !is_meteora_dbc_instruction_fee_materialization_candidate(
decoded_event.event_kind.as_str(),
)
{
let inferred_result = self
.resolve_allowlisted_actual_fee_amounts_from_spl_transfers(
@@ -626,7 +702,8 @@ impl NonTradeEventMaterializationService {
fee_amount_raw = None;
},
}
let updated_payload = decoded_payload_with_materialized_fee_legs(payload, &fee_amount_legs);
let updated_payload =
decoded_payload_with_materialized_fee_legs(payload, &fee_amount_legs);
payload_json = updated_payload.to_string();
let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
self.database.as_ref(),
@@ -650,10 +727,11 @@ impl NonTradeEventMaterializationService {
Err(error) => return Err(error),
};
if let Some(explanation) = explanation {
let updated_payload = decoded_payload_with_unmaterialized_fee_amount_recovery_reason(
payload,
explanation.as_str(),
);
let updated_payload =
decoded_payload_with_unmaterialized_fee_amount_recovery_reason(
payload,
explanation.as_str(),
);
payload_json = updated_payload.to_string();
let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
self.database.as_ref(),
@@ -781,10 +859,13 @@ impl NonTradeEventMaterializationService {
Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
None => None,
};
let instruction_index = extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
let instructions_result =
crate::query_chain_instructions_list_by_transaction_id(self.database.as_ref(), transaction_id)
.await;
let instruction_index =
extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
self.database.as_ref(),
transaction_id,
)
.await;
let instructions = match instructions_result {
Ok(instructions) => instructions,
Err(error) => return Err(error),
@@ -855,10 +936,13 @@ impl NonTradeEventMaterializationService {
Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
None => None,
};
let instruction_index = extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
let instructions_result =
crate::query_chain_instructions_list_by_transaction_id(self.database.as_ref(), transaction_id)
.await;
let instruction_index =
extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
self.database.as_ref(),
transaction_id,
)
.await;
let instructions = match instructions_result {
Ok(instructions) => instructions,
Err(error) => return Err(error),
@@ -929,10 +1013,13 @@ impl NonTradeEventMaterializationService {
Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
None => None,
};
let instruction_index = extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
let instructions_result =
crate::query_chain_instructions_list_by_transaction_id(self.database.as_ref(), transaction_id)
.await;
let instruction_index =
extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
self.database.as_ref(),
transaction_id,
)
.await;
let instructions = match instructions_result {
Ok(instructions) => instructions,
Err(error) => return Err(error),
@@ -988,9 +1075,7 @@ impl NonTradeEventMaterializationService {
non_zero_mints.insert(mint);
}
if non_zero_mints.len() > 1 {
return Ok(Some(
"multi_token_fee_amounts_materialized_as_fee_amount_legs".to_string(),
));
return Ok(Some("multi_token_fee_amounts_materialized_as_fee_amount_legs".to_string()));
}
if transfer_count > 0 && non_zero_transfer_count == 0 {
return Ok(Some("fee_instruction_has_only_zero_amount_transfers".to_string()));
@@ -1001,6 +1086,86 @@ impl NonTradeEventMaterializationService {
return Ok(None);
}
async fn resolve_allowlisted_actual_reward_amount_from_spl_transfers(
&self,
transaction_id: i64,
decoded_event: &crate::DexDecodedEventDto,
payload: &serde_json::Value,
) -> Result<std::option::Option<MeteoraDbcActualAmount>, crate::Error> {
let parent_instruction_id = match decoded_event.id {
Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
None => None,
};
let instruction_index =
extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
self.database.as_ref(),
transaction_id,
)
.await;
let instructions = match instructions_result {
Ok(instructions) => instructions,
Err(error) => return Err(error),
};
let mut amount_legs = std::vec::Vec::new();
for instruction in &instructions {
if !instruction_is_child_of_decoded_event(
instruction,
parent_instruction_id,
instruction_index,
) {
continue;
}
let parsed_type = match instruction.parsed_type.as_deref() {
Some(parsed_type) => parsed_type,
None => continue,
};
if parsed_type != "transferChecked" && parsed_type != "transfer" {
continue;
}
let parsed_json = match instruction.parsed_json.as_deref() {
Some(parsed_json) => parsed_json,
None => continue,
};
let parsed_value = serde_json::from_str::<serde_json::Value>(parsed_json);
let parsed_value = match parsed_value {
Ok(parsed_value) => parsed_value,
Err(_) => continue,
};
let amount_raw = extract_spl_transfer_amount_raw(&parsed_value);
let amount_raw = match amount_raw {
Some(amount_raw) => amount_raw,
None => continue,
};
let amount = amount_raw.parse::<u128>();
let amount = match amount {
Ok(amount) => amount,
Err(_) => continue,
};
if amount == 0 {
continue;
}
let mint = extract_first_string(&parsed_value, &["mint"]);
let mint = match mint {
Some(mint) => mint,
None => continue,
};
let source_account = extract_first_string(&parsed_value, &["source"]);
let destination_account = extract_first_string(&parsed_value, &["destination"]);
amount_legs.push(MeteoraDbcActualAmount {
token_mint: mint,
amount_raw,
source: "allowlisted_reward_inner_spl_transfer".to_string(),
source_account,
destination_account,
});
}
return Ok(single_mint_amount_from_actual_amounts(
amount_legs.as_slice(),
"allowlisted_reward_inner_spl_transfer",
));
}
async fn resolve_meteora_dbc_unmaterialized_fee_skip_reason(
&self,
transaction_id: i64,
@@ -1011,10 +1176,13 @@ impl NonTradeEventMaterializationService {
Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
None => None,
};
let instruction_index = extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
let instructions_result =
crate::query_chain_instructions_list_by_transaction_id(self.database.as_ref(), transaction_id)
.await;
let instruction_index =
extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
self.database.as_ref(),
transaction_id,
)
.await;
let instructions = match instructions_result {
Ok(instructions) => instructions,
Err(error) => return Err(error),
@@ -1070,9 +1238,7 @@ impl NonTradeEventMaterializationService {
non_zero_mints.insert(mint);
}
if non_zero_mints.len() > 1 {
return Ok(Some(
"multi_token_fee_amounts_require_multiple_fee_rows".to_string(),
));
return Ok(Some("multi_token_fee_amounts_require_multiple_fee_rows".to_string()));
}
if transfer_count > 0 && non_zero_transfer_count == 0 {
return Ok(Some("fee_instruction_has_only_zero_amount_transfers".to_string()));
@@ -1112,11 +1278,11 @@ impl NonTradeEventMaterializationService {
"user",
],
);
let reward_token_mint = extract_first_string(
let mut reward_token_mint = extract_first_string(
payload,
&["rewardTokenMint", "reward_token_mint", "tokenMint", "token_mint", "mint"],
);
let reward_amount_raw = extract_first_amount_string(
let mut reward_amount_raw = extract_first_amount_string(
payload,
&[
"rewardAmountRaw",
@@ -1128,6 +1294,35 @@ impl NonTradeEventMaterializationService {
"amount",
],
);
let mut reward_payload_json = decoded_event.payload_json.clone();
if reward_amount_recovery_policy_for_event_kind(decoded_event.event_kind.as_str())
== FeeAmountRecoveryPolicy::InnerSplTransfer
&& (optional_text_is_missing(&reward_token_mint)
|| optional_text_is_missing(&reward_amount_raw))
{
let recovered_reward_result = self
.resolve_allowlisted_actual_reward_amount_from_spl_transfers(
transaction_id,
decoded_event,
payload,
)
.await;
let recovered_reward = match recovered_reward_result {
Ok(recovered_reward) => recovered_reward,
Err(error) => return Err(error),
};
if let Some(recovered_reward) = recovered_reward {
if optional_text_is_missing(&reward_token_mint) {
reward_token_mint = Some(recovered_reward.token_mint.clone());
}
if optional_text_is_missing(&reward_amount_raw) {
reward_amount_raw = Some(recovered_reward.amount_raw.clone());
}
reward_payload_json =
decoded_payload_with_materialized_reward_amount(payload, &recovered_reward)
.to_string();
}
}
let dto = crate::RewardEventDto::new(
transaction_id,
Some(decoded_event_id),
@@ -1143,7 +1338,7 @@ impl NonTradeEventMaterializationService {
actor_wallet,
reward_token_mint,
reward_amount_raw,
decoded_event.payload_json.clone(),
reward_payload_json,
);
let upsert_result = crate::query_reward_events_upsert(self.database.as_ref(), &dto).await;
match upsert_result {
@@ -2100,6 +2295,119 @@ struct MeteoraDbcActualAmount {
destination_account: std::option::Option<std::string::String>,
}
fn single_mint_amount_from_actual_amounts(
amount_legs: &[MeteoraDbcActualAmount],
source: &str,
) -> std::option::Option<MeteoraDbcActualAmount> {
let mut token_mint: std::option::Option<std::string::String> = None;
let mut amount_total: u128 = 0;
let mut source_account: std::option::Option<std::string::String> = None;
let mut destination_account: std::option::Option<std::string::String> = None;
for leg in amount_legs {
if leg.token_mint.trim().is_empty() {
continue;
}
let amount = leg.amount_raw.parse::<u128>();
let amount = match amount {
Ok(amount) => amount,
Err(_) => continue,
};
if amount == 0 {
continue;
}
match token_mint.as_ref() {
Some(existing_mint) => {
if existing_mint != &leg.token_mint {
return None;
}
},
None => token_mint = Some(leg.token_mint.clone()),
}
let checked_total = amount_total.checked_add(amount);
amount_total = match checked_total {
Some(checked_total) => checked_total,
None => return None,
};
if source_account.is_none() {
source_account = leg.source_account.clone();
}
if destination_account.is_none() {
destination_account = leg.destination_account.clone();
}
}
let token_mint = match token_mint {
Some(token_mint) => token_mint,
None => return None,
};
if amount_total == 0 {
return None;
}
return Some(MeteoraDbcActualAmount {
token_mint,
amount_raw: amount_total.to_string(),
source: source.to_string(),
source_account,
destination_account,
});
}
fn fee_amount_legs_from_payload(
payload: &serde_json::Value,
) -> std::vec::Vec<MeteoraDbcActualAmount> {
let mut legs = std::vec::Vec::new();
let candidate_arrays = ["feeAmountLegs", "materializedFeeAmountLegs"];
for candidate_key in candidate_arrays {
let candidate = match payload.get(candidate_key) {
Some(candidate) => candidate,
None => continue,
};
let array = match candidate.as_array() {
Some(array) => array,
None => continue,
};
for leg_value in array {
let token_mint = extract_first_string(
leg_value,
&["tokenMint", "token_mint", "feeTokenMint", "fee_token_mint", "mint"],
);
let amount_raw = extract_first_amount_string(
leg_value,
&["amountRaw", "amount_raw", "feeAmountRaw", "fee_amount_raw", "amount"],
);
let token_mint = match token_mint {
Some(token_mint) => token_mint,
None => continue,
};
let amount_raw = match amount_raw {
Some(amount_raw) => amount_raw,
None => continue,
};
let amount_trimmed = amount_raw.trim();
if amount_trimmed.is_empty() || amount_trimmed == "0" {
continue;
}
let source =
match extract_first_string(leg_value, &["amountSource", "amount_source", "source"])
{
Some(source) => source,
None => "decoded_fee_amount_leg".to_string(),
};
let source_account =
extract_first_string(leg_value, &["sourceAccount", "source_account"]);
let destination_account =
extract_first_string(leg_value, &["destinationAccount", "destination_account"]);
legs.push(MeteoraDbcActualAmount {
token_mint,
amount_raw: amount_trimmed.to_string(),
source,
source_account,
destination_account,
});
}
}
return legs;
}
fn instruction_is_child_of_decoded_event(
instruction: &crate::ChainInstructionDto,
parent_instruction_id: std::option::Option<i64>,
@@ -2147,6 +2455,57 @@ fn extract_spl_transfer_amount_raw(
return None;
}
fn decoded_payload_with_materialized_reward_amount(
payload: &serde_json::Value,
reward_amount: &MeteoraDbcActualAmount,
) -> serde_json::Value {
let mut object = match payload.clone() {
serde_json::Value::Object(object) => object,
other => {
let mut object = serde_json::Map::new();
object.insert("rawPayload".to_string(), other);
object
},
};
object.insert("skipRewardReason".to_string(), serde_json::Value::String(String::new()));
object.insert("rewardMaterialized".to_string(), serde_json::Value::Bool(true));
object.insert(
"rewardMaterializationSource".to_string(),
serde_json::Value::String(reward_amount.source.clone()),
);
object.insert(
"rewardTokenMint".to_string(),
serde_json::Value::String(reward_amount.token_mint.clone()),
);
object.insert(
"rewardAmountRaw".to_string(),
serde_json::Value::String(reward_amount.amount_raw.clone()),
);
match reward_amount.source_account.as_ref() {
Some(source_account) => {
object.insert(
"rewardSourceAccount".to_string(),
serde_json::Value::String(source_account.clone()),
);
},
None => {},
}
match reward_amount.destination_account.as_ref() {
Some(destination_account) => {
object.insert(
"rewardDestinationAccount".to_string(),
serde_json::Value::String(destination_account.clone()),
);
},
None => {},
}
object.insert(
"eventActionability".to_string(),
serde_json::Value::String("reward_materialized".to_string()),
);
return serde_json::Value::Object(object);
}
fn decoded_payload_with_materialized_fee_legs(
payload: &serde_json::Value,
legs: &[MeteoraDbcActualAmount],
@@ -2159,11 +2518,11 @@ fn decoded_payload_with_materialized_fee_legs(
object
},
};
object.insert("skipFeeReason".to_string(), serde_json::Value::String(std::string::String::new()));
object.insert(
"feeMaterialized".to_string(),
serde_json::Value::Bool(true),
"skipFeeReason".to_string(),
serde_json::Value::String(std::string::String::new()),
);
object.insert("feeMaterialized".to_string(), serde_json::Value::Bool(true));
object.insert(
"feeMaterializationSource".to_string(),
serde_json::Value::String("fee_event_amounts".to_string()),
@@ -2217,18 +2576,9 @@ fn fee_amount_leg_payload_json(
"legIndex".to_string(),
serde_json::Value::Number(serde_json::Number::from(u64::from(leg_index))),
);
object.insert(
"tokenMint".to_string(),
serde_json::Value::String(leg.token_mint.clone()),
);
object.insert(
"amountRaw".to_string(),
serde_json::Value::String(leg.amount_raw.clone()),
);
object.insert(
"amountSource".to_string(),
serde_json::Value::String(leg.source.clone()),
);
object.insert("tokenMint".to_string(), serde_json::Value::String(leg.token_mint.clone()));
object.insert("amountRaw".to_string(), serde_json::Value::String(leg.amount_raw.clone()));
object.insert("amountSource".to_string(), serde_json::Value::String(leg.source.clone()));
match leg.source_account.as_ref() {
Some(source_account) => {
object.insert(
@@ -2250,7 +2600,6 @@ fn fee_amount_leg_payload_json(
return serde_json::Value::Object(object).to_string();
}
fn fee_event_amount_leg_from_parent_amount(
decoded_event: &crate::DexDecodedEventDto,
transaction_id: i64,
@@ -2272,7 +2621,11 @@ fn fee_event_amount_leg_from_parent_amount(
if fee_amount_raw.trim().is_empty() {
return None;
}
let payload_json = fee_amount_parent_payload_json(decoded_event, fee_token_mint.as_str(), fee_amount_raw.as_str());
let payload_json = fee_amount_parent_payload_json(
decoded_event,
fee_token_mint.as_str(),
fee_amount_raw.as_str(),
);
return Some(crate::FeeEventAmountDto::new(
0,
transaction_id,
@@ -2302,14 +2655,8 @@ fn fee_amount_parent_payload_json(
"legIndex".to_string(),
serde_json::Value::Number(serde_json::Number::from(0_u64)),
);
object.insert(
"tokenMint".to_string(),
serde_json::Value::String(token_mint.to_string()),
);
object.insert(
"amountRaw".to_string(),
serde_json::Value::String(amount_raw.to_string()),
);
object.insert("tokenMint".to_string(), serde_json::Value::String(token_mint.to_string()));
object.insert("amountRaw".to_string(), serde_json::Value::String(amount_raw.to_string()));
object.insert(
"amountSource".to_string(),
serde_json::Value::String("parent_fee_event_amount".to_string()),
@@ -2329,14 +2676,8 @@ fn decoded_payload_with_unmaterialized_fee_reason(
object
},
};
object.insert(
"skipFeeReason".to_string(),
serde_json::Value::String(reason.to_string()),
);
object.insert(
"feeMaterialized".to_string(),
serde_json::Value::Bool(false),
);
object.insert("skipFeeReason".to_string(), serde_json::Value::String(reason.to_string()));
object.insert("feeMaterialized".to_string(), serde_json::Value::Bool(false));
object.insert(
"feeMaterializationSource".to_string(),
serde_json::Value::String("none".to_string()),
@@ -2368,10 +2709,7 @@ fn decoded_payload_with_unmaterialized_fee_amount_recovery_reason(
"feeAmountRecoveryReason".to_string(),
serde_json::Value::String(reason.to_string()),
);
object.insert(
"feeAmountLegsMaterialized".to_string(),
serde_json::Value::Bool(false),
);
object.insert("feeAmountLegsMaterialized".to_string(), serde_json::Value::Bool(false));
object.insert(
"feeAmountLegCount".to_string(),
serde_json::Value::Number(serde_json::Number::from(0_u64)),
@@ -2387,7 +2725,8 @@ fn resolve_meteora_dbc_actual_fee_amount_from_lamport_delta(
if !decoded_event.event_kind.contains("pool_creation_fee") {
return None;
}
let transaction_value = serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
let transaction_value =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
let transaction_value = match transaction_value {
Ok(transaction_value) => transaction_value,
Err(_) => return None,
@@ -2408,7 +2747,8 @@ fn resolve_meteora_dbc_actual_fee_amount_from_lamport_delta(
append_unique_candidate_account(&mut candidate_accounts, decoded_event.pool_account.as_deref());
let payload_pool_account = extract_first_string(payload, &["poolAccount", "pool", "poolState"]);
append_unique_candidate_account(&mut candidate_accounts, payload_pool_account.as_deref());
let payload_related_account = extract_first_string(payload, &["relatedAccount", "receiver", "feeReceiver"]);
let payload_related_account =
extract_first_string(payload, &["relatedAccount", "receiver", "feeReceiver"]);
append_unique_candidate_account(&mut candidate_accounts, payload_related_account.as_deref());
if let Some(accounts) = payload.get("accounts").and_then(serde_json::Value::as_array) {
for account in accounts {
@@ -3325,6 +3665,155 @@ mod tests {
super::fee_amount_recovery_policy_for_event_kind("meteora_dbc.claim_trading_fee"),
super::FeeAmountRecoveryPolicy::Disabled
);
assert_eq!(
super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_fee"),
super::FeeAmountRecoveryPolicy::InnerSplTransfer
);
assert_eq!(
super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_fee2"),
super::FeeAmountRecoveryPolicy::InnerSplTransfer
);
assert_eq!(
super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.composition_fee_event"),
super::FeeAmountRecoveryPolicy::InnerSplTransfer
);
assert_eq!(
super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.withdraw_protocol_fee"),
super::FeeAmountRecoveryPolicy::InnerSplTransfer
);
assert_eq!(
super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.zap_protocol_fee"),
super::FeeAmountRecoveryPolicy::InnerSplTransfer
);
assert_eq!(
super::fee_amount_recovery_policy_for_event_kind(
"meteora_dlmm.close_claim_fee_operator_account"
),
super::FeeAmountRecoveryPolicy::Disabled
);
assert_eq!(
super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.swap_event"),
super::FeeAmountRecoveryPolicy::Disabled
);
}
#[test]
fn reward_amount_recovery_policy_is_allowlisted_only() {
assert_eq!(
super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_reward"),
super::FeeAmountRecoveryPolicy::InnerSplTransfer
);
assert_eq!(
super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_reward2"),
super::FeeAmountRecoveryPolicy::InnerSplTransfer
);
assert_eq!(
super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_reward_event"),
super::FeeAmountRecoveryPolicy::InnerSplTransfer
);
assert_eq!(
super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.fund_reward"),
super::FeeAmountRecoveryPolicy::InnerSplTransfer
);
assert_eq!(
super::reward_amount_recovery_policy_for_event_kind(
"meteora_dlmm.update_fees_and_rewards"
),
super::FeeAmountRecoveryPolicy::Disabled
);
assert_eq!(
super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.swap_event"),
super::FeeAmountRecoveryPolicy::Disabled
);
}
#[test]
fn single_mint_reward_amount_sums_matching_mints_only() {
let legs = vec![
super::MeteoraDbcActualAmount {
token_mint: "RewardMint111".to_string(),
amount_raw: "7".to_string(),
source: "test".to_string(),
source_account: Some("Source111".to_string()),
destination_account: Some("Destination111".to_string()),
},
super::MeteoraDbcActualAmount {
token_mint: "RewardMint111".to_string(),
amount_raw: "5".to_string(),
source: "test".to_string(),
source_account: Some("Source222".to_string()),
destination_account: Some("Destination222".to_string()),
},
];
let resolved = super::single_mint_amount_from_actual_amounts(
legs.as_slice(),
"allowlisted_reward_inner_spl_transfer",
);
let resolved = match resolved {
Some(resolved) => resolved,
None => panic!("expected single mint reward amount"),
};
assert_eq!(resolved.token_mint, "RewardMint111");
assert_eq!(resolved.amount_raw, "12");
assert_eq!(resolved.source, "allowlisted_reward_inner_spl_transfer");
let mixed_mint_legs = vec![
super::MeteoraDbcActualAmount {
token_mint: "RewardMint111".to_string(),
amount_raw: "7".to_string(),
source: "test".to_string(),
source_account: None,
destination_account: None,
},
super::MeteoraDbcActualAmount {
token_mint: "OtherMint111".to_string(),
amount_raw: "5".to_string(),
source: "test".to_string(),
source_account: None,
destination_account: None,
},
];
assert!(
super::single_mint_amount_from_actual_amounts(
mixed_mint_legs.as_slice(),
"allowlisted_reward_inner_spl_transfer",
)
.is_none()
);
}
#[test]
fn decoded_payload_with_materialized_reward_amount_marks_source() {
let payload = serde_json::json!({
"eventKind": "meteora_dlmm.claim_reward"
});
let reward_amount = super::MeteoraDbcActualAmount {
token_mint: "RewardMint111".to_string(),
amount_raw: "42".to_string(),
source: "allowlisted_reward_inner_spl_transfer".to_string(),
source_account: Some("Source111".to_string()),
destination_account: Some("Destination111".to_string()),
};
let materialized =
super::decoded_payload_with_materialized_reward_amount(&payload, &reward_amount);
assert_eq!(
materialized.get("rewardTokenMint").and_then(serde_json::Value::as_str),
Some("RewardMint111")
);
assert_eq!(
materialized.get("rewardAmountRaw").and_then(serde_json::Value::as_str),
Some("42")
);
assert_eq!(
materialized
.get("rewardMaterializationSource")
.and_then(serde_json::Value::as_str),
Some("allowlisted_reward_inner_spl_transfer")
);
assert_eq!(
materialized.get("eventActionability").and_then(serde_json::Value::as_str),
Some("reward_materialized")
);
}
#[test]

View File

@@ -196,7 +196,9 @@ WHERE de.protocol_name = 'meteora_dlmm'
'meteora_dlmm.swap_exact_out',
'meteora_dlmm.swap_exact_out2',
'meteora_dlmm.swap_with_price_impact',
'meteora_dlmm.swap_with_price_impact2'
'meteora_dlmm.swap_with_price_impact2',
'meteora_dlmm.swap_event',
'meteora_dlmm.swap2_evt'
)
GROUP BY de.event_kind
HAVING trade_count > 0 OR candle_count > 0
@@ -268,7 +270,32 @@ GROUP BY de.event_kind
HAVING fee_count > 0 OR reward_count > 0
ORDER BY de.event_kind;
-- 13. Global watchlist after replay.
-- 13. Limit/orderbook no trade/candle double-count. Target: empty.
SELECT
de.event_kind,
COUNT(DISTINCT te.id) AS trade_count,
COUNT(DISTINCT pc.id) AS candle_count,
COUNT(DISTINCT obe.id) AS orderbook_count,
MIN(tx.signature) AS sample_signature
FROM k_sol_dex_decoded_events de
JOIN k_sol_chain_transactions tx ON tx.id = de.transaction_id
LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id
LEFT JOIN k_sol_pair_candles pc ON pc.pair_id = te.pair_id
LEFT JOIN k_sol_orderbook_events obe ON obe.decoded_event_id = de.id
WHERE de.protocol_name = 'meteora_dlmm'
AND (
de.event_kind LIKE '%limit_order%'
OR de.event_kind IN (
'meteora_dlmm.place_limit_order_evt',
'meteora_dlmm.cancel_limit_order_evt',
'meteora_dlmm.close_limit_order_evt'
)
)
GROUP BY de.event_kind
HAVING trade_count > 0 OR candle_count > 0
ORDER BY trade_count DESC, candle_count DESC, de.event_kind;
-- 14. Global watchlist after replay.
SELECT
COALESCE(json_extract(de.payload_json, '$.upstreamDecoderCode'), de.protocol_name) AS backlog_decoder,
de.event_kind,
@@ -283,3 +310,63 @@ WHERE de.protocol_name IN ('upstream_git', 'meteora_dlmm')
GROUP BY backlog_decoder, de.event_kind, upstream_entry_name, upstream_discriminator_hex
ORDER BY decoded_count DESC, backlog_decoder, de.event_kind
LIMIT 100;
-- 15. Coverage observed > materialized explained by failed transactions.
-- The final accepted residual is close_bin_array: 16 observed / 14 materialized,
-- with 2 failed transactions Custom 6015 and 0 business materialization on failed tx.
SELECT
ce.entry_name,
ce.entry_kind,
ce.event_family,
ce.expected_db_target,
ce.local_event_kind,
ce.discriminator_hex,
ce.observed_count,
ce.materialized_count,
ce.observed_count - ce.materialized_count AS missing_count
FROM k_sol_dex_event_coverage_entries ce
WHERE ce.decoder_code = 'meteora_dlmm'
AND ce.observed_count > ce.materialized_count
AND ce.observed_count > 0
ORDER BY missing_count DESC, ce.entry_kind, ce.entry_name, ce.discriminator_hex;
-- 16. close_bin_array success/failed split.
-- Final acceptable result:
-- success rows -> decoded_count = lifecycle_count = 14
-- failed rows -> decoded_count = 2, lifecycle_count = 0
SELECT
de.event_kind,
tx.err_json,
COUNT(*) AS decoded_count,
COUNT(DISTINCT ple.id) AS lifecycle_count,
MIN(tx.signature) AS sample_signature
FROM k_sol_dex_decoded_events de
JOIN k_sol_chain_transactions tx
ON tx.id = de.transaction_id
LEFT JOIN k_sol_pool_lifecycle_events ple
ON ple.decoded_event_id = de.id
WHERE de.protocol_name = 'meteora_dlmm'
AND de.event_kind = 'meteora_dlmm.close_bin_array'
GROUP BY de.event_kind, tx.err_json
ORDER BY decoded_count DESC;
-- 17. Logical duplicate coverage rows. Target: empty.
SELECT
decoder_code,
COALESCE(program_id, '') AS program_id,
entry_kind,
entry_name,
COALESCE(discriminator_hex, '') AS discriminator_hex,
COALESCE(local_event_kind, '') AS local_event_kind,
COUNT(*) AS duplicate_count
FROM k_sol_dex_event_coverage_entries
WHERE decoder_code = 'meteora_dlmm'
GROUP BY
decoder_code,
COALESCE(program_id, ''),
entry_kind,
entry_name,
COALESCE(discriminator_hex, ''),
COALESCE(local_event_kind, '')
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC, entry_kind, entry_name, discriminator_hex;