This commit is contained in:
2026-06-11 17:22:55 +02:00
parent bfdb2e69ae
commit 38f42da970
23 changed files with 2650 additions and 35 deletions

View File

@@ -84,3 +84,4 @@
0.7.50 - Raydium Launchpad event coverage bootstrap : normalisation locale canonique vers `raydium_launchpad`, ajout de `RAYDIUM_LAUNCHPAD_PROGRAM_ID`, synchronisation des entrées Carbon Launchpad dans le registre upstream, fallback audit/mapped decoder pour discriminants Launchpad, enrichissement audit Anchor self-CPI, maintien conservatoire en `decoded_events_only`, rapport Launchpad et SQL de validation dédiés. 0.7.50 - Raydium Launchpad event coverage bootstrap : normalisation locale canonique vers `raydium_launchpad`, ajout de `RAYDIUM_LAUNCHPAD_PROGRAM_ID`, synchronisation des entrées Carbon Launchpad dans le registre upstream, fallback audit/mapped decoder pour discriminants Launchpad, enrichissement audit Anchor self-CPI, maintien conservatoire en `decoded_events_only`, rapport Launchpad et SQL de validation dédiés.
0.7.50-pre-r2 - Clôture CPMM/CLMM post-Launchpad : ajout des entrées Carbon `cpi_event` pour `raydium_cpmm` et `raydium_clmm`, ajout de `raydium_clmm.update_dynamic_fee_config`, normalisation des Program-data events CLMM, ajout de la table `k_sol_token_account_events` et de la matérialisation `create_support_mint_associated`, reclassement des familles ambiguës (`cpi_transport`, `liquidity_calculation`, `liquidity_change`, `position_open`, `pool_create`, `admin_config`, `account_create`, `idl_management`), codage du discriminant CPMM `40f4bc78a7e9690a` comme `raydium_cpmm.anchor_idl_instruction` decoded-only après inspection Solscan, et contexte de secours pour matérialisation liquidity CLMM via événements frères quand possible. 0.7.50-pre-r2 - Clôture CPMM/CLMM post-Launchpad : ajout des entrées Carbon `cpi_event` pour `raydium_cpmm` et `raydium_clmm`, ajout de `raydium_clmm.update_dynamic_fee_config`, normalisation des Program-data events CLMM, ajout de la table `k_sol_token_account_events` et de la matérialisation `create_support_mint_associated`, reclassement des familles ambiguës (`cpi_transport`, `liquidity_calculation`, `liquidity_change`, `position_open`, `pool_create`, `admin_config`, `account_create`, `idl_management`), codage du discriminant CPMM `40f4bc78a7e9690a` comme `raydium_cpmm.anchor_idl_instruction` decoded-only après inspection Solscan, et contexte de secours pour matérialisation liquidity CLMM via événements frères quand possible.
0.7.51 - Raydium AMM v4 event coverage clôturé : decoder maximal local pour tous les discriminants officiels AMM v4 `00..11`, spécialisation des swaps `swap_base_in/out` et `swap_base_in/out_v2`, suppression durable du `raydium_amm_v4.swap` legacy, index AMM v4 en discriminant 1 octet, matérialisation validée des swaps, liquidity, lifecycle, fees, admin/config et side effects orderbook, `pre_initialize` conservé comme lifecycle audit deprecated/partial, `simulate_info` decoded-only, reset replay renforcé par `protocol_name`, validation des invariants failed/non-swap/single-target/unexplained gaps et maintien de `raydium_pool_v4` en audit conditionnel sans decoder autonome. 0.7.51 - Raydium AMM v4 event coverage clôturé : decoder maximal local pour tous les discriminants officiels AMM v4 `00..11`, spécialisation des swaps `swap_base_in/out` et `swap_base_in/out_v2`, suppression durable du `raydium_amm_v4.swap` legacy, index AMM v4 en discriminant 1 octet, matérialisation validée des swaps, liquidity, lifecycle, fees, admin/config et side effects orderbook, `pre_initialize` conservé comme lifecycle audit deprecated/partial, `simulate_info` decoded-only, reset replay renforcé par `protocol_name`, validation des invariants failed/non-swap/single-target/unexplained gaps et maintien de `raydium_pool_v4` en audit conditionnel sans decoder autonome.
0.7.52 - Raydium Stable Swap event coverage clôturé : decoder legacy 1 octet pour la surface locale `00..0d`, matérialisation lifecycle/liquidity/admin/fee/orderbook selon contexte, swaps `swap_base_in/out` matérialisés uniquement depuis deltas de vaults exacts (`stable_swap_vault_balance_delta`), conservation des bornes dinstruction comme audit-only, failed transactions decoded-only avec skip reasons, validation locale 407 tests et clippy `-D warnings` OK.

View File

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

View File

@@ -78,7 +78,7 @@ non-swap CLMM avec trade_count > 0 = 0
Les 11 Anchor / `Program data` events CLMM restent listés en `upstream_git_unverified` car aucun corpus local ne les observe encore. Le code est préparé pour les accueillir en audit-only lorsquils apparaîtront dans un corpus local, sans créer de trade/candle par défaut. Les 11 Anchor / `Program data` events CLMM restent listés en `upstream_git_unverified` car aucun corpus local ne les observe encore. Le code est préparé pour les accueillir en audit-only lorsquils apparaîtront dans un corpus local, sans créer de trade/candle par défaut.
La tranche `0.7.51 raydium_amm_v4` est maintenant validée côté `kb_lib`. La suite de roadmap reprend avec `0.7.52 raydium_stable`, tandis que `raydium_pool_v4` reste un audit conditionnel ultérieur et ne doit pas être promu sans confirmation de program id/rôle/corpus. Les tranches `0.7.51 raydium_amm_v4` et `0.7.52 raydium_stable_swap` sont maintenant validées côté `kb_lib`. La suite de roadmap reprend avec les rechecks conditionnels et les surfaces restantes, tandis que `raydium_pool_v4` reste un audit conditionnel ultérieur et ne doit pas être promu sans confirmation de program id/rôle/corpus.
## Organisation documentaire ## Organisation documentaire
@@ -320,7 +320,7 @@ Chaque DEX ou variante de DEX doit avoir sa propre étape de validation. Les fam
À garder dans la matrice mais sans bloquer les versions immédiates : À garder dans la matrice mais sans bloquer les versions immédiates :
- `raydium_stable_swap` tant que son usage réel nest pas démontré par corpus local ; - `raydium_stable_swap` est désormais démontré par corpus local en `0.7.52` ; le garder comme DEX effectif supporté, avec surveillance des nouveaux discriminants ;
- vieux programmes legacy uniquement utiles pour compatibilité ou replay historique ; - vieux programmes legacy uniquement utiles pour compatibilité ou replay historique ;
- agrégateurs/routeurs comme `okx_dex` tant quils ne correspondent pas à un DEX direct matérialisable ; - agrégateurs/routeurs comme `okx_dex` tant quils ne correspondent pas à un DEX direct matérialisable ;
- entrées ambiguës comme `zora` tant quaucun programme Solana pertinent nest prouvé. - entrées ambiguës comme `zora` tant quaucun programme Solana pertinent nest prouvé.
@@ -433,7 +433,7 @@ La priorité immédiate après le point de reprise `0.7.43-E5C` est :
2. `0.7.49` : `raydium_clmm` — clôturé côté instructions observées, matérialisation non-trade prouvée et nettoyage fallback ; 2. `0.7.49` : `raydium_clmm` — clôturé côté instructions observées, matérialisation non-trade prouvée et nettoyage fallback ;
3. `0.7.50-pre-r2` : `raydium_launchpad` clos + re-vérification `raydium_cpmm` / `raydium_clmm` ; 3. `0.7.50-pre-r2` : `raydium_launchpad` clos + re-vérification `raydium_cpmm` / `raydium_clmm` ;
4. `0.7.51` : `raydium_amm_v4` ; 4. `0.7.51` : `raydium_amm_v4` ;
5. `0.7.52` : `raydium_stable` ; 5. `0.7.52` : `raydium_stable_swap` — clôturé ;
6. `0.7.53` : `raydium_pool_v4` audit / program-id decision seulement si program id distinct et corpus exploitable ; 6. `0.7.53` : `raydium_pool_v4` audit / program-id decision seulement si program id distinct et corpus exploitable ;
7. `0.7.54` : `pump_swap` ; 7. `0.7.54` : `pump_swap` ;
8. `0.7.55` : `pump_fun` ; 8. `0.7.55` : `pump_fun` ;
@@ -546,7 +546,7 @@ La suite fonctionnelle reprend par Raydium avant Meteora :
2. `0.7.49``raydium_clmm` ; 2. `0.7.49``raydium_clmm` ;
3. `0.7.50-pre-r2``raydium_launchpad` + clôture CPMM/CLMM ; 3. `0.7.50-pre-r2``raydium_launchpad` + clôture CPMM/CLMM ;
4. `0.7.51``raydium_amm_v4` ; 4. `0.7.51``raydium_amm_v4` ;
5. `0.7.52``raydium_stable` ; 5. `0.7.52``raydium_stable_swap` — clôturé ;
6. `0.7.53``raydium_pool_v4` audit conditionnel, sans promotion automatique ; 6. `0.7.53``raydium_pool_v4` audit conditionnel, sans promotion automatique ;
7. `0.7.54+` — Pump, Meteora, Phoenix/OpenBook, Orca puis les autres DEX/surfaces. 7. `0.7.54+` — Pump, Meteora, Phoenix/OpenBook, Orca puis les autres DEX/surfaces.
@@ -632,3 +632,36 @@ Cette tranche complète la clôture Raydium en ajoutant `cpi_event` pour CPMM/CL
Le discriminant CPMM `40f4bc78a7e9690a` est désormais codé comme `raydium_cpmm.anchor_idl_instruction` : les signatures inspectées correspondent aux instructions Anchor `IdlCreateAccount` / `IdlCloseAccount`, donc il reste `decoded_events_only` et ne matérialise aucune table métier. Le discriminant CPMM `40f4bc78a7e9690a` est désormais codé comme `raydium_cpmm.anchor_idl_instruction` : les signatures inspectées correspondent aux instructions Anchor `IdlCreateAccount` / `IdlCloseAccount`, donc il reste `decoded_events_only` et ne matérialise aucune table métier.
Rapport de clôture : `docs/reports/RAYDIUM_CPMM_CLMM_RECHECK_REPORT_0_7_50_PRE_R2.md`. Rapport de clôture : `docs/reports/RAYDIUM_CPMM_CLMM_RECHECK_REPORT_0_7_50_PRE_R2.md`.
## Tranche clôturée — 0.7.52 raydium_stable_swap
`0.7.52` clôture Raydium Stable Swap avec le code local canonique `raydium_stable_swap` et le program id `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h`.
Décisions finales :
- Stable Swap est décodé en layout legacy **1 octet**.
- La surface locale observée `00..0d` est couverte : lifecycle, model setup, admin/config, liquidity, orderbook side effects, fees et swaps.
- `swap_base_in` / `swap_base_out` produisent trades/candles uniquement avec des montants exacts dérivés des deltas de vaults (`amountSource=stable_swap_vault_balance_delta`).
- Les arguments dinstruction `amountInRaw`, `minimumAmountOutRaw`, `maxAmountInRaw`, `amountOutRaw` sont conservés comme bornes dinstruction, mais ne sont pas utilisés comme prix/montants exacts.
- Les transactions failed restent decoded-only avec `skipTradeReason=failed_transaction` et `skipCandleReason=failed_transaction`.
Livrables de clôture :
- `kb_lib/src/dex/raydium_stable_swap.rs`
- `docs/reports/RAYDIUM_STABLE_SWAP_EVENT_COVERAGE_REPORT.md`
- `validation_sql/SQL_VALIDATION_RAYDIUM_STABLE_SWAP_0_7_52.sql`
Validation locale finale :
```text
cargo test -p kb_lib -> 407 passed, 0 failed
cargo clippy -p kb_lib --all-targets -- -D warnings -> ok
```
Replay final observé :
```text
replayed=298, trades=290, liquidity=16, lifecycle=4, candle_upserts=1160,
instructionObservations=5317, catalog=40 tokens / 59 pools / 59 pairs
```
Statut : **clôturé côté code et validation locale**.

View File

@@ -36,7 +36,7 @@ Règles de planification :
| `0.7.49` | `raydium_clmm` | Clôturé : 33 instructions observées/décodées, orderbook CLMM, liquidity/fee/reward/admin/lifecycle, fallbacks upstream nettoyés, 11 Program-data events préparés mais non observés. | | `0.7.49` | `raydium_clmm` | Clôturé : 33 instructions observées/décodées, orderbook CLMM, liquidity/fee/reward/admin/lifecycle, fallbacks upstream nettoyés, 11 Program-data events préparés mais non observés. |
| `0.7.50` | `raydium_launchpad` | Bootstrap ouvert : surface LaunchLab/Launchpad, discriminants Carbon/IDL, fallback audit, SQL de validation, aucune matérialisation métier sans corpus. | | `0.7.50` | `raydium_launchpad` | Bootstrap ouvert : surface LaunchLab/Launchpad, discriminants Carbon/IDL, fallback audit, SQL de validation, aucune matérialisation métier sans corpus. |
| `0.7.51` | `raydium_amm_v4` | Clôturé : decoder maximal `00..11`, swaps spécialisés, lifecycle/liquidity/fees/admin/orderbook, `pre_initialize` audit, `simulate_info` decoded-only, cleanup legacy/fallback. | | `0.7.51` | `raydium_amm_v4` | Clôturé : decoder maximal `00..11`, swaps spécialisés, lifecycle/liquidity/fees/admin/orderbook, `pre_initialize` audit, `simulate_info` decoded-only, cleanup legacy/fallback. |
| `0.7.52` | `raydium_stable` | Reprendre Raydium Stable : program ids/IDL, swaps stables, pool lifecycle, liquidity, fees/admin, invariants pricing/candles. | | `0.7.52` | `raydium_stable_swap` | Clôturé : surface legacy `00..0d`, swaps via deltas vault exacts, failed tx decoded-only, invariants trade/candle propres. |
| `0.7.53` | `raydium_pool_v4` | Audit / program-id decision seulement : confirmer program id, rôle exact et corpus avant toute promotion métier. | | `0.7.53` | `raydium_pool_v4` | Audit / program-id decision seulement : confirmer program id, rôle exact et corpus avant toute promotion métier. |
| `0.7.54` | `pump_swap` | Couvrir `buy/sell` et tous les events auxiliaires disponibles : fees, cashback, volume accumulator, admin/config. | | `0.7.54` | `pump_swap` | Couvrir `buy/sell` et tous les events auxiliaires disponibles : fees, cashback, volume accumulator, admin/config. |
| `0.7.55` | `pump_fun` | Traiter launch/bonding/migration ; séparer création token, buy/sell bonding, migration vers DEX effectif. | | `0.7.55` | `pump_fun` | Traiter launch/bonding/migration ; séparer création token, buy/sell bonding, migration vers DEX effectif. |
@@ -855,7 +855,7 @@ Matrice cible initiale :
| `raydium_launchpad` | launch surface | planifié, program id local connu | ajouter decoder/materialization dédiée | | `raydium_launchpad` | launch surface | planifié, program id local connu | ajouter decoder/materialization dédiée |
| `raydium_amm_v4` | AMM legacy | partiel | corpus dédié après autres Raydium | | `raydium_amm_v4` | AMM legacy | partiel | corpus dédié après autres Raydium |
| `raydium_router` | router | partiel | ne pas matérialiser en trade direct avant preuve | | `raydium_router` | router | partiel | ne pas matérialiser en trade direct avant preuve |
| `raydium_stable_swap` | AMM legacy | planifié | traiter seulement si corpus pertinent | | `raydium_stable_swap` | AMM legacy | supporté / 0.7.52 clos | swaps depuis deltas vault exacts ; failed tx decoded-only |
| `meteora_dlmm` | DLMM | supporté | verrouiller corpus et non-régression | | `meteora_dlmm` | DLMM | supporté | verrouiller corpus et non-régression |
| `meteora_damm_v1` | AMM legacy | partiel | garder skip explicite sans payload montant/prix | | `meteora_damm_v1` | AMM legacy | partiel | garder skip explicite sans payload montant/prix |
| `meteora_damm_v2` | AMM | partiel | corpus et séparation events | | `meteora_damm_v2` | AMM | partiel | corpus et séparation events |
@@ -1006,7 +1006,7 @@ Réalisé :
- maintien des launch surfaces comme surfaces reportées et non prioritaires ; - maintien des launch surfaces comme surfaces reportées et non prioritaires ;
- ajout du profil `0.7.39_dex_first_effective_swap_surfaces` ; - ajout du profil `0.7.39_dex_first_effective_swap_surfaces` ;
- validation locale confirmée avec `validationPassed = true`, `blockingIssueCount = 0`, `actionableMissingTradeEventCount = 0` et `missingTradeEventCount = 0` ; - validation locale confirmée avec `validationPassed = true`, `blockingIssueCount = 0`, `actionableMissingTradeEventCount = 0` et `missingTradeEventCount = 0` ;
- confirmation par corpus local initial que Raydium CLMM est observé, tandis que Raydium AMM v4 et Stable Swap ne sont pas encore exploitables sans constitution de corpus dédiée. - confirmation par corpus local initial que Raydium CLMM est observé ; les tranches ultérieures ont ensuite clôturé Raydium AMM v4 en `0.7.51` et Stable Swap en `0.7.52`.
Décision : `0.7.39` est clos. La suite immédiate ne doit pas commencer par un décodeur Raydium AMM v4 sans corpus. Il faut dabord ajouter les outils de découverte on-chain et de backfill ciblé afin dobtenir des signatures, pools/state accounts, token mints et instructions exploitables. Décision : `0.7.39` est clos. La suite immédiate ne doit pas commencer par un décodeur Raydium AMM v4 sans corpus. Il faut dabord ajouter les outils de découverte on-chain et de backfill ciblé afin dobtenir des signatures, pools/state accounts, token mints et instructions exploitables.
@@ -1310,10 +1310,10 @@ Objectif : hisser AMM v4 legacy au niveau de couverture CPMM/CLMM.
Réalisé : decoder maximal AMM v4 pour tous les discriminants officiels `00..11`, spécialisation des swaps (`swap_base_in`, `swap_base_out`, `swap_base_in_v2`, `swap_base_out_v2`), suppression du legacy `raydium_amm_v4.swap`, observation locale de tous les discriminants, matérialisation validée des familles trade, liquidity, lifecycle, fee, admin/config et orderbook, `pre_initialize` conservé comme lifecycle audit deprecated/partial, `simulate_info` conservé en decoded-only, gaps successful non matérialisés expliqués, et validation des invariants failed/non-swap/single-target. Réalisé : decoder maximal AMM v4 pour tous les discriminants officiels `00..11`, spécialisation des swaps (`swap_base_in`, `swap_base_out`, `swap_base_in_v2`, `swap_base_out_v2`), suppression du legacy `raydium_amm_v4.swap`, observation locale de tous les discriminants, matérialisation validée des familles trade, liquidity, lifecycle, fee, admin/config et orderbook, `pre_initialize` conservé comme lifecycle audit deprecated/partial, `simulate_info` conservé en decoded-only, gaps successful non matérialisés expliqués, et validation des invariants failed/non-swap/single-target.
### 6.084. Version `0.7.52` — `raydium_stable` event coverage ### 6.084. Version `0.7.52` — `raydium_stable_swap` event coverage
Objectif : reprendre Raydium Stable comme troisième tranche Raydium post-CLMM. Objectif : reprendre Raydium Stable comme tranche Raydium dédiée après AMM v4.
À faire : vérifier program ids/IDL, swaps stables, liquidity, pool lifecycle, fees/admin/config, cohérence des montants/prix et absence de faux trades/candles. 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. Version `0.7.53` — `raydium_pool_v4` audit / program-id decision ### 6.085. Version `0.7.53` — `raydium_pool_v4` audit / program-id decision
Objectif : auditer `raydium_pool_v4.json` comme source IDL annexe, sans promotion métier automatique. Objectif : auditer `raydium_pool_v4.json` comme source IDL annexe, sans promotion métier automatique.
@@ -1566,7 +1566,7 @@ Ordre de travail recommandé pour la suite :
7. `0.7.49` : `raydium_clmm` — clos ; 7. `0.7.49` : `raydium_clmm` — clos ;
8. `0.7.50-pre-r2` : `raydium_launchpad` clos + re-vérification CPMM/CLMM ; 8. `0.7.50-pre-r2` : `raydium_launchpad` clos + re-vérification CPMM/CLMM ;
9. `0.7.51` : `raydium_amm_v4` ; 9. `0.7.51` : `raydium_amm_v4` ;
10. `0.7.52` : `raydium_stable` ; 10. `0.7.52` : `raydium_stable_swap` — clôturé ;
11. `0.7.53` : `raydium_pool_v4` audit conditionnel ; 11. `0.7.53` : `raydium_pool_v4` audit conditionnel ;
12. `0.7.54` : `pump_swap` ; 12. `0.7.54` : `pump_swap` ;
13. `0.7.55` : `pump_fun` ; 13. `0.7.55` : `pump_fun` ;
@@ -1649,7 +1649,7 @@ La tranche CPMM reconnaît désormais tous les discriminants instruction-level l
`0.7.48` est clôturable côté `raydium_cpmm`. Le decoder couvre les instructions/events CPMM listés par Carbon/fnzero/Raydium CP-Swap, avec matérialisation locale validée pour trades, liquidity, lifecycle, fees et admin/config. `swap_event` reste audit-only pour éviter les doublons avec `swap_base_input` / `swap_base_output`. Les side effects SPL Token / Token-2022 observés via Solscan (`burn`, `transfer`, `transferChecked`, `closeAccount`) restent hors decoder CPMM direct et alimenteront une réflexion transversale future. `0.7.48` est clôturable côté `raydium_cpmm`. Le decoder couvre les instructions/events CPMM listés par Carbon/fnzero/Raydium CP-Swap, avec matérialisation locale validée pour trades, liquidity, lifecycle, fees et admin/config. `swap_event` reste audit-only pour éviter les doublons avec `swap_base_input` / `swap_base_output`. Les side effects SPL Token / Token-2022 observés via Solscan (`burn`, `transfer`, `transferChecked`, `closeAccount`) restent hors decoder CPMM direct et alimenteront une réflexion transversale future.
La suite après `0.7.49 raydium_clmm` reprend en `0.7.50-pre-r2` par la clôture Launchpad et la re-vérification CPMM/CLMM, puis `0.7.51 raydium_amm_v4`, `0.7.52 raydium_stable` et `0.7.53 raydium_pool_v4` uniquement comme audit conditionnel, en gardant la même discipline : sources Git/IDL + Solscan pour accélérer la découverte, mais corpus local obligatoire avant toute promotion métier. La suite après `0.7.49 raydium_clmm` reprend en `0.7.50-pre-r2` par la clôture Launchpad et la re-vérification CPMM/CLMM, puis `0.7.51 raydium_amm_v4`, `0.7.52 raydium_stable_swap` et `0.7.53 raydium_pool_v4` uniquement comme audit conditionnel, en gardant la même discipline : sources Git/IDL + Solscan pour accélérer la découverte, mais corpus local obligatoire avant toute promotion métier.
## Clôture `0.7.51` — Raydium AMM v4 ## Clôture `0.7.51` — Raydium AMM v4
@@ -1674,4 +1674,52 @@ Décision `raydium_pool_v4` : ne pas ouvrir de decoder autonome dans cette tranc
Le rapport de décision est `docs/reports/RAYDIUM_POOL_V4_DECISION_NOTE.md`. Le rapport de décision est `docs/reports/RAYDIUM_POOL_V4_DECISION_NOTE.md`.
La suite reprend `0.7.52 raydium_stable`, après rechecks CPMM/CLMM/Launchpad si la base de validation les contient. La tranche `0.7.52 raydium_stable_swap` est clôturée ; la suite reprend sur les surfaces restantes ou les audits conditionnels selon le corpus disponible.
### Addendum final — `0.7.52 raydium_stable_swap`
Statut : **clôturé**.
Décision finale : `raydium_stable_swap` est le code local canonique pour le program id `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h`. Le decoder utilise un layout legacy 1 octet et couvre la surface localement observée `00..0d`.
Résultat de tranche :
- `initialize` matérialise le lifecycle quand le contexte est complet ;
- `init_model_data` reste decoded-only expliqué ;
- `update_model_data` matérialise `k_sol_pool_admin_events` ;
- `deposit` / `withdraw` matérialisent `k_sol_liquidity_events` ;
- `monitor_step` / `admin_cancel_orders` matérialisent `k_sol_orderbook_events` quand le contexte est complet ;
- `set_params` matérialise `k_sol_pool_admin_events` ;
- `withdraw_pnl` / `withdraw_srm` matérialisent `k_sol_fee_events` quand le contexte est complet ;
- `simulate_info` reste decoded-only ;
- `swap_base_in` / `swap_base_out` matérialisent trades/candles uniquement depuis `amountSource=stable_swap_vault_balance_delta` ;
- `stable_swap_instruction_bounds_only` reste decoded-only et ne matérialise pas de trade/candle ;
- les transactions failed restent decoded-only avec `failed_transaction`.
Validation finale :
```text
cargo test -p kb_lib -> 407 passed, 0 failed
cargo clippy -p kb_lib --all-targets -- -D warnings -> ok
```
Replay final observé :
```text
replayed=298, trades=290, liquidity=16, lifecycle=4, candle_upserts=1160,
instructionObservations=5317, catalog=40 tokens / 59 pools / 59 pairs
```
Clôture swap spécifique :
```text
swap_base_in stable_swap_vault_balance_delta success 171 decoded / 171 trades
swap_base_in stable_swap_instruction_bounds_only failed 27 decoded / 0 trades
swap_base_out stable_swap_vault_balance_delta success 4 decoded / 4 trades
swap_base_out stable_swap_instruction_bounds_only failed 2 decoded / 0 trades
```
SQL de validation : `validation_sql/SQL_VALIDATION_RAYDIUM_STABLE_SWAP_0_7_52.sql`.
Rapport : `docs/reports/RAYDIUM_STABLE_SWAP_EVENT_COVERAGE_REPORT.md`.

View File

@@ -369,3 +369,16 @@ Règles validées :
- les side effects SPL Token / Token-2022 restent transversaux. - les side effects SPL Token / Token-2022 restent transversaux.
Contrôle final AMM v4 : le SQL `materialized_target_count > 1` doit rester vide. Contrôle final AMM v4 : le SQL `materialized_target_count > 1` doit rester vide.
## 0.7.52 — Raydium Stable Swap DB model decision
No schema migration is introduced for `raydium_stable_swap` at tranche opening.
Stable Swap maps to existing DB targets:
- `initialize` / `pre_initialize``k_sol_pool_lifecycle_events` when pool context is sufficient;
- `deposit` / `withdraw``k_sol_liquidity_events` when pool/pair context is sufficient;
- `swap_base_in` / `swap_base_out``k_sol_trade_events` and candles only when mints and amounts are reliable;
- `swap_event``k_sol_dex_decoded_events` only until a corpus-backed materialization decision exists.
Side effects from SPL Token, Token-2022, Serum/OpenBook-style accounts or router transport remain transverse evidence and are not promoted as direct `raydium_stable_swap.*` business events without an explicit later DB decision.

View File

@@ -31,8 +31,8 @@ Cette matrice complète `kb_lib/src/dex_support_matrix.rs`. Elle documente **ce
| 1 | `raydium_cpmm` | `supported / 0.7.50-pre-r2 closure recheck` | Couverture CPMM clôturée : swaps, lifecycle, fees, admin/config, deposit/withdraw, `lp_change_event`, `swap_event` decoded-only, `cpi_event` transport Carbon et `anchor_idl_instruction` Solscan/manual pour `40f4bc78a7e9690a`. | Ne pas promouvoir `anchor_idl_instruction` : c'est de la gestion Anchor IDL, pas un événement AMM métier. | | 1 | `raydium_cpmm` | `supported / 0.7.50-pre-r2 closure recheck` | Couverture CPMM clôturée : swaps, lifecycle, fees, admin/config, deposit/withdraw, `lp_change_event`, `swap_event` decoded-only, `cpi_event` transport Carbon et `anchor_idl_instruction` Solscan/manual pour `40f4bc78a7e9690a`. | Ne pas promouvoir `anchor_idl_instruction` : c'est de la gestion Anchor IDL, pas un événement AMM métier. |
| 2 | `raydium_clmm` | `supported / 0.7.50-pre-r2 closure recheck` | Couverture CLMM complétée : `cpi_event`, `update_dynamic_fee_config`, Program-data events locaux, `create_support_mint_associated` vers `k_sol_token_account_events`, familles sans `unknown`, router/swap Program-data en decoded-only. | Rejouer la base CLMM et confirmer que les seuls résidus sont `decoded_events_only`, transactions failed ou absence prouvée de contexte pool/pair. | | 2 | `raydium_clmm` | `supported / 0.7.50-pre-r2 closure recheck` | Couverture CLMM complétée : `cpi_event`, `update_dynamic_fee_config`, Program-data events locaux, `create_support_mint_associated` vers `k_sol_token_account_events`, familles sans `unknown`, router/swap Program-data en decoded-only. | Rejouer la base CLMM et confirmer que les seuls résidus sont `decoded_events_only`, transactions failed ou absence prouvée de contexte pool/pair. |
| 3 | `raydium_launchpad` | `bootstrap / 0.7.50` | Surface canonique normalisée, 1 entrée programme + 26 discriminants Carbon/IDL listés, fallback audit/mapped decoder, SQL dédié. | Créer DB neuve, backfill par discriminant, replay forcé, promouvoir seulement après corpus local. | | 3 | `raydium_launchpad` | `bootstrap / 0.7.50` | Surface canonique normalisée, 1 entrée programme + 26 discriminants Carbon/IDL listés, fallback audit/mapped decoder, SQL dédié. | Créer DB neuve, backfill par discriminant, replay forcé, promouvoir seulement après corpus local. |
| 4 | `raydium_amm_v4` | `supported / 0.7.51 closed` | Decoder maximal AMM v4 `00..11`, swaps spécialisés, lifecycle/liquidity/fees/admin/orderbook validés. | Rechecks CPMM/CLMM/Launchpad puis `raydium_stable`. | | 4 | `raydium_amm_v4` | `supported / 0.7.51 closed` | Decoder maximal AMM v4 `00..11`, swaps spécialisés, lifecycle/liquidity/fees/admin/orderbook validés. | Stable Swap clôturé ensuite en `0.7.52`; surveiller les surfaces restantes. |
| 5 | `raydium_stable_swap` | `planned / 0.7.52` | Entrée conservée. | Reprendre Stable séparément : swaps stables, pool lifecycle, liquidity, fees/admin, montants/prix exploitables. | | 5 | `raydium_stable_swap` | `supported / 0.7.52 closed` | Decoder legacy 1 octet, surface `00..0d`, swaps matérialisés depuis deltas vault exacts. | Surveiller seulement de nouveaux discriminants ou `swap_event` observé. |
| 6 | `raydium_pool_v4` | `to_verify / 0.7.53 conditional audit` | IDL annexe mentionnée par fnzero, non présente dans l'archive locale, pas de program id/rôle confirmé ici. | Ne pas promouvoir tant que program id distinct, rôle exact et corpus exploitable ne sont pas confirmés. | | 6 | `raydium_pool_v4` | `to_verify / 0.7.53 conditional audit` | IDL annexe mentionnée par fnzero, non présente dans l'archive locale, pas de program id/rôle confirmé ici. | Ne pas promouvoir tant que program id distinct, rôle exact et corpus exploitable ne sont pas confirmés. |
| 7 | `pump_swap` | `supported / 0.7.54 planned` | `buy`/`sell` décodés et matérialisés ; trade/candle OK. | Ajouter tous les events Carbon/Solana Streamer : cashback, fee, volume accumulator, admin/config ; conserver les non-trades hors candles. | | 7 | `pump_swap` | `supported / 0.7.54 planned` | `buy`/`sell` décodés et matérialisés ; trade/candle OK. | Ajouter tous les events Carbon/Solana Streamer : cashback, fee, volume accumulator, admin/config ; conserver les non-trades hors candles. |
| 8 | `pump_fun` | `partial / 0.7.55 launch_surface` | Création/token launch partiellement décodée ; intégrée au pipeline de listings. | Traiter tous les events Pump.fun disponibles : buy/sell/migrate/create/update ; séparer bonding/launch de DEX effectif ; valider migration vers PumpSwap. | | 8 | `pump_fun` | `partial / 0.7.55 launch_surface` | Création/token launch partiellement décodée ; intégrée au pipeline de listings. | Traiter tous les events Pump.fun disponibles : buy/sell/migrate/create/update ; séparer bonding/launch de DEX effectif ; valider migration vers PumpSwap. |
@@ -99,7 +99,7 @@ Un event peut devenir `materialized` uniquement si :
| `raydium_launchpad` | `launch_surface` | `launch` | `known` | non | oui | non | `bootstrap` | decoded_events_only_until_local_corpus | | `raydium_launchpad` | `launch_surface` | `launch` | `known` | non | oui | non | `bootstrap` | decoded_events_only_until_local_corpus |
| `raydium_liquidity_locking` | `to_verify` | `liquidity_locking` | `to_verify` | non | non | non | `to_verify` | upstream_git_program_id_requires_local_corpus_verification | | `raydium_liquidity_locking` | `to_verify` | `liquidity_locking` | `to_verify` | non | non | non | `to_verify` | upstream_git_program_id_requires_local_corpus_verification |
| `raydium_router` | `aggregator_router` | `router` | `known` | non | non | non | `partial` | router_not_materialized_as_direct_trade_surface | | `raydium_router` | `aggregator_router` | `router` | `known` | non | non | non | `partial` | router_not_materialized_as_direct_trade_surface |
| `raydium_stable_swap` | `dex_effective` | `AMM` | `known` | non | non | non | `planned` | deprecated_program_not_prioritized | | `raydium_stable_swap` | `dex_effective` | `AMM` | `known` | oui | oui | oui | `supported` | 0.7.52 closed; swaps via `stable_swap_vault_balance_delta` uniquement |
| `meteora_dlmm` | `dex_effective` | `DLMM` | `known` | oui | oui | oui | `supported` | | | `meteora_dlmm` | `dex_effective` | `DLMM` | `known` | oui | oui | oui | `supported` | |
| `meteora_dlc` | `to_verify` | `unknown` | `to_verify` | non | non | non | `to_verify` | surface_and_program_id_to_verify | | `meteora_dlc` | `to_verify` | `unknown` | `to_verify` | non | non | non | `to_verify` | surface_and_program_id_to_verify |
| `meteora_damm_v1` | `dex_effective` | `AMM` | `known` | oui | oui | non | `partial` | meteora_damm_v1_swap_without_amount_payload | | `meteora_damm_v1` | `dex_effective` | `AMM` | `known` | oui | oui | non | `partial` | meteora_damm_v1_swap_without_amount_payload |
@@ -266,3 +266,10 @@ La clôture `0.7.50-pre-r2` complète les tranches `0.7.48` et `0.7.49` sans rou
La tranche a été validée sur base SQLite dédiée : tous les discriminants `00..11` sont observés localement. Les gaps de matérialisation restants sont expliqués par decoded-only, transaction failed ou absence de catalogue/deltas exploitables. La tranche a été validée sur base SQLite dédiée : tous les discriminants `00..11` sont observés localement. Les gaps de matérialisation restants sont expliqués par decoded-only, transaction failed ou absence de catalogue/deltas exploitables.
## 0.7.52 — Raydium Stable Swap
| decoder_code | program id | status | layout | notes |
|---|---|---|---|---|
| `raydium_stable_swap` | `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h` | supported / closed | legacy 1 octet | Surface locale `00..0d` couverte ; swaps `swap_base_in/out` matérialisés uniquement depuis deltas vault exacts ; instruction bounds et failed tx restent decoded-only. |

View File

@@ -189,3 +189,28 @@ Validation locale finale : tous les discriminants AMM v4 officiels `00..11` sont
| `cpi/informational` | `simulate_info` | observed decoded-only | `k_sol_dex_decoded_events_only` | Audit technique uniquement. | | `cpi/informational` | `simulate_info` | observed decoded-only | `k_sol_dex_decoded_events_only` | Audit technique uniquement. |
| `token side effects` | SPL Token / Token-2022 inner instructions | transversal | decoded-only actuellement | Ne pas promouvoir comme AMM v4 direct. | | `token side effects` | SPL Token / Token-2022 inner instructions | transversal | decoded-only actuellement | Ne pas promouvoir comme AMM v4 direct. |
| `unknown/unmapped audit` | residual `raydium_amm_v4.instruction_audit` | vide | decoded-only si futur inconnu | Tout residual doit être expliqué avant promotion. | | `unknown/unmapped audit` | residual `raydium_amm_v4.instruction_audit` | vide | decoded-only si futur inconnu | Tout residual doit être expliqué avant promotion. |
## 0.7.52 — `raydium_stable_swap`
Status: **closed on local corpus**.
| entry | discriminator | family | expected target | local event kind | status |
|---|---:|---|---|---|---|
| `initialize` | `00` | `pool_create` | `k_sol_pool_lifecycle_events` | `raydium_stable_swap.initialize` | observed/materialized when context complete |
| `init_model_data` | `01` | `model_setup` | decoded-only | `raydium_stable_swap.init_model_data` | observed decoded-only / explained |
| `update_model_data` | `02` | `admin_config` | `k_sol_pool_admin_events` | `raydium_stable_swap.update_model_data` | observed/materialized |
| `deposit` | `03` | `liquidity_add` | `k_sol_liquidity_events` | `raydium_stable_swap.deposit` | observed/materialized |
| `withdraw` | `04` | `liquidity_remove` | `k_sol_liquidity_events` | `raydium_stable_swap.withdraw` | observed/materialized |
| `monitor_step` | `05` | `order_place` | `k_sol_orderbook_events` | `raydium_stable_swap.monitor_step` | observed/materialized |
| `set_params` | `06` | `admin_config` | `k_sol_pool_admin_events` | `raydium_stable_swap.set_params` | observed/materialized |
| `withdraw_pnl` | `07` | `fee` | `k_sol_fee_events` | `raydium_stable_swap.withdraw_pnl` | observed/materialized |
| `withdraw_srm` | `08` | `fee` | `k_sol_fee_events` | `raydium_stable_swap.withdraw_srm` | observed/materialized when context complete |
| `swap_base_in` | `09` | `swap` | `k_sol_trade_events` from vault deltas only | `raydium_stable_swap.swap_base_in` | success/vault-delta materialized; failed decoded-only |
| `pre_initialize` | `0a` | `pool_create` | lifecycle or decoded-only | `raydium_stable_swap.pre_initialize` | observed decoded-only / explained in current corpus |
| `swap_base_out` | `0b` | `swap` | `k_sol_trade_events` from vault deltas only | `raydium_stable_swap.swap_base_out` | success/vault-delta materialized; failed decoded-only |
| `simulate_info` | `0c` | `cpi_transport` | decoded-only | `raydium_stable_swap.simulate_info` | observed decoded-only / explained |
| `admin_cancel_orders` | `0d` | `orderbook_admin` | `k_sol_orderbook_events` | `raydium_stable_swap.admin_cancel_orders` | observed/materialized when context complete |
| `swap_event` | `40c6cde8260871e2` | `cpi_transport` | decoded-only | `raydium_stable_swap.swap_event` | upstream mapped; not observed locally |
Stable Swap swaps are not materialized from instruction min/max bounds. `swap_base_in/out` require `amountSource=stable_swap_vault_balance_delta`; `stable_swap_instruction_bounds_only` remains decoded-only and, in the final corpus, appears only on failed transactions.

View File

@@ -10,9 +10,6 @@ This file records the manual Solscan account inventory added during the `0.7.50`
| `Aldrin AMM V2` | `CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4` | `no_idl` | https://solscan.io/account/CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4 | | `Aldrin AMM V2` | `CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4` | `no_idl` | https://solscan.io/account/CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4 |
| `ApePro Smart Wallet Program` | `JSW99DKmxNyREQM14SQLDykeBvEUG63TeohrvmofEiw` | `solscan_program_idl` | https://solscan.io/account/JSW99DKmxNyREQM14SQLDykeBvEUG63TeohrvmofEiw#programIdl | | `ApePro Smart Wallet Program` | `JSW99DKmxNyREQM14SQLDykeBvEUG63TeohrvmofEiw` | `solscan_program_idl` | https://solscan.io/account/JSW99DKmxNyREQM14SQLDykeBvEUG63TeohrvmofEiw#programIdl |
| `Aquifer` | `AQU1FRd7papthgdrwPTTq5JacJh8YtwEXaBfKU3bTz45` | `no_idl` | https://solscan.io/account/AQU1FRd7papthgdrwPTTq5JacJh8YtwEXaBfKU3bTz45 | | `Aquifer` | `AQU1FRd7papthgdrwPTTq5JacJh8YtwEXaBfKU3bTz45` | `no_idl` | https://solscan.io/account/AQU1FRd7papthgdrwPTTq5JacJh8YtwEXaBfKU3bTz45 |
| `Arbitrage Bot (3s1rA)` | `3s1rAymURnacreXreMy718GfqW6kygQsLNka1xDyW8pC` | `no_idl` | https://solscan.io/account/3s1rAymURnacreXreMy718GfqW6kygQsLNka1xDyW8pC |
| `Arbitrage Bot (6MWVT)` | `6MWVTis8rmmk6Vt9zmAJJbmb3VuLpzoQ1aHH4N6wQEGh` | `no_idl` | https://solscan.io/account/6MWVTis8rmmk6Vt9zmAJJbmb3VuLpzoQ1aHH4N6wQEGh |
| `Arbitrage Bot (9Zzf9)` | `9Zzf9QqTy3TkyXysvJBsXyuRjda5aXCEJ9vXfL2HKSYv` | `no_idl` | https://solscan.io/account/9Zzf9QqTy3TkyXysvJBsXyuRjda5aXCEJ9vXfL2HKSYv |
| `Axiom Trade` | `FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9` | `no_idl` | https://solscan.io/account/FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9 | | `Axiom Trade` | `FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9` | `no_idl` | https://solscan.io/account/FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9 |
| `Bags: Token Authority` | `BAGSB9TpGrZxQbEsrEznv5jXXdwyP6AXerN8aVRiAmcv` | `solscan_account` | https://solscan.io/account/BAGSB9TpGrZxQbEsrEznv5jXXdwyP6AXerN8aVRiAmcv | | `Bags: Token Authority` | `BAGSB9TpGrZxQbEsrEznv5jXXdwyP6AXerN8aVRiAmcv` | `solscan_account` | https://solscan.io/account/BAGSB9TpGrZxQbEsrEznv5jXXdwyP6AXerN8aVRiAmcv |
| `Believe : Token Authority` | `5qWya6UjwWnGVhdSBL3hyZ7B45jbk6Byt1hwd7ohEGXE` | `no_idl` | https://solscan.io/account/5qWya6UjwWnGVhdSBL3hyZ7B45jbk6Byt1hwd7ohEGXE | | `Believe : Token Authority` | `5qWya6UjwWnGVhdSBL3hyZ7B45jbk6Byt1hwd7ohEGXE` | `no_idl` | https://solscan.io/account/5qWya6UjwWnGVhdSBL3hyZ7B45jbk6Byt1hwd7ohEGXE |

View File

@@ -0,0 +1,67 @@
# Validation status — 0.7.52 Raydium Stable Swap final
## Scope
Decoder: `raydium_stable_swap`
Program id:
```text
5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h
```
## Local commands
```text
cargo test -p kb_lib
407 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
cargo clippy -p kb_lib --all-targets -- -D warnings
ok
```
## Final replay snapshot
```text
replayed=298
decode_skipped=0
ledger_upserts=298
unsafe_ledger_rows=258
trades=290
liquidity=16
lifecycle=4
tokenAccount=0
candle_upserts=1160
instructionObservations=5317
resetDeleted=1059
catalog=40 tokens / 59 pools / 59 pairs
```
## Stable Swap swap closure
```text
raydium_stable_swap.swap_base_in stable_swap_instruction_bounds_only failed decoded=27 trades=0
raydium_stable_swap.swap_base_in stable_swap_vault_balance_delta success decoded=171 trades=171
raydium_stable_swap.swap_base_out stable_swap_instruction_bounds_only failed decoded=2 trades=0
raydium_stable_swap.swap_base_out stable_swap_vault_balance_delta success decoded=4 trades=4
```
No successful Stable Swap swap remains without trade or skip reason.
## Invariants
| invariant | status |
|---|---|
| residual local `instruction_audit` | empty |
| residual `upstream_git.instruction_match` for covered entries | empty |
| decoded without coverage | empty |
| non-swap materialized as trade | empty |
| failed tx materialized as trade | empty |
| multi-target materialization | empty |
| unexplained successful non-materialized event | empty |
| successful swap via vault deltas | `trade_count = decoded_count` |
| failed swap instruction bounds only | `trade_count = 0` |
## Decision
`0.7.52 raydium_stable_swap` is closed for the current local corpus.

View File

@@ -0,0 +1,162 @@
# Raydium Stable Swap event coverage report — 0.7.52 final
## Scope
`0.7.52` closes the `raydium_stable_swap` tranche after `0.7.51 raydium_amm_v4`.
Canonical local decoder code:
```text
raydium_stable_swap
```
Canonical program id validated by local corpus:
```text
5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h
```
Stable Swap is handled as a Raydium legacy AMM-style program with a one-byte instruction discriminator layout. Anchor-like 8-byte discriminants remain upstream discovery evidence only and are not business proof.
## Final implementation status
Implemented and locally validated:
- `kb_lib/src/dex/raydium_stable_swap.rs`.
- `RaydiumStableSwapDecoder` re-exported through `kb_lib/src/dex.rs` and `kb_lib/src/lib.rs`.
- Stable Swap route in `DexDecodeService` before generic Raydium instruction-audit preservation.
- One-byte Stable Swap instruction observation support.
- Coverage entries for all locally observed Stable Swap discriminants `00..0d`.
- Materialization into lifecycle/liquidity/fee/admin/orderbook/trade tables when the local corpus proves a safe target.
- Swap materialization from exact vault balance deltas only.
- Validation SQL in `validation_sql/SQL_VALIDATION_RAYDIUM_STABLE_SWAP_0_7_52.sql`.
## Instruction surface
| entry | discriminator | family | final target | local event kind | final status |
|---|---:|---|---|---|---|
| `initialize` | `00` | `pool_create` | `k_sol_pool_lifecycle_events` | `raydium_stable_swap.initialize` | observed/materialized when context complete |
| `init_model_data` | `01` | `model_setup` | decoded-only | `raydium_stable_swap.init_model_data` | observed decoded-only / explained |
| `update_model_data` | `02` | `admin_config` | `k_sol_pool_admin_events` | `raydium_stable_swap.update_model_data` | observed/materialized |
| `deposit` | `03` | `liquidity_add` | `k_sol_liquidity_events` | `raydium_stable_swap.deposit` | observed/materialized |
| `withdraw` | `04` | `liquidity_remove` | `k_sol_liquidity_events` | `raydium_stable_swap.withdraw` | observed/materialized |
| `monitor_step` | `05` | `order_place` | `k_sol_orderbook_events` | `raydium_stable_swap.monitor_step` | observed/materialized |
| `set_params` | `06` | `admin_config` | `k_sol_pool_admin_events` | `raydium_stable_swap.set_params` | observed/materialized |
| `withdraw_pnl` | `07` | `fee` | `k_sol_fee_events` | `raydium_stable_swap.withdraw_pnl` | observed/materialized |
| `withdraw_srm` | `08` | `fee` | `k_sol_fee_events` | `raydium_stable_swap.withdraw_srm` | observed/materialized when context complete |
| `swap_base_in` | `09` | `swap` | `k_sol_trade_events` only from vault deltas | `raydium_stable_swap.swap_base_in` | observed, materialized for successful swaps with exact deltas |
| `pre_initialize` | `0a` | `pool_create` | decoded-only or lifecycle when complete | `raydium_stable_swap.pre_initialize` | observed decoded-only / explained in current corpus |
| `swap_base_out` | `0b` | `swap` | `k_sol_trade_events` only from vault deltas | `raydium_stable_swap.swap_base_out` | observed, materialized for successful swaps with exact deltas |
| `simulate_info` | `0c` | `cpi_transport` | decoded-only | `raydium_stable_swap.simulate_info` | observed decoded-only / explained |
| `admin_cancel_orders` | `0d` | `orderbook_admin` | `k_sol_orderbook_events` | `raydium_stable_swap.admin_cancel_orders` | observed/materialized when context complete |
| `swap_event` | `40c6cde8260871e2` | `cpi_transport` | decoded-only | `raydium_stable_swap.swap_event` | upstream mapped, not observed in local corpus |
## Swap amount policy
Stable Swap instruction arguments are retained as instruction bounds, but they are not sufficient for trade/candle materialization:
```text
swap_base_in:
amountInRaw = exact input argument
minimumAmountOutRaw = slippage lower bound, not exact output
swap_base_out:
amountOutRaw = requested output argument
maxAmountInRaw = slippage upper bound, not exact input
```
Therefore, `swap_base_in` and `swap_base_out` materialize as trades/candles only when exact base/quote amounts are inferred from vault balance deltas:
```text
amountSource = stable_swap_vault_balance_delta
```
Instruction-bound-only swaps remain decoded-only:
```text
amountSource = stable_swap_instruction_bounds_only
tradeCandidate = false
candleCandidate = false
```
For failed transactions the skip reasons are:
```text
skipTradeReason = failed_transaction
skipCandleReason = failed_transaction
```
For successful transactions where exact vault deltas cannot be proven, the expected skip reason is:
```text
stable_swap_exact_amounts_unresolved
```
The final local corpus has no successful unresolved Stable Swap swap.
## Final local validation snapshot
Latest confirmed local commands:
```text
cargo test -p kb_lib
407 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
cargo clippy -p kb_lib --all-targets -- -D warnings
ok
```
Latest replay snapshot:
```text
replayed=298
decode_skipped=0
ledger_upserts=298
unsafe_ledger_rows=258
trades=290
liquidity=16
lifecycle=4
token_account=0
candle_upserts=1160
instructionObservations=5317
resetDeleted=1059
catalog=40 tokens / 59 pools / 59 pairs
```
Stable Swap swap closure:
| event kind | amount source | tx status | decoded | trades |
|---|---|---|---:|---:|
| `raydium_stable_swap.swap_base_in` | `stable_swap_instruction_bounds_only` | failed | 27 | 0 |
| `raydium_stable_swap.swap_base_in` | `stable_swap_vault_balance_delta` | success | 171 | 171 |
| `raydium_stable_swap.swap_base_out` | `stable_swap_instruction_bounds_only` | failed | 2 | 0 |
| `raydium_stable_swap.swap_base_out` | `stable_swap_vault_balance_delta` | success | 4 | 4 |
UI smoke evidence after the vault-delta correction:
```text
pair 27, timeframe 60s -> 70 candles
pair 30, timeframe 60s -> 44 candles
```
## Final invariant status
Validated as clean on the local corpus:
- residual `raydium_stable_swap.instruction_audit`: empty;
- residual `upstream_git.instruction_match` for covered local entries: empty;
- decoded-without-coverage: empty;
- non-swap materialized as trade: empty;
- failed transaction materialized as business trade: empty;
- multi-target materialization: empty;
- successful non-materialized swaps without skip reason: empty;
- Stable Swap successful swaps with `stable_swap_vault_balance_delta`: `trade_count = decoded_count`;
- Stable Swap instruction-bound-only swaps: failed only, `trade_count = 0`.
## Closure decision
`0.7.52 raydium_stable_swap` is closed for the currently observed local corpus.
The decoder has detected all locally observed Stable Swap instruction discriminants, materialized every event that can safely be materialized, and preserved non-materializable/failed events as decoded-only with explicit reasons.
Future work is not a blocker for `0.7.52` and should be handled as a later tranche if a new local corpus reveals additional discriminants or a direct, reliable `swap_event` path.

View File

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

View File

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

View File

@@ -17,6 +17,7 @@ mod raydium_amm_v4;
mod raydium_clmm; mod raydium_clmm;
mod raydium_cpmm; mod raydium_cpmm;
pub(crate) mod raydium_launchpad; pub(crate) mod raydium_launchpad;
mod raydium_stable_swap;
pub use dexlab::DexlabCreatePoolDecoded; pub use dexlab::DexlabCreatePoolDecoded;
pub use dexlab::DexlabDecodedEvent; pub use dexlab::DexlabDecodedEvent;
@@ -90,3 +91,9 @@ pub use raydium_cpmm::RaydiumCpmmSwapMode;
pub use raydium_cpmm::classify_raydium_cpmm_instruction_data; pub use raydium_cpmm::classify_raydium_cpmm_instruction_data;
pub use raydium_cpmm::decode_raydium_cpmm_instruction; pub use raydium_cpmm::decode_raydium_cpmm_instruction;
pub use raydium_cpmm::decode_raydium_cpmm_program_data_event; pub use raydium_cpmm::decode_raydium_cpmm_program_data_event;
pub use raydium_stable_swap::RaydiumStableSwapDecodedEvent;
pub use raydium_stable_swap::RaydiumStableSwapDecoder;
pub use raydium_stable_swap::RaydiumStableSwapInstructionDecoded;
pub use raydium_stable_swap::RaydiumStableSwapSwapEventDecoded;
pub use raydium_stable_swap::classify_raydium_stable_swap_instruction_data;
pub use raydium_stable_swap::decode_raydium_stable_swap_program_data_event;

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@ pub struct DexDecodeService {
persistence: crate::DetectionPersistenceService, persistence: crate::DetectionPersistenceService,
raydium_amm_v4_decoder: crate::RaydiumAmmV4Decoder, raydium_amm_v4_decoder: crate::RaydiumAmmV4Decoder,
raydium_clmm_decoder: crate::RaydiumClmmDecoder, raydium_clmm_decoder: crate::RaydiumClmmDecoder,
raydium_stable_swap_decoder: crate::RaydiumStableSwapDecoder,
pump_fun_decoder: crate::PumpFunDecoder, pump_fun_decoder: crate::PumpFunDecoder,
pump_swap_decoder: crate::PumpSwapDecoder, pump_swap_decoder: crate::PumpSwapDecoder,
orca_whirlpools_decoder: crate::OrcaWhirlpoolsDecoder, orca_whirlpools_decoder: crate::OrcaWhirlpoolsDecoder,
@@ -33,6 +34,7 @@ impl DexDecodeService {
persistence, persistence,
raydium_amm_v4_decoder: crate::RaydiumAmmV4Decoder::new(), raydium_amm_v4_decoder: crate::RaydiumAmmV4Decoder::new(),
raydium_clmm_decoder: crate::RaydiumClmmDecoder::new(), raydium_clmm_decoder: crate::RaydiumClmmDecoder::new(),
raydium_stable_swap_decoder: crate::RaydiumStableSwapDecoder::new(),
pump_fun_decoder: crate::PumpFunDecoder::new(), pump_fun_decoder: crate::PumpFunDecoder::new(),
pump_swap_decoder: crate::PumpSwapDecoder::new(), pump_swap_decoder: crate::PumpSwapDecoder::new(),
orca_whirlpools_decoder: crate::OrcaWhirlpoolsDecoder::new(), orca_whirlpools_decoder: crate::OrcaWhirlpoolsDecoder::new(),
@@ -78,6 +80,13 @@ impl DexDecodeService {
if let Err(error) = append_result { if let Err(error) = append_result {
return Err(error); return Err(error);
} }
let append_result = append_persisted_events_result(
&mut persisted,
self.decode_and_persist_raydium_stable_swap_events(&transaction, &instructions).await,
);
if let Err(error) = append_result {
return Err(error);
}
let append_result = append_persisted_events_result( let append_result = append_persisted_events_result(
&mut persisted, &mut persisted,
self.decode_and_persist_raydium_clmm_events(&transaction, &instructions).await, self.decode_and_persist_raydium_clmm_events(&transaction, &instructions).await,
@@ -1512,6 +1521,65 @@ impl DexDecodeService {
.await; .await;
} }
async fn persist_raydium_stable_swap_event(
&self,
transaction: &crate::ChainTransactionDto,
decoded_event: &crate::RaydiumStableSwapDecodedEvent,
) -> Result<crate::DexDecodedEventDto, crate::Error> {
let transaction_id = match transaction.id {
Some(transaction_id) => transaction_id,
None => {
return Err(crate::Error::InvalidState(format!(
"transaction '{}' has no internal id",
transaction.signature
)));
},
};
let instruction_id = match decoded_event.instruction_id() {
Some(instruction_id) => instruction_id,
None => {
return Err(crate::Error::InvalidState(format!(
"raydium stable swap decoded event for transaction '{}' has no instruction id",
transaction.signature
)));
},
};
let event_kind = decoded_event.event_kind().to_string();
let raw_payload_json = match decoded_event.to_payload_json() {
Some(payload_json) => payload_json,
None => {
return Err(crate::Error::Json(
"cannot serialize decoded raydium stable swap payload".to_string(),
));
},
};
let payload_value_result = enriched_raydium_payload_value(
"raydium_stable_swap",
event_kind.as_str(),
raw_payload_json.as_str(),
);
let payload_value = match payload_value_result {
Ok(payload_value) => payload_value,
Err(error) => return Err(error),
};
return self
.materialize_named_dex_event(
transaction,
transaction_id,
instruction_id,
"raydium_stable_swap",
crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID.to_string(),
event_kind.as_str(),
decoded_event.pool_account().map(|value| return value.to_string()),
decoded_event.market_account().map(|value| return value.to_string()),
decoded_event.base_mint().map(|value| return value.to_string()),
decoded_event.quote_mint().map(|value| return value.to_string()),
decoded_event.lp_mint().map(|value| return value.to_string()),
payload_value,
)
.await;
}
async fn persist_pump_fun_event( async fn persist_pump_fun_event(
&self, &self,
transaction: &crate::ChainTransactionDto, transaction: &crate::ChainTransactionDto,
@@ -1708,6 +1776,32 @@ impl DexDecodeService {
return Ok(persisted); return Ok(persisted);
} }
async fn decode_and_persist_raydium_stable_swap_events(
&self,
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
let decoded_result = self
.raydium_stable_swap_decoder
.decode_transaction(transaction, instructions);
let decoded_events = match decoded_result {
Ok(decoded_events) => decoded_events,
Err(error) => return Err(error),
};
let mut persisted = std::vec::Vec::new();
for decoded_event in &decoded_events {
let persist_result = self
.persist_raydium_stable_swap_event(transaction, decoded_event)
.await;
let persisted_event = match persist_result {
Ok(persisted_event) => persisted_event,
Err(error) => return Err(error),
};
persisted.push(persisted_event);
}
return Ok(persisted);
}
async fn decode_and_persist_raydium_clmm_events( async fn decode_and_persist_raydium_clmm_events(
&self, &self,
transaction: &crate::ChainTransactionDto, transaction: &crate::ChainTransactionDto,
@@ -2509,6 +2603,13 @@ fn raydium_instruction_audit_spec(
candidate_pool_account_index: 3, candidate_pool_account_index: 3,
}); });
} }
if program_id == crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID {
return Some(RaydiumInstructionAuditSpec {
protocol_name: "raydium_stable_swap",
event_kind: "raydium_stable_swap.instruction_audit",
candidate_pool_account_index: 1,
});
}
if program_id == crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID { if program_id == crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID {
return Some(RaydiumInstructionAuditSpec { return Some(RaydiumInstructionAuditSpec {
protocol_name: "raydium_launchpad", protocol_name: "raydium_launchpad",
@@ -4234,6 +4335,7 @@ fn instruction_audit_event_kind_by_protocol(
"raydium_amm_v4" => return Some("raydium_amm_v4.instruction_audit"), "raydium_amm_v4" => return Some("raydium_amm_v4.instruction_audit"),
"raydium_clmm" => return Some("raydium_clmm.instruction_audit"), "raydium_clmm" => return Some("raydium_clmm.instruction_audit"),
"raydium_cpmm" => return Some("raydium_cpmm.instruction_audit"), "raydium_cpmm" => return Some("raydium_cpmm.instruction_audit"),
"raydium_stable_swap" => return Some("raydium_stable_swap.instruction_audit"),
"raydium_launchpad" => return Some("raydium_launchpad.instruction_audit"), "raydium_launchpad" => return Some("raydium_launchpad.instruction_audit"),
"meteora_dlmm" => return Some("meteora_dlmm.instruction_audit"), "meteora_dlmm" => return Some("meteora_dlmm.instruction_audit"),
"meteora_damm_v1" => return Some("meteora_damm_v1.instruction_audit"), "meteora_damm_v1" => return Some("meteora_damm_v1.instruction_audit"),
@@ -4719,7 +4821,7 @@ fn raydium_instruction_discriminator_hex(
bytes: std::option::Option<&[u8]>, bytes: std::option::Option<&[u8]>,
offset: usize, offset: usize,
) -> std::option::Option<std::string::String> { ) -> std::option::Option<std::string::String> {
if protocol_name == "raydium_amm_v4" { if protocol_name == "raydium_amm_v4" || protocol_name == "raydium_stable_swap" {
return discriminator_hex_from_bytes_with_len(bytes, offset, 1); return discriminator_hex_from_bytes_with_len(bytes, offset, 1);
} }
return discriminator_hex_from_bytes(bytes, offset); return discriminator_hex_from_bytes(bytes, offset);

View File

@@ -108,6 +108,12 @@ impl DexDetectService {
crate::dex_detection_route::DexDetectionRoute::RaydiumCpmmTrade => { crate::dex_detection_route::DexDetectionRoute::RaydiumCpmmTrade => {
self.detect_raydium_cpmm_trade(&transaction, decoded_event).await self.detect_raydium_cpmm_trade(&transaction, decoded_event).await
}, },
crate::dex_detection_route::DexDetectionRoute::RaydiumStableSwapTrade => {
self.detect_raydium_stable_swap_trade(&transaction, decoded_event).await
},
crate::dex_detection_route::DexDetectionRoute::RaydiumStableSwapPool => {
self.detect_raydium_stable_swap_pool(&transaction, decoded_event).await
},
crate::dex_detection_route::DexDetectionRoute::RaydiumClmmTrade => { crate::dex_detection_route::DexDetectionRoute::RaydiumClmmTrade => {
self.detect_raydium_clmm_trade(&transaction, decoded_event).await self.detect_raydium_clmm_trade(&transaction, decoded_event).await
}, },
@@ -668,6 +674,78 @@ impl DexDetectService {
.await; .await;
} }
async fn detect_raydium_stable_swap_pool(
&self,
transaction: &crate::ChainTransactionDto,
decoded_event: &crate::DexDecodedEventDto,
) -> Result<crate::DexPoolDetectionResult, crate::Error> {
return self
.detect_materialized_pool_from_decoded_event(
transaction,
decoded_event,
"raydium_stable_swap",
crate::PoolKind::Amm,
crate::PoolStatus::Active,
"signal.dex.raydium_stable_swap",
)
.await;
}
async fn detect_raydium_stable_swap_trade(
&self,
transaction: &crate::ChainTransactionDto,
decoded_event: &crate::DexDecodedEventDto,
) -> Result<crate::DexPoolDetectionResult, crate::Error> {
let dex_id_result =
crate::dex_catalog::ensure_known_dex(self.database.as_ref(), "raydium_stable_swap").await;
let dex_id = match dex_id_result {
Ok(dex_id) => dex_id,
Err(error) => return Err(error),
};
let payload_value_result = parse_payload_json(decoded_event.payload_json.as_str());
let payload_value = match payload_value_result {
Ok(payload_value) => payload_value,
Err(error) => return Err(error),
};
let base_vault_address = extract_payload_string_field(&payload_value, "baseVault");
let quote_vault_address = extract_payload_string_field(&payload_value, "quoteVault");
let input_result =
crate::dex_pool_materialization::DexPoolMaterializationInput::from_decoded_event(
decoded_event,
dex_id,
crate::PoolKind::Amm,
crate::PoolStatus::Active,
crate::dex_pool_materialization::DexPoolTokenOrder::AlreadyBaseQuote,
base_vault_address,
quote_vault_address,
transaction.source_endpoint_name.clone(),
);
let input = match input_result {
Ok(input) => input,
Err(error) => return Err(error),
};
let detection_result =
crate::dex_pool_materialization::materialize_dex_pool(self.database.as_ref(), &input)
.await;
let detection_result = match detection_result {
Ok(detection_result) => detection_result,
Err(error) => return Err(error),
};
let signal_result = self
.record_pool_detection_signals(
transaction,
"signal.dex.raydium_stable_swap",
&detection_result,
payload_value,
)
.await;
if let Err(error) = signal_result {
return Err(error);
}
return Ok(detection_result);
}
async fn detect_raydium_cpmm_trade( async fn detect_raydium_cpmm_trade(
&self, &self,
transaction: &crate::ChainTransactionDto, transaction: &crate::ChainTransactionDto,

View File

@@ -11,6 +11,10 @@ pub(crate) enum DexDetectionRoute {
RaydiumAmmV4Trade, RaydiumAmmV4Trade,
/// Raydium CPMM trade route. /// Raydium CPMM trade route.
RaydiumCpmmTrade, RaydiumCpmmTrade,
/// Raydium Stable Swap trade route.
RaydiumStableSwapTrade,
/// Raydium Stable Swap pool lifecycle route.
RaydiumStableSwapPool,
/// Raydium CLMM trade route. /// Raydium CLMM trade route.
RaydiumClmmTrade, RaydiumClmmTrade,
/// Raydium Launchpad pool or bonding-curve creation route. /// Raydium Launchpad pool or bonding-curve creation route.
@@ -67,6 +71,18 @@ pub(crate) fn dex_detection_route(
("raydium_cpmm", "raydium_cpmm.swap_base_output") => { ("raydium_cpmm", "raydium_cpmm.swap_base_output") => {
return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumCpmmTrade); return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumCpmmTrade);
}, },
("raydium_stable_swap", "raydium_stable_swap.initialize") => {
return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumStableSwapPool);
},
("raydium_stable_swap", "raydium_stable_swap.pre_initialize") => {
return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumStableSwapPool);
},
("raydium_stable_swap", "raydium_stable_swap.swap_base_in") => {
return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumStableSwapTrade);
},
("raydium_stable_swap", "raydium_stable_swap.swap_base_out") => {
return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumStableSwapTrade);
},
("raydium_clmm", "raydium_clmm.swap") => { ("raydium_clmm", "raydium_clmm.swap") => {
return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumClmmTrade); return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumClmmTrade);
}, },

View File

@@ -260,6 +260,39 @@ fn infer_expected_db_target_for_entry(
); );
} }
} }
if decoder_code == "raydium_stable_swap" {
if entry_name == "initialize" || entry_name == "pre_initialize" {
return Some(
crate::DexEventCoverageEntryDto::DB_TARGET_POOL_LIFECYCLE_EVENTS.to_string(),
);
}
if entry_name == "deposit" || entry_name == "withdraw" {
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS.to_string());
}
if entry_name == "swap_base_in" || entry_name == "swap_base_out" {
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_TRADE_EVENTS.to_string());
}
if entry_name == "withdraw_pnl" || entry_name == "withdraw_srm" {
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_FEE_EVENTS.to_string());
}
if entry_name == "set_params" {
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_POOL_ADMIN_EVENTS.to_string());
}
if entry_name == "monitor_step" || entry_name == "admin_cancel_orders" {
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_ORDERBOOK_EVENTS.to_string());
}
if entry_name == "update_model_data" {
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_POOL_ADMIN_EVENTS.to_string());
}
if entry_name == "init_model_data"
|| entry_name == "simulate_info"
|| entry_name == "swap_event"
{
return Some(
crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string(),
);
}
}
if decoder_code == "raydium_clmm" { if decoder_code == "raydium_clmm" {
if entry_name == "initialize_reward" { if entry_name == "initialize_reward" {
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_REWARD_EVENTS.to_string()); return Some(crate::DexEventCoverageEntryDto::DB_TARGET_REWARD_EVENTS.to_string());
@@ -405,6 +438,9 @@ fn infer_event_family_for_entry(
if decoder_code == "raydium_cpmm" { if decoder_code == "raydium_cpmm" {
return infer_raydium_cpmm_event_family(entry_name, entry_kind); return infer_raydium_cpmm_event_family(entry_name, entry_kind);
} }
if decoder_code == "raydium_stable_swap" {
return infer_raydium_stable_swap_event_family(entry_name, entry_kind);
}
return infer_event_family(entry_name, entry_kind); return infer_event_family(entry_name, entry_kind);
} }
@@ -452,6 +488,34 @@ fn infer_raydium_cpmm_event_family(
} }
} }
fn infer_raydium_stable_swap_event_family(
entry_name: &str,
entry_kind: &str,
) -> std::option::Option<std::string::String> {
if entry_kind == crate::ENTRY_KIND_PROGRAM {
return None;
}
match entry_name {
"initialize" => return Some("pool_create".to_string()),
"pre_initialize" => return Some("pool_create".to_string()),
"init_model_data" => return Some("model_setup".to_string()),
"update_model_data" => return Some("admin_config".to_string()),
"deposit" => return Some("liquidity_add".to_string()),
"withdraw" => return Some("liquidity_remove".to_string()),
"monitor_step" => return Some("order_place".to_string()),
"set_params" => return Some("admin_config".to_string()),
"withdraw_pnl" => return Some("fee".to_string()),
"withdraw_srm" => return Some("fee".to_string()),
"swap_base_in" => return Some("swap".to_string()),
"swap_base_out" => return Some("swap".to_string()),
"simulate_info" => return Some("cpi_transport".to_string()),
"admin_cancel_orders" => return Some("orderbook_admin".to_string()),
"swap_event" => return Some("cpi_transport".to_string()),
_ => return infer_event_family(entry_name, entry_kind),
}
}
fn infer_raydium_clmm_event_family( fn infer_raydium_clmm_event_family(
entry_name: &str, entry_name: &str,
entry_kind: &str, entry_kind: &str,
@@ -725,6 +789,32 @@ fn raydium_amm_v4_local_event_kind(entry_name: &str) -> std::option::Option<std:
} }
} }
fn raydium_stable_swap_local_event_kind(
entry_name: &str,
) -> std::option::Option<std::string::String> {
match entry_name {
"initialize" => return Some("raydium_stable_swap.initialize".to_string()),
"init_model_data" => return Some("raydium_stable_swap.init_model_data".to_string()),
"update_model_data" => return Some("raydium_stable_swap.update_model_data".to_string()),
"pre_initialize" => return Some("raydium_stable_swap.pre_initialize".to_string()),
"deposit" => return Some("raydium_stable_swap.deposit".to_string()),
"withdraw" => return Some("raydium_stable_swap.withdraw".to_string()),
"monitor_step" => return Some("raydium_stable_swap.monitor_step".to_string()),
"set_params" => return Some("raydium_stable_swap.set_params".to_string()),
"withdraw_pnl" => return Some("raydium_stable_swap.withdraw_pnl".to_string()),
"withdraw_srm" => return Some("raydium_stable_swap.withdraw_srm".to_string()),
"swap_base_in" => return Some("raydium_stable_swap.swap_base_in".to_string()),
"swap_base_out" => return Some("raydium_stable_swap.swap_base_out".to_string()),
"simulate_info" => return Some("raydium_stable_swap.simulate_info".to_string()),
"admin_cancel_orders" => {
return Some("raydium_stable_swap.admin_cancel_orders".to_string());
},
"swap_event" => return Some("raydium_stable_swap.swap_event".to_string()),
_ => return None,
}
}
pub(crate) fn known_local_event_kind( pub(crate) fn known_local_event_kind(
decoder_code: &str, decoder_code: &str,
entry_name: &str, entry_name: &str,
@@ -732,6 +822,9 @@ pub(crate) fn known_local_event_kind(
if decoder_code == "raydium_amm_v4" { if decoder_code == "raydium_amm_v4" {
return raydium_amm_v4_local_event_kind(entry_name); return raydium_amm_v4_local_event_kind(entry_name);
} }
if decoder_code == "raydium_stable_swap" {
return raydium_stable_swap_local_event_kind(entry_name);
}
if decoder_code == "raydium_launchpad" && raydium_launchpad_local_entry_is_known(entry_name) { if decoder_code == "raydium_launchpad" && raydium_launchpad_local_entry_is_known(entry_name) {
return Some(format!("raydium_launchpad.{}", entry_name)); return Some(format!("raydium_launchpad.{}", entry_name));
} }

View File

@@ -224,7 +224,29 @@ fn resolve_instruction_name(
}; };
return Some(name.to_string()); return Some(name.to_string());
} }
if program_id == crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID
|| decoder_code == Some("raydium_stable_swap")
{
let name = match discriminator_hex {
"00" => "raydium_stable_swap.initialize",
"01" => "raydium_stable_swap.init_model_data",
"02" => "raydium_stable_swap.update_model_data",
"03" => "raydium_stable_swap.deposit",
"04" => "raydium_stable_swap.withdraw",
"05" => "raydium_stable_swap.monitor_step",
"06" => "raydium_stable_swap.set_params",
"07" => "raydium_stable_swap.withdraw_pnl",
"08" => "raydium_stable_swap.withdraw_srm",
"09" => "raydium_stable_swap.swap_base_in",
"0a" => "raydium_stable_swap.pre_initialize",
"0b" => "raydium_stable_swap.swap_base_out",
"0c" => "raydium_stable_swap.simulate_info",
"0d" => "raydium_stable_swap.admin_cancel_orders",
"40c6cde8260871e2" => "raydium_stable_swap.swap_event",
_ => return None,
};
return Some(name.to_string());
}
if program_id == crate::RAYDIUM_CPMM_PROGRAM_ID || decoder_code == Some("raydium_cpmm") { if program_id == crate::RAYDIUM_CPMM_PROGRAM_ID || decoder_code == Some("raydium_cpmm") {
let name = match discriminator_hex { let name = match discriminator_hex {
"9c5420764587467b" => "raydium_cpmm.close_permission_pda", "9c5420764587467b" => "raydium_cpmm.close_permission_pda",
@@ -284,7 +306,6 @@ fn resolve_instruction_name(
}; };
return Some(name.to_string()); return Some(name.to_string());
} }
if program_id == crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID if program_id == crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID
|| decoder_code == Some("raydium_launchpad") || decoder_code == Some("raydium_launchpad")
{ {
@@ -309,7 +330,9 @@ fn discriminator_hex_from_data_json(
Some(decoded) => decoded, Some(decoded) => decoded,
None => return None, None => return None,
}; };
let discriminator_len = if program_id == crate::RAYDIUM_AMM_V4_PROGRAM_ID { let discriminator_len = if program_id == crate::RAYDIUM_AMM_V4_PROGRAM_ID
|| program_id == crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID
{
1_usize 1_usize
} else { } else {
8_usize 8_usize

View File

@@ -505,6 +505,8 @@ pub use db::FeeEventEntity;
pub use db::InstructionObservationDto; pub use db::InstructionObservationDto;
/// Persisted technical observation for one Solana instruction. /// Persisted technical observation for one Solana instruction.
pub use db::InstructionObservationEntity; pub use db::InstructionObservationEntity;
/// Raw source row used to rebuild the technical instruction-observation index.
pub use db::InstructionObservationSourceRow;
/// Application-facing known HTTP endpoint DTO. /// Application-facing known HTTP endpoint DTO.
pub use db::KnownHttpEndpointDto; pub use db::KnownHttpEndpointDto;
/// Application-facing known WebSocket endpoint DTO. /// Application-facing known WebSocket endpoint DTO.
@@ -517,6 +519,8 @@ pub use db::KnownWsEndpointEntity;
pub use db::LaunchAttributionDto; pub use db::LaunchAttributionDto;
/// Persisted launch attribution row. /// Persisted launch attribution row.
pub use db::LaunchAttributionEntity; pub use db::LaunchAttributionEntity;
/// Input used to upsert one launch event row.
pub use db::LaunchEventUpsertInput;
/// Application-facing launch surface DTO. /// Application-facing launch surface DTO.
pub use db::LaunchSurfaceDto; pub use db::LaunchSurfaceDto;
/// Persisted launch surface row. /// Persisted launch surface row.
@@ -762,12 +766,12 @@ pub use db::query_dex_decoded_events_delete_local_replay_scope_by_transaction_id
pub use db::query_dex_decoded_events_delete_locally_covered_upstream_instruction_matches; pub use db::query_dex_decoded_events_delete_locally_covered_upstream_instruction_matches;
/// Deletes Meteora DLMM Anchor self-CPI swap audit rows already covered by decoded swaps. /// Deletes Meteora DLMM Anchor self-CPI swap audit rows already covered by decoded swaps.
pub use db::query_dex_decoded_events_delete_meteora_dlmm_anchor_swap_instruction_audits; pub use db::query_dex_decoded_events_delete_meteora_dlmm_anchor_swap_instruction_audits;
/// Deletes decoded DEX instruction audit rows related to one decoded instruction.
pub use db::query_dex_decoded_events_delete_related_instruction_audit;
/// Deletes one Raydium CLMM instruction-audit row by discriminator. /// Deletes one Raydium CLMM instruction-audit row by discriminator.
pub use db::query_dex_decoded_events_delete_raydium_clmm_instruction_audit_by_discriminator; pub use db::query_dex_decoded_events_delete_raydium_clmm_instruction_audit_by_discriminator;
/// Deletes one Raydium Launchpad self-CPI audit row by discriminator. /// Deletes one Raydium Launchpad self-CPI audit row by discriminator.
pub use db::query_dex_decoded_events_delete_raydium_launchpad_anchor_self_cpi_audit; pub use db::query_dex_decoded_events_delete_raydium_launchpad_anchor_self_cpi_audit;
/// Deletes decoded DEX instruction audit rows related to one decoded instruction.
pub use db::query_dex_decoded_events_delete_related_instruction_audit;
/// Deletes Raydium CLMM instruction-audit rows for locally mapped CLMM instructions. /// Deletes Raydium CLMM instruction-audit rows for locally mapped CLMM instructions.
pub use db::query_dex_decoded_events_delete_replaced_raydium_clmm_instruction_audits; pub use db::query_dex_decoded_events_delete_replaced_raydium_clmm_instruction_audits;
/// Deletes Raydium CPMM instruction-audit rows already covered by local named rows. /// Deletes Raydium CPMM instruction-audit rows already covered by local named rows.
@@ -778,10 +782,10 @@ pub use db::query_dex_decoded_events_get_by_key;
pub use db::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint; pub use db::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint;
/// Lists decoded DEX events for one transaction. /// Lists decoded DEX events for one transaction.
pub use db::query_dex_decoded_events_list_by_transaction_id; pub use db::query_dex_decoded_events_list_by_transaction_id;
/// Inserts or updates one decoded DEX event row.
pub use db::query_dex_decoded_events_upsert;
/// Updates the persisted payload of one decoded DEX event row. /// Updates the persisted payload of one decoded DEX event row.
pub use db::query_dex_decoded_events_update_payload_json_by_id; pub use db::query_dex_decoded_events_update_payload_json_by_id;
/// Inserts or updates one decoded DEX event row.
pub use db::query_dex_decoded_events_upsert;
/// Deletes DEX event coverage entries for one decoder. /// Deletes DEX event coverage entries for one decoder.
pub use db::query_dex_event_coverage_entries_delete_by_decoder; pub use db::query_dex_event_coverage_entries_delete_by_decoder;
/// Lists DEX event coverage entries for one decoder. /// Lists DEX event coverage entries for one decoder.
@@ -819,8 +823,6 @@ pub use db::query_instruction_observations_delete_by_transaction_ids;
pub use db::query_instruction_observations_list_by_filter; pub use db::query_instruction_observations_list_by_filter;
/// Upserts one instruction observation row. /// Upserts one instruction observation row.
pub use db::query_instruction_observations_upsert; pub use db::query_instruction_observations_upsert;
/// Raw source row used to rebuild the technical instruction-observation index.
pub use db::InstructionObservationSourceRow;
/// Reads one known HTTP endpoint by name. /// Reads one known HTTP endpoint by name.
pub use db::query_known_http_endpoints_get; pub use db::query_known_http_endpoints_get;
/// Lists all known HTTP endpoints. /// Lists all known HTTP endpoints.
@@ -841,8 +843,6 @@ pub use db::query_launch_attributions_list_by_pool_id;
pub use db::query_launch_attributions_upsert; pub use db::query_launch_attributions_upsert;
/// Inserts or updates one launch event row. /// Inserts or updates one launch event row.
pub use db::query_launch_events_upsert; pub use db::query_launch_events_upsert;
/// Input used to upsert one launch event row.
pub use db::LaunchEventUpsertInput;
/// Returns one launch-surface matching key identified by its kind and value, if it exists. /// Returns one launch-surface matching key identified by its kind and value, if it exists.
pub use db::query_launch_surface_keys_get_by_match; pub use db::query_launch_surface_keys_get_by_match;
/// Lists all launch-surface matching keys attached to one launch surface id. /// Lists all launch-surface matching keys attached to one launch surface id.
@@ -1221,8 +1221,18 @@ pub use dex::RaydiumCpmmSwapDecoded;
pub use dex::RaydiumCpmmSwapEventDecoded; pub use dex::RaydiumCpmmSwapEventDecoded;
/// Raydium CPMM swap mode. /// Raydium CPMM swap mode.
pub use dex::RaydiumCpmmSwapMode; pub use dex::RaydiumCpmmSwapMode;
/// Raydium Stable Swap decoded event.
pub use dex::RaydiumStableSwapDecodedEvent;
/// Raydium Stable Swap decoder.
pub use dex::RaydiumStableSwapDecoder;
/// Decoded Raydium Stable Swap instruction.
pub use dex::RaydiumStableSwapInstructionDecoded;
/// Decoded Raydium Stable Swap program-data swap event.
pub use dex::RaydiumStableSwapSwapEventDecoded;
/// Decodes one Raydium CPMM instruction from projected instruction fields. /// Decodes one Raydium CPMM instruction from projected instruction fields.
pub use dex::classify_raydium_cpmm_instruction_data; pub use dex::classify_raydium_cpmm_instruction_data;
/// Classifies one Raydium Stable Swap instruction data payload.
pub use dex::classify_raydium_stable_swap_instruction_data;
/// Decodes a Raydium CLMM instruction. /// Decodes a Raydium CLMM instruction.
pub use dex::decode_raydium_clmm_instruction; pub use dex::decode_raydium_clmm_instruction;
/// Decodes one Raydium CLMM Anchor Program data event. /// Decodes one Raydium CLMM Anchor Program data event.
@@ -1231,6 +1241,8 @@ pub use dex::decode_raydium_clmm_program_data_event;
pub use dex::decode_raydium_cpmm_instruction; pub use dex::decode_raydium_cpmm_instruction;
/// Decodes Raydium CPMM Anchor events emitted in `Program data:` logs. /// Decodes Raydium CPMM Anchor events emitted in `Program data:` logs.
pub use dex::decode_raydium_cpmm_program_data_event; pub use dex::decode_raydium_cpmm_program_data_event;
/// Decodes Raydium Stable Swap `Program data:` event payloads.
pub use dex::decode_raydium_stable_swap_program_data_event;
/// DEX decode service. /// DEX decode service.
pub use dex_decode::DexDecodeService; pub use dex_decode::DexDecodeService;
/// Business-level DEX detection service. /// Business-level DEX detection service.

View File

@@ -98,6 +98,7 @@ pub(crate) async fn resolve_trade_amounts(
} }
if (input.decoded_event.event_kind.starts_with("raydium_amm_v4.") if (input.decoded_event.event_kind.starts_with("raydium_amm_v4.")
|| input.decoded_event.event_kind.starts_with("raydium_cpmm.") || input.decoded_event.event_kind.starts_with("raydium_cpmm.")
|| input.decoded_event.event_kind.starts_with("raydium_stable_swap.")
|| input.decoded_event.event_kind.starts_with("raydium_clmm.")) || input.decoded_event.event_kind.starts_with("raydium_clmm."))
&& (base_amount_raw.is_none() && (base_amount_raw.is_none()
|| quote_amount_raw.is_none() || quote_amount_raw.is_none()
@@ -183,6 +184,21 @@ pub(crate) async fn resolve_trade_amounts(
return Err(error); return Err(error);
} }
} }
if input.decoded_event.event_kind.starts_with("raydium_stable_swap.")
&& (base_amount_raw.is_none() || quote_amount_raw.is_none())
{
let resolution_result = crate::trade_amount_resolution::apply_vault_balance_delta_fallback(
input,
input.base_vault_address,
input.quote_vault_address,
&mut base_amount_raw,
&mut quote_amount_raw,
&mut price_quote_per_base,
);
if let Err(error) = resolution_result {
return Err(error);
}
}
if input.decoded_event.event_kind.starts_with("raydium_clmm.") if input.decoded_event.event_kind.starts_with("raydium_clmm.")
&& (base_amount_raw.is_none() || quote_amount_raw.is_none()) && (base_amount_raw.is_none() || quote_amount_raw.is_none())
{ {

View File

@@ -12836,6 +12836,28 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[
8, 8,
"decoders/raydium-liquidity-locking-decoder/src/instructions/settle_cp_fee_event.rs", "decoders/raydium-liquidity-locking-decoder/src/instructions/settle_cp_fee_event.rs",
), ),
manual_solscan_discriminator_entry(
"raydium_stable_swap",
Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID),
"raydium",
"stable_swap",
crate::ENTRY_KIND_INSTRUCTION,
"init_model_data",
"01",
1,
"docs.raydium.io/products/stable/instructions#initmodeldata-local-corpus-observed",
),
manual_solscan_discriminator_entry(
"raydium_stable_swap",
Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID),
"raydium",
"stable_swap",
crate::ENTRY_KIND_INSTRUCTION,
"update_model_data",
"02",
1,
"docs.raydium.io/products/stable/instructions#updatemodeldata-local-corpus-observed",
),
upstream_git_discriminator_entry( upstream_git_discriminator_entry(
"raydium_stable_swap", "raydium_stable_swap",
Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID), Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID),
@@ -12869,6 +12891,50 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[
1, 1,
"decoders/raydium-stable-swap-decoder/src/instructions/pre_initialize.rs", "decoders/raydium-stable-swap-decoder/src/instructions/pre_initialize.rs",
), ),
manual_solscan_discriminator_entry(
"raydium_stable_swap",
Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID),
"raydium",
"stable_swap",
crate::ENTRY_KIND_INSTRUCTION,
"monitor_step",
"05",
1,
"docs.raydium.io/products/stable/instructions#monitorstep-local-corpus-observed",
),
manual_solscan_discriminator_entry(
"raydium_stable_swap",
Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID),
"raydium",
"stable_swap",
crate::ENTRY_KIND_INSTRUCTION,
"set_params",
"06",
1,
"docs.raydium.io/products/stable/instructions#setparams-local-corpus-observed",
),
manual_solscan_discriminator_entry(
"raydium_stable_swap",
Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID),
"raydium",
"stable_swap",
crate::ENTRY_KIND_INSTRUCTION,
"withdraw_pnl",
"07",
1,
"docs.raydium.io/products/stable/instructions#withdrawpnl-local-corpus-observed",
),
manual_solscan_discriminator_entry(
"raydium_stable_swap",
Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID),
"raydium",
"stable_swap",
crate::ENTRY_KIND_INSTRUCTION,
"withdraw_srm",
"08",
1,
"docs.raydium.io/products/stable/instructions#withdrawsrm-local-corpus-observed",
),
upstream_git_discriminator_entry( upstream_git_discriminator_entry(
"raydium_stable_swap", "raydium_stable_swap",
Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID), Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID),
@@ -12902,6 +12968,39 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[
1, 1,
"decoders/raydium-stable-swap-decoder/src/instructions/withdraw.rs", "decoders/raydium-stable-swap-decoder/src/instructions/withdraw.rs",
), ),
manual_solscan_discriminator_entry(
"raydium_stable_swap",
Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID),
"raydium",
"stable_swap",
crate::ENTRY_KIND_INSTRUCTION,
"simulate_info",
"0c",
1,
"docs.raydium.io/products/stable/instructions#simulateinfo-local-corpus-observed",
),
manual_solscan_discriminator_entry(
"raydium_stable_swap",
Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID),
"raydium",
"stable_swap",
crate::ENTRY_KIND_INSTRUCTION,
"admin_cancel_orders",
"0d",
1,
"raydium-amm/program/src/instruction.rs#admincancelorders-stable-local-corpus-observed",
),
manual_solscan_discriminator_entry(
"raydium_stable_swap",
Some(crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID),
"raydium",
"stable_swap",
crate::ENTRY_KIND_EVENT,
"swap_event",
"40c6cde8260871e2",
8,
"docs.raydium.io/products/stable/instructions#program-data-swap-event-decoded-only",
),
upstream_git_discriminator_entry( upstream_git_discriminator_entry(
"stabble_stable_swap", "stabble_stable_swap",
Some(crate::STABBLE_STABLE_SWAP_PROGRAM_ID), Some(crate::STABBLE_STABLE_SWAP_PROGRAM_ID),

View File

@@ -0,0 +1,292 @@
-- file: validation_sql/SQL_VALIDATION_RAYDIUM_STABLE_SWAP_0_7_52.sql
-- 0.7.52 raydium_stable_swap validation checklist.
-- Run on a dedicated fresh SQLite database after corpus construction and replay with:
-- skipDexDecode=no, forceDexDecode=yes, deferInstructionObservations=yes.
-- 01. Coverage stable swap.
SELECT
entry_name,
entry_kind,
event_family,
expected_db_target,
proof_status,
local_event_kind,
discriminator_hex,
observed_count,
materialized_count,
trade_count
FROM k_sol_dex_event_coverage_entries
WHERE decoder_code = 'raydium_stable_swap'
ORDER BY entry_kind, entry_name, discriminator_hex;
-- 02. Instruction observations.
SELECT
instruction_name,
discriminator_hex,
COUNT(*) AS observed_count,
COUNT(DISTINCT signature) AS tx_count
FROM k_sol_instruction_observations
WHERE decoder_code = 'raydium_stable_swap'
GROUP BY instruction_name, discriminator_hex
ORDER BY observed_count DESC, instruction_name, discriminator_hex;
-- 03. Residual local instruction audit.
SELECT
json_extract(payload_json, '$.discriminatorHex') AS discriminator_hex,
COUNT(*) AS audit_count,
COUNT(DISTINCT transaction_id) AS tx_count
FROM k_sol_dex_decoded_events
WHERE protocol_name = 'raydium_stable_swap'
AND event_kind = 'raydium_stable_swap.instruction_audit'
GROUP BY discriminator_hex
ORDER BY audit_count DESC, discriminator_hex;
-- 04. Residual upstream fallback for covered local entries.
SELECT
json_extract(ug.payload_json, '$.upstreamDecoderCode') AS upstream_decoder_code,
json_extract(ug.payload_json, '$.upstreamEntryName') AS entry_name,
json_extract(ug.payload_json, '$.upstreamDiscriminatorHex') AS discriminator_hex,
json_extract(ug.payload_json, '$.upstreamSourceRepo') AS source_repo,
COUNT(*) AS fallback_count,
COUNT(DISTINCT ug.transaction_id) AS tx_count
FROM k_sol_dex_decoded_events ug
JOIN k_sol_dex_event_coverage_entries ce
ON ce.decoder_code = json_extract(ug.payload_json, '$.upstreamDecoderCode')
AND ce.entry_name = json_extract(ug.payload_json, '$.upstreamEntryName')
AND ce.discriminator_hex = json_extract(ug.payload_json, '$.upstreamDiscriminatorHex')
AND ce.local_event_kind IS NOT NULL
AND ce.local_event_kind <> ''
WHERE ug.protocol_name = 'upstream_git'
AND ug.event_kind = 'upstream_git.instruction_match'
AND json_extract(ug.payload_json, '$.upstreamDecoderCode') = 'raydium_stable_swap'
GROUP BY upstream_decoder_code, entry_name, discriminator_hex, source_repo
ORDER BY fallback_count DESC, entry_name;
-- 05. Non-swap safety: non-swap event must not materialize as trade.
SELECT
de.event_kind,
ce.event_family,
COUNT(*) AS decoded_count,
COUNT(te.id) AS trade_count
FROM k_sol_dex_decoded_events de
LEFT JOIN k_sol_dex_event_coverage_entries ce
ON ce.decoder_code = 'raydium_stable_swap'
AND ce.local_event_kind = de.event_kind
LEFT JOIN k_sol_trade_events te
ON te.decoded_event_id = de.id
WHERE de.protocol_name = 'raydium_stable_swap'
GROUP BY de.event_kind, ce.event_family
HAVING ce.event_family <> 'swap'
AND COUNT(te.id) > 0
ORDER BY trade_count DESC, de.event_kind;
-- 06. Failed transaction safety: failed tx must not materialize as business trade.
SELECT
de.event_kind,
COUNT(*) AS decoded_failed_count,
COUNT(te.id) AS trade_count
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
WHERE de.protocol_name = 'raydium_stable_swap'
AND tx.err_json IS NOT NULL
AND tx.err_json <> ''
AND tx.err_json <> 'null'
GROUP BY de.event_kind
HAVING COUNT(te.id) > 0
ORDER BY trade_count DESC, de.event_kind;
-- 07. Decoded without coverage entry.
SELECT
de.event_kind,
COUNT(*) AS decoded_count
FROM k_sol_dex_decoded_events de
LEFT JOIN k_sol_dex_event_coverage_entries ce
ON ce.decoder_code = 'raydium_stable_swap'
AND ce.local_event_kind = de.event_kind
WHERE de.protocol_name = 'raydium_stable_swap'
AND ce.id IS NULL
GROUP BY de.event_kind
ORDER BY decoded_count DESC, de.event_kind;
-- 08. Multi-target materialization.
SELECT
de.event_kind,
COUNT(DISTINCT de.id) AS decoded_count,
COUNT(DISTINCT te.id) AS trade_count,
COUNT(DISTINCT le.id) AS liquidity_count,
COUNT(DISTINCT pe.id) AS lifecycle_count,
COUNT(DISTINCT fe.id) AS fee_count,
COUNT(DISTINCT ae.id) AS admin_count,
COUNT(DISTINCT oe.id) AS orderbook_count,
(
CASE WHEN COUNT(DISTINCT te.id) > 0 THEN 1 ELSE 0 END
+ CASE WHEN COUNT(DISTINCT le.id) > 0 THEN 1 ELSE 0 END
+ CASE WHEN COUNT(DISTINCT pe.id) > 0 THEN 1 ELSE 0 END
+ CASE WHEN COUNT(DISTINCT fe.id) > 0 THEN 1 ELSE 0 END
+ CASE WHEN COUNT(DISTINCT ae.id) > 0 THEN 1 ELSE 0 END
+ CASE WHEN COUNT(DISTINCT oe.id) > 0 THEN 1 ELSE 0 END
) AS materialized_target_count
FROM k_sol_dex_decoded_events de
LEFT JOIN k_sol_trade_events te
ON te.decoded_event_id = de.id
LEFT JOIN k_sol_liquidity_events le
ON le.decoded_event_id = de.id
LEFT JOIN k_sol_pool_lifecycle_events pe
ON pe.decoded_event_id = de.id
LEFT JOIN k_sol_fee_events fe
ON fe.decoded_event_id = de.id
LEFT JOIN k_sol_pool_admin_events ae
ON ae.decoded_event_id = de.id
LEFT JOIN k_sol_orderbook_events oe
ON oe.decoded_event_id = de.id
WHERE de.protocol_name = 'raydium_stable_swap'
GROUP BY de.event_kind
HAVING materialized_target_count > 1
ORDER BY materialized_target_count DESC, de.event_kind;
-- 09. Unexplained successful non-materialized events.
SELECT
de.event_kind,
COUNT(*) AS unexplained_count
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_liquidity_events le
ON le.decoded_event_id = de.id
LEFT JOIN k_sol_pool_lifecycle_events pe
ON pe.decoded_event_id = de.id
LEFT JOIN k_sol_fee_events fe
ON fe.decoded_event_id = de.id
LEFT JOIN k_sol_pool_admin_events ae
ON ae.decoded_event_id = de.id
LEFT JOIN k_sol_orderbook_events oe
ON oe.decoded_event_id = de.id
LEFT JOIN k_sol_token_account_events tae
ON tae.decoded_event_id = de.id
WHERE de.protocol_name = 'raydium_stable_swap'
AND (
tx.err_json IS NULL
OR tx.err_json = ''
OR tx.err_json = 'null'
)
AND te.id IS NULL
AND le.id IS NULL
AND pe.id IS NULL
AND fe.id IS NULL
AND ae.id IS NULL
AND oe.id IS NULL
AND tae.id IS NULL
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipTradeReason')), '') = ''
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipLiquidityReason')), '') = ''
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipLifecycleReason')), '') = ''
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipCatalogReason')), '') = ''
GROUP BY de.event_kind
ORDER BY unexplained_count DESC, de.event_kind;
-- 10. Materialization summary.
SELECT
de.event_kind,
COUNT(DISTINCT de.id) AS decoded_count,
COUNT(DISTINCT te.id) AS trade_count,
COUNT(DISTINCT le.id) AS liquidity_count,
COUNT(DISTINCT pe.id) AS lifecycle_count,
COUNT(DISTINCT fe.id) AS fee_count,
COUNT(DISTINCT ae.id) AS admin_count,
COUNT(DISTINCT oe.id) AS orderbook_count
FROM k_sol_dex_decoded_events de
LEFT JOIN k_sol_trade_events te
ON te.decoded_event_id = de.id
LEFT JOIN k_sol_liquidity_events le
ON le.decoded_event_id = de.id
LEFT JOIN k_sol_pool_lifecycle_events pe
ON pe.decoded_event_id = de.id
LEFT JOIN k_sol_fee_events fe
ON fe.decoded_event_id = de.id
LEFT JOIN k_sol_pool_admin_events ae
ON ae.decoded_event_id = de.id
LEFT JOIN k_sol_orderbook_events oe
ON oe.decoded_event_id = de.id
WHERE de.protocol_name = 'raydium_stable_swap'
GROUP BY de.event_kind
ORDER BY de.event_kind;
-- 11. Stable Swap swap amount-source closure.
-- Successful swaps must materialize from exact vault deltas.
-- Failed swaps may remain instruction-bounds-only and must not materialize as trades.
SELECT
de.event_kind,
json_extract(de.payload_json, '$.amountSource') AS amount_source,
CASE
WHEN tx.err_json IS NOT NULL
AND tx.err_json <> ''
AND tx.err_json <> 'null'
THEN 'failed'
ELSE 'success'
END AS tx_status,
COUNT(*) AS decoded_count,
COUNT(te.id) AS trade_count
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
WHERE de.protocol_name = 'raydium_stable_swap'
AND de.event_kind IN (
'raydium_stable_swap.swap_base_in',
'raydium_stable_swap.swap_base_out'
)
GROUP BY de.event_kind, amount_source, tx_status
ORDER BY de.event_kind, amount_source, tx_status;
-- 12. Stable Swap successful swap without trade and without explanation.
-- Expected result: empty.
SELECT
de.event_kind,
COUNT(*) AS unexplained_success_count
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
WHERE de.protocol_name = 'raydium_stable_swap'
AND de.event_kind IN (
'raydium_stable_swap.swap_base_in',
'raydium_stable_swap.swap_base_out'
)
AND (
tx.err_json IS NULL
OR tx.err_json = ''
OR tx.err_json = 'null'
)
AND te.id IS NULL
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipTradeReason')), '') = ''
GROUP BY de.event_kind;
-- 13. Stable Swap failed swap safety.
-- Expected result: empty.
SELECT
de.event_kind,
json_extract(de.payload_json, '$.amountSource') AS amount_source,
COUNT(*) AS failed_trade_count
FROM k_sol_dex_decoded_events de
JOIN k_sol_chain_transactions tx
ON tx.id = de.transaction_id
JOIN k_sol_trade_events te
ON te.decoded_event_id = de.id
WHERE de.protocol_name = 'raydium_stable_swap'
AND de.event_kind IN (
'raydium_stable_swap.swap_base_in',
'raydium_stable_swap.swap_base_out'
)
AND tx.err_json IS NOT NULL
AND tx.err_json <> ''
AND tx.err_json <> 'null'
GROUP BY de.event_kind, amount_source
ORDER BY de.event_kind, amount_source;