# Prompt de reprise — `khadhroony-bobobot` — `0.7.52 raydium_stable_swap` Reprise du projet `khadhroony-bobobot` après clôture de `0.7.51 raydium_amm_v4`. ## Archive de départ Utiliser la dernière archive complète du workspace intégrant les deltas validés jusqu’à : ```text 0.7.51-raydium-amm-v4-final ``` Joindre aussi les docs et SQL de validation à jour : ```text README.md ROADMAP.md CHANGELOG.md docs/DEX_DECODER_MATRIX.md docs/DEX_EVENT_COVERAGE_MATRIX.md docs/DB_EVENT_MODEL_REVIEW.md docs/reports/RAYDIUM_AMM_V4_EVENT_COVERAGE_REPORT.md docs/reports/RAYDIUM_POOL_V4_DECISION_NOTE.md docs/VALIDATION_STATUS_0_7_51_FINAL.md validation_sql/SQL_VALIDATION_RAYDIUM_AMM_V4_0_7_51.sql ``` ## État validé avant reprise `0.7.51` a clôturé `raydium_amm_v4`. Validation locale finale rapportée : ```text cargo test -p kb_lib 405 passed / 0 failed cargo clippy -p kb_lib --all-targets -- -D warnings OK ``` Dernier replay local `0.7.51` : ```text 195 replayed 0 decode skipped 195 ledger upserts 70 unsafe ledger rows 168 trades 7 liquidity 15 lifecycle 0 tokenAccount 668 candle upserts instructionObservations = 2599 resetDeleted = 1578 catalog = 61 tokens / 65 pools / 65 pairs ``` Points de clôture AMM v4 à préserver : ```text raydium_amm_v4.swap legacy = vide decoded without coverage entry = vide instruction_observations > 1 octet = vide non-swap -> trade = vide failed tx -> trade = vide unexplained successful non-materialized events = vide multi-target materialization = vide pre_initialize lifecycle audit = 7 / 7 migrate_to_open_book = orderbook only simulate_info = decoded-only raydium_pool_v4 = audit-only / pas de decoder autonome ``` ## Décision de reprise Ouvrir une nouvelle tranche : ```text 0.7.52 raydium_stable_swap ``` Code local canonique : ```text raydium_stable_swap ``` Program id canonique à utiliser comme hypothèse de départ : ```text 5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h ``` Important : upstream Git/IDL/Solscan est un indice, pas une preuve métier. Le program id doit être confirmé par corpus local via `k_sol_instruction_observations`, decoded events, coverage entries et absence de fallback upstream. ## Nouvelle base de travail Démarrer `0.7.52` sur une base SQLite vide dédiée. Avant le replay de validation complet, construire volontairement un corpus initial : ```text 1. Demo3 program_id = 5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h 2. Solscan non filtré + essais instruction= 3. backfill Demo2 de signatures contenant des instructions stable swap variées 4. backfill de pools stable swap quand Demo3/Solscan fournit un AMM/pool account fiable ``` Ne pas interpréter l’absence de résultat Solscan comme absence on-chain définitive. ## Note Solscan importante Pour `raydium_stable_swap`, il semble que Solscan ne dispose pas d’un Program IDL exploitable sur : ```text https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h#programIdl ``` Donc le filtrage Solscan par instruction peut ne pas fonctionner avec les discriminants 8 octets Carbon/Pinax. Il faut tester deux approches : ```text 1. Liens exploratoires courts : instruction=00, 01, 02, ... 2. Liens discriminants upstream 8 octets : instruction= ``` Solscan est une aide de découverte uniquement. La preuve métier reste locale : signatures backfillées, decoded events, instruction observations et coverage DB. ## Sources Git/IDL à utiliser systématiquement Sources globales : ```text https://github.com/sevenlabs-hq/carbon/tree/main/decoders https://github.com/0xfnzero/solana-streamer https://github.com/0xfnzero/sol-parser-sdk/tree/main/idl https://github.com/0xfnzero/sol-parser-sdk/tree/main/idls https://github.com/pinax-network/substreams-solana-idls/tree/main/src https://github.com/hodlwarden/solana-tx-parser/tree/main/src https://docs.vybenetwork.com/docs/available-dexs-amms ``` Sources spécifiques `raydium_stable_swap` à vérifier en priorité : ```text https://github.com/sevenlabs-hq/carbon/tree/main/decoders/raydium-stable-swap-decoder https://github.com/pinax-network/substreams-solana-idls/tree/main/src/raydium/stable https://github.com/pinax-network/substreams-solana-idls/tree/main/src/raydium/stable/idl.json https://github.com/pinax-network/substreams-solana-idls/tree/main/src/raydium/stable/instructions.rs https://github.com/pinax-network/substreams-solana-idls/tree/main/src/raydium/stable/events.rs https://github.com/pinax-network/substreams-solana-idls/tree/main/src/raydium/stable/accounts.rs ``` ## Solscan — liens exploratoires Base non filtrée : ```text https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?hide_spam=false&hide_failed=false&show_related=true&sort=desc ``` Base non filtrée sans related : ```text https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?hide_spam=false&hide_failed=false&show_related=false&sort=desc ``` ### Essais courts `instruction=00..11` Ces liens sont exploratoires. Ils ne prouvent pas que le program utilise des discriminants 1 octet ; ils servent seulement à tester le comportement Solscan quand aucun IDL n’est présent. ```text instruction=00 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=00&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=01 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=01&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=02 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=02&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=03 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=03&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=04 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=04&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=05 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=05&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=06 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=06&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=07 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=07&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=08 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=08&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=09 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=09&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=0a https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=0a&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=0b https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=0b&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=0c https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=0c&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=0d https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=0d&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=0e https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=0e&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=0f https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=0f&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=10 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=10&hide_spam=false&hide_failed=false&show_related=false&sort=desc instruction=11 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=11&hide_spam=false&hide_failed=false&show_related=false&sort=desc ``` ### Essais discriminants 8 octets Carbon/Pinax À tester aussi, mais ne pas bloquer si Solscan ne filtre rien. ```text initialize / afaf6d1f0d989bed https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=afaf6d1f0d989bed&hide_spam=false&hide_failed=false&show_related=false&sort=desc pre_initialize / ff5c572dc6acec02 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=ff5c572dc6acec02&hide_spam=false&hide_failed=false&show_related=false&sort=desc deposit / f223c68952e1f2b6 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=f223c68952e1f2b6&hide_spam=false&hide_failed=false&show_related=false&sort=desc withdraw / b712469c946da122 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=b712469c946da122&hide_spam=false&hide_failed=false&show_related=false&sort=desc swap_base_in / 2aec48a2f2182754 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=2aec48a2f2182754&hide_spam=false&hide_failed=false&show_related=false&sort=desc swap_base_out / a3d29bd0af92d596 https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h?instruction=a3d29bd0af92d596&hide_spam=false&hide_failed=false&show_related=false&sort=desc ``` ## Instructions/discriminants de départ à couvrir À partir de Carbon stable swap, à vérifier contre Pinax : ```text initialize afaf6d1f0d989bed pool_create / k_sol_pool_lifecycle_events pre_initialize ff5c572dc6acec02 pool_create deprecated/partial / k_sol_pool_lifecycle_events si pool context suffisant deposit f223c68952e1f2b6 liquidity_add / k_sol_liquidity_events withdraw b712469c946da122 liquidity_remove / k_sol_liquidity_events swap_base_in 2aec48a2f2182754 swap / k_sol_trade_events swap_base_out a3d29bd0af92d596 swap / k_sol_trade_events ``` Si Pinax expose des discriminants numériques ou une ABI non Anchor, ne pas forcer les discriminants 8 octets. Le decoder local doit suivre le layout prouvé par corpus local. ## Objectif `0.7.52` — `raydium_stable_swap` Reprendre Raydium Stable Swap au même niveau de couverture que CPMM/CLMM/AMM v4 : ```text initialize / pre_initialize pool lifecycle / pool_create deposit / withdraw swap_base_in / swap_base_out fees / admin/config si présents dans IDL/events/accounts OpenBook/Serum side effects documentés si présents side effects SPL Token / Token-2022 documentés mais non promus comme raydium_stable_swap.* directs fallback instruction_audit nettoyé quand une entrée locale spécialisée couvre l’instruction coverage entries synchronisées et rafraîchies decoded-only explicitement expliqué quand la matérialisation métier est impossible ``` ## Règles fixes ```text Rust 2024 pas de mod.rs fichiers Rust avec // file: ... pas de anyhow pas de thiserror pas de ? / unwrap / expect dans kb_lib applicatif match / if let Err / let Err = ... else rustdoc sur API publique re-exports db.rs puis lib.rs si DB modifiée ``` ## Invariants métier ```text non-trade event = jamais trade/candle failed transaction = audit-only / jamais matérialisée métier upstream Git/IDL/Solscan = indice, pas preuve métier program id upstream non promu sans corpus local side effects SPL Token / Token-2022 restent transversaux sauf preuve multi-DEX et décision DB instruction_audit et upstream_git.instruction_match doivent être nettoyés quand une entrée locale spécialisée couvre le discriminant observed_count ne doit pas obligatoirement égaler materialized_count règle de clôture : observed_count = materialized_count + decoded_only_explained_count + failed_count ``` ## Workflow conseillé 1. Créer une nouvelle base SQLite dédiée `0.7.52`. 2. Inventorier Carbon + Pinax pour `raydium_stable_swap`. 3. Vérifier explicitement le program id `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h`. 4. Vérifier si les discriminants sont 8 octets Anchor-like, 1 octet, ou autre layout. 5. Synchroniser `k_sol_dex_event_coverage_entries` avec `decoder_code = raydium_stable_swap`. 6. Utiliser Solscan seulement comme aide exploratoire ; si le filtre instruction échoue, utiliser Demo3 program_id + signatures récentes/non filtrées. 7. Backfill Demo2 signature/pool sur corpus varié. 8. Replay local avec : ```text skipDexDecode = no forceDexDecode = yes deferInstructionObservations = yes ``` 9. Vérifier : ```text coverage listed/observed/materialized residual instruction_audit residual upstream_git.instruction_match decoded without coverage entry failed tx materialization = 0 non-trade trade_count = 0 single-target materialization trade/candle only for swap events validés decoded-only explanations ``` ## SQL de contrôle minimal `0.7.52` Coverage stable swap : ```sql 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; ``` Instruction observations : ```sql 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; ``` Residual audit : ```sql 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; ``` Fallback upstream : ```sql 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; ``` Non-swap safety : ```sql 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; ``` Failed tx safety : ```sql 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; ``` Decoded without coverage : ```sql 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; ``` Multi-target materialization : ```sql 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; ``` Unexplained successful non-materialized events : ```sql 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; ``` Materialization summary : ```sql 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; ``` ## Livrables attendus ```text archive delta fichiers modifiés/ajoutés README.md / ROADMAP.md / CHANGELOG.md mis à jour docs/DEX_DECODER_MATRIX.md docs/DEX_EVENT_COVERAGE_MATRIX.md docs/DB_EVENT_MODEL_REVIEW.md docs/reports/RAYDIUM_STABLE_SWAP_EVENT_COVERAGE_REPORT.md validation_sql/SQL_VALIDATION_RAYDIUM_STABLE_SWAP_0_7_52.sql ``` Validation finale locale : ```bash cargo fmt cargo test -p kb_lib cargo clippy -p kb_lib --all-targets -- -D warnings ``` ## Critères de clôture `0.7.52` ```text tous les discriminants stable swap connus sont listés en coverage tous les discriminants stable swap connus sont observés localement ou explicitement marqués mapped_unverified instruction_audit résiduel vide pour les discriminants couverts fallback upstream_git.instruction_match résiduel vide pour les discriminants couverts decoded without coverage vide non-swap -> trade vide failed tx -> trade vide multi-target materialization vide successful decoded-only events expliqués par skip*Reason trade/candle uniquement pour swaps avec montants fiables deposit/withdraw uniquement vers liquidity initialize/pre_initialize uniquement vers lifecycle simulate/transport éventuel reste decoded-only sauf preuve métier ```