diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fc1ab1..c35bfef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,3 +80,7 @@ 0.7.47 - Upstream Git Registry / DEX discovery preparation : registre générique `upstream_git`, extension Demo3 aux targets multi-surfaces, premiers decoders audit-only OpenBook v2 et Phoenix v1, matrices DEX/event coverage, revue DB et invariant maintenu : aucune entrée upstream ne produit trade/candle sans decoder spécialisé et corpus local. 0.7.48 - Raydium CPMM event coverage clôturé : couverture instructions/events CPMM Carbon/Raydium/fnzero, table coverage synchronisée, `k_sol_instruction_observations`, recherche Demo3 par instruction/discriminant, matérialisation validée des swaps, lifecycle, fees, admin/config, deposit/withdraw et `lp_change_event`, `swap_event` audit-only, fallback upstream remplacé quand le decoder local couvre l’entrée. 0.7.49 - Raydium CLMM event coverage clôturé : 45 entrées listées, 33 instructions locales observées/décodées, 25 entrées matérialisées, ajout `k_sol_orderbook_events`, matérialisation des limit orders, liquidity, fees, rewards, admin/config et lifecycle prouvés par corpus, préparation audit-only des 11 Anchor Program-data events non observés, nettoyage des `raydium_clmm.instruction_audit` et `upstream_git.instruction_match` redondants, validation des invariants failed transaction / non-swap / trade-candle. +0.7.50-pre3 - Raydium Launchpad self-CPI/pool catalog correction : ajout du preset Demo3 `raydium_launchpad`, décodage direct des self-CPI Launchpad `trade_event` et `pool_create_event`, correction des indices `initialize*` (`pool_state=5`, `base_mint=6`, `quote_mint=7`) et routage des `initialize*` vers la matérialisation catalogue pool/pair Launchpad sans promotion trade/candle. +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-final - Clôture Raydium Launchpad et recheck Raydium : correction FK-safe du cleanup `raydium_cpmm.instruction_audit` pour le discriminant `40f4bc78a7e9690a`, conservation de `raydium_cpmm.anchor_idl_instruction` en decoded-only, suppression attendue des decoded events CPMM sans ligne coverage, documentation finale Launchpad/CPMM/CLMM et prompt de reprise `0.7.51 raydium_amm_v4`. diff --git a/Cargo.toml b/Cargo.toml index b039e13..61620e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ ] [workspace.package] -version = "0.7.49" +version = "0.7.50" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot" diff --git a/README.md b/README.md index 738c10e..65948ee 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,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 lorsqu’ils apparaîtront dans un corpus local, sans créer de trade/candle par défaut. -La prochaine tranche fonctionnelle est `0.7.50 raydium_launchpad`, avant `0.7.51 raydium_amm_v4` et `0.7.52 raydium_stable`. +La tranche fonctionnelle ouverte est `0.7.50-pre-r2`, dédiée à la clôture Raydium Launchpad puis à la re-vérification CPMM/CLMM, avant `0.7.51 raydium_amm_v4` et `0.7.52 raydium_stable`. `raydium_pool_v4` reste un audit conditionnel `0.7.53` et ne doit pas être promu sans confirmation de program id/rôle/corpus. ## Organisation documentaire @@ -257,7 +257,7 @@ Chaque DEX ou variante de DEX doit avoir sa propre étape de validation. Les fam À reprendre après les DEX effectifs, sauf si une surface est indispensable pour comprendre une migration vers un pool tradable : - `pump_fun` ; -- `raydium_launchlab` ; +- `raydium_launchpad` ; - `letsbonk` / `bonk_fun` ; - `bags` ; - `moonshot` ; @@ -382,12 +382,13 @@ La priorité immédiate après le point de reprise `0.7.43-E5C` est : 1. `0.7.48` : `raydium_cpmm` — clôturé côté event coverage ; 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` : `raydium_launchpad` ; +3. `0.7.50-pre-r2` : `raydium_launchpad` clos + re-vérification `raydium_cpmm` / `raydium_clmm` ; 4. `0.7.51` : `raydium_amm_v4` ; 5. `0.7.52` : `raydium_stable` ; -6. `0.7.53` : `pump_swap` ; -7. `0.7.54` : `pump_fun` ; -8. `0.7.55+` : Meteora, Phoenix/OpenBook, Orca puis validation progressive des autres DEX/surfaces issus du registre upstream Git. +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` ; +8. `0.7.55` : `pump_fun` ; +9. `0.7.56+` : Meteora, Phoenix/OpenBook, Orca puis validation progressive des autres DEX/surfaces issus du registre upstream Git. Garde-fous constants : @@ -494,10 +495,11 @@ La suite fonctionnelle reprend par Raydium avant Meteora : 1. `0.7.48` — `raydium_cpmm` ; 2. `0.7.49` — `raydium_clmm` ; -3. `0.7.50` — `raydium_launchpad` ; +3. `0.7.50-pre-r2` — `raydium_launchpad` + clôture CPMM/CLMM ; 4. `0.7.51` — `raydium_amm_v4` ; 5. `0.7.52` — `raydium_stable` ; -6. `0.7.53+` — Pump, Meteora, Phoenix/OpenBook, Orca puis les autres DEX/surfaces. +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. ## Note 0.7.48 — Raydium CPMM event coverage @@ -553,3 +555,54 @@ Points finalisés : La validation finale est dans `validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_49.sql`. + + +## Note 0.7.50 — Raydium Launchpad event coverage bootstrap + +La tranche `0.7.50` ouvre `raydium_launchpad` comme troisième tranche Raydium après CPMM et CLMM. Le code local canonique est `raydium_launchpad`. Le program id canonique est `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj`. + +Points préparés dans le bootstrap : + +- normalisation des entrées launch surface, DEX support matrix, catalogue et registre upstream vers `raydium_launchpad` ; +- ajout de `RAYDIUM_LAUNCHPAD_PROGRAM_ID` ; +- inventaire initial de `1` entrée programme et `26` entrées discriminées Carbon/IDL ; +- fallback `raydium_launchpad.instruction_audit` pour les instructions non mappées, avec enrichissement Anchor self-CPI (`e445a52e51cb9a1d`) ; +- mapping conservatoire des discriminants Launchpad vers `raydium_launchpad.` ; +- cible coverage initiale forcée à `decoded_events_only`, y compris pour buy/sell/trade/migration, afin d'éviter toute fausse trade/candle avant corpus local ; +- rapport `docs/reports/RAYDIUM_LAUNCHPAD_EVENT_COVERAGE_REPORT.md` ; +- SQL `validation_sql/SQL_VALIDATION_RAYDIUM_LAUNCHPAD_0_7_50.sql`. +- pre3 : Demo3 contient le preset `Raydium Launchpad`; les `initialize*` Launchpad peuvent créer le catalogue pool/pair local, tandis que `trade_event` reste non promu en trade/candle. + +`raydium_pool_v4.json` reste un indice IDL annexe. Il n'est pas promu en surface métier tant que son program id, son rôle exact et un corpus local exploitable ne sont pas confirmés. + + +## Note 0.7.50-pre-r2 — CPMM/CLMM closure re-check + +Cette tranche complète la clôture Raydium en ajoutant `cpi_event` pour CPMM/CLMM, `update_dynamic_fee_config` pour CLMM, le rattachement local des Program-data events CLMM et la table `k_sol_token_account_events` pour `create_support_mint_associated`. + +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`. + +## Note 0.7.50-final — Launchpad closure and Raydium recheck cleanup + +The final `0.7.50` cleanup keeps the Raydium CPMM discriminator `40f4bc78a7e9690a` as `raydium_cpmm.anchor_idl_instruction` decoded-only and removes stale `raydium_cpmm.instruction_audit` duplicates in an FK-safe way by unlinking `k_sol_instruction_observations.decoded_event_id` before deletion. + +Expected post-replay checks: + +```text +raydium_cpmm.instruction_audit = 0 +raydium_cpmm decoded events missing coverage row = 0 +``` + +Validation helper: + +```text +validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_AUDIT_CLEANUP_0_7_50_FINAL.sql +``` + +Next-session handoff: + +```text +docs/prompts/PROMPT_REPRISE_khadhroony-bobobot_0.7.51-raydium-amm-v4.md +``` diff --git a/ROADMAP.md b/ROADMAP.md index 0ab3157..251f47d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -28,25 +28,26 @@ Règles de planification : | `https://github.com/all-in-one-blockchain/phoenix-onchain-mm` | Source Phoenix/MM complémentaire. | | `https://docs.vybenetwork.com/docs/available-dexs-amms` | Source externe de découverte DEX/AMM, non vérifiante. | -### Plan révisé `0.7.48` à `0.7.62+` +### Plan révisé `0.7.48` à `0.7.63+` | Version cible | Scope | Objectif de clôture | |---|---|---| | `0.7.48` | `raydium_cpmm` | Clôturé : instructions/events CPMM, lifecycle, fees, admin/config, deposit/withdraw, `lp_change_event`, invariants trade/candle. | | `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` | Reprendre Launchpad comme surface Raydium prioritaire : identifier les program ids/IDL, launch, pool creation, migration, bonding éventuel, fees/admin, et rattachement au DEX effectif. | +| `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` | Reprendre AMM v4 legacy au même niveau de couverture que CPMM/CLMM : swaps, pool lifecycle, liquidity, fees/admin, side effects documentés. | | `0.7.52` | `raydium_stable` | Reprendre Raydium Stable : program ids/IDL, swaps stables, pool lifecycle, liquidity, fees/admin, invariants pricing/candles. | -| `0.7.53` | `pump_swap` | Couvrir `buy/sell` et tous les events auxiliaires disponibles : fees, cashback, volume accumulator, admin/config. | -| `0.7.54` | `pump_fun` | Traiter launch/bonding/migration ; séparer création token, buy/sell bonding, migration vers DEX effectif. | -| `0.7.55` | `meteora_dbc` | Couverture DBC : bonding curve, swap, migration, launch attribution, fees/admin, non-trade. | -| `0.7.56` | `meteora_dlmm` | Audit final de parité avec sources Git/IDL ; fermer ou documenter les audits résiduels. | -| `0.7.57` | `meteora_damm_v1` | Parité upstream complète ; résoudre les cas non matérialisés faute de pool/pair quand possible. | -| `0.7.58` | `meteora_damm_v2` | Couverture DAMM v2 complète : create, swap, liquidity, fees/admin/config ; décider trade actionability. | -| `0.7.59` | `phoenix_v1` | Finir tous les events Git disponibles en audit ; préparer mais ne pas activer trade materialization. | -| `0.7.60` | `openbook_v2` | Finir layouts logs/events ; définir conditions futures de trade/candle sans les activer par défaut. | -| `0.7.61` | `orca_whirlpools` | Reprendre Whirlpools depuis IDL/source : swaps, pools, positions, liquidity, fees/rewards. | -| `0.7.62+` | Launch surfaces / DEX candidats / validation consolidée | Moonshot/Moonit, Boop, Heaven, Bags, LetsBonk, FluxBeam, DexLab, Lifinity, Stabble, BonkSwap, GooseFX, Obric, SolFi puis base neuve multi-DEX. | +| `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.55` | `pump_fun` | Traiter launch/bonding/migration ; séparer création token, buy/sell bonding, migration vers DEX effectif. | +| `0.7.56` | `meteora_dbc` | Couverture DBC : bonding curve, swap, migration, launch attribution, fees/admin, non-trade. | +| `0.7.57` | `meteora_dlmm` | Audit final de parité avec sources Git/IDL ; fermer ou documenter les audits résiduels. | +| `0.7.58` | `meteora_damm_v1` | Parité upstream complète ; résoudre les cas non matérialisés faute de pool/pair quand possible. | +| `0.7.59` | `meteora_damm_v2` | Couverture DAMM v2 complète : create, swap, liquidity, fees/admin/config ; décider trade actionability. | +| `0.7.60` | `phoenix_v1` | Finir tous les events Git disponibles en audit ; préparer mais ne pas activer trade materialization. | +| `0.7.61` | `openbook_v2` | Finir layouts logs/events ; définir conditions futures de trade/candle sans les activer par défaut. | +| `0.7.62` | `orca_whirlpools` | Reprendre Whirlpools depuis IDL/source : swaps, pools, positions, liquidity, fees/rewards. | +| `0.7.63+` | Launch surfaces / DEX candidats / validation consolidée | Moonshot/Moonit, Boop, Heaven, Bags, LetsBonk, FluxBeam, DexLab, Lifinity, Stabble, BonkSwap, GooseFX, Obric, SolFi puis base neuve multi-DEX. | Ce plan remplace les anciens regroupements larges `0.7.50+` qui mélangeaient plusieurs DEX dans une même version. @@ -851,7 +852,7 @@ Matrice cible initiale : | `pump_swap` | AMM / swap | supporté | conserver trades/candles | | `raydium_cpmm` | AMM | supporté | conserver trades/candles | | `raydium_clmm` | CLMM | supporté | conserver trades/candles | -| `raydium_launchlab` | launch surface | planifié, program id local connu | ajouter decoder/materialization dédiée | +| `raydium_launchpad` | launch surface | 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_router` | router | partiel | ne pas matérialiser en trade direct avant preuve | | `raydium_stable_swap` | AMM legacy | planifié | traiter seulement si corpus pertinent | @@ -1231,7 +1232,7 @@ Objectif : accélérer la découverte multi-DEX en indexant les `program_id`, di Familles prioritaires à indexer en premier : -- DEX / AMM / CLMM / orderbook : `meteora_damm_v2`, `meteora_dbc`, `meteora_dlmm`, `meteora_vault`, `raydium_amm_v4`, `raydium_clmm`, `raydium_cpmm`, `raydium_launchlab`, `raydium_liquidity_locking`, `raydium_stable_swap`, `orca_whirlpools`, `fluxbeam`, `lifinity_v2`, `phoenix_v1`, `openbook_v2`, `stabble_stable_swap`, `stabble_weighted_swap`, `bonkswap`, `boop`, `moonshot`, `heaven`, `okx_dex`, `pancake_swap`, `vertigo`, `virtuals`, `wavebreak`, `onchain_labs_dex_v1`, `onchain_labs_dex_v2` ; +- DEX / AMM / CLMM / orderbook : `meteora_damm_v2`, `meteora_dbc`, `meteora_dlmm`, `meteora_vault`, `raydium_amm_v4`, `raydium_clmm`, `raydium_cpmm`, `raydium_launchpad`, `raydium_liquidity_locking`, `raydium_stable_swap`, `orca_whirlpools`, `fluxbeam`, `lifinity_v2`, `phoenix_v1`, `openbook_v2`, `stabble_stable_swap`, `stabble_weighted_swap`, `bonkswap`, `boop`, `moonshot`, `heaven`, `okx_dex`, `pancake_swap`, `vertigo`, `virtuals`, `wavebreak`, `onchain_labs_dex_v1`, `onchain_labs_dex_v2` ; - agrégateurs / ordres / perps / lending utiles au routage ou à l’analyse : `jupiter_swap`, `jupiter_dca`, `jupiter_limit_order`, `jupiter_limit_order_2`, `jupiter_perpetuals`, `jupiter_lend`, `kamino_lending`, `kamino_vault`, `kamino_farms`, `kamino_limit_order`, `drift_v2`, `marginfi_v2`, `dflow_aggregator_v4`, `zeta` ; - contexte transactionnel non DEX : `system_program`, `token_program`, `token_2022`, `associated_token_account`, `address_lookup_table`, `memo_program`, `stake_program`, `mpl_token_metadata`, `mpl_core`, `bubblegum`, `name_service`, `marinade_finance`, `solayer_restaking_program`, `swig`, `sharky`, `circle_message_transmitter_v2`, `circle_token_messenger_v2`. @@ -1291,10 +1292,18 @@ Réalisé : - préparation audit-only des 11 Anchor / `Program data` events non encore observés ; - invariants validés : aucun faux trade/candle, aucune matérialisation sur transaction échouée, `raydium_clmm.instruction_audit` résiduel à zéro. -### 6.082. Version `0.7.50` — `raydium_launchpad` event coverage -Objectif : reprendre Raydium Launchpad comme prochaine surface Raydium, avant AMM v4 et Stable. +### 6.082. Version `0.7.50-pre-r2` — Raydium CPMM/CLMM coverage closure +Objectif : clôturer la vérification CPMM/CLMM après la tranche Launchpad, en comparant le code local avec Carbon, Solscan Program IDL et `sol-parser-sdk`, puis en supprimant les familles ambiguës restantes de la matrice coverage. -À faire : identifier les program ids/IDL depuis sources Git/IDL/Solscan, couvrir launch/pool creation, migration/rattachement au DEX effectif, fees/admin/config, éventuels side effects SPL/Token-2022, et matérialiser seulement les events prouvés par corpus local. +Réalisé : ajout des entrées `cpi_event` CPMM/CLMM (`e445a52e51cb9a1d`), ajout de `update_dynamic_fee_config` CLMM (`0707500802c784f0`), rattachement local des Program-data events CLMM (`swap_event`, `pool_created_event`, `liquidity_change_event`, `create_personal_position_event`, `config_change_event`, `collect_protocol_fee_event`, `update_reward_infos_event`) et ajout de `k_sol_token_account_events` pour les événements type `create_support_mint_associated`. Les familles `unknown` restantes sont remplacées par `cpi_transport`, `liquidity_calculation`, `liquidity_change`, `position_open`, `pool_create`, `admin_config` ou `account_create` selon le cas. + +Point audit résolu : le discriminant CPMM local `40f4bc78a7e9690a` est codé comme `raydium_cpmm.anchor_idl_instruction` avec `event_family=idl_management` et `expected_db_target=k_sol_dex_decoded_events_only`. Les signatures Solscan montrent `IdlCreateAccount` / `IdlCloseAccount` sur le compte `anchor:idl`; cette entrée reste donc informative et ne doit pas produire trade, candle, liquidity, fee ou admin métier. + +Décisions de clôture CLMM : `swap_event` et `swap_router_base_in` restent `decoded_events_only` pour éviter le double comptage avec les instructions `swap` / `swap_v2`; `liquidity_calculate_event` reste diagnostic ; `close_position` / `close_protocol_position` restent décodés mais non matérialisés tant que le corpus ne fournit pas un rattachement pool/pair fiable. La matérialisation liquidity tente désormais un contexte de secours via événements frères de la même transaction quand un event CLMM porte les montants mais pas directement le pool/pair. + +Suite locale : rebrancher les bases CPMM/CLMM, rejouer `forceDexDecode=yes`, vérifier que les requêtes `unknown`, fallback upstream, audit résiduel et matérialisation attendue ne remontent plus que des cas explicitement `decoded_events_only` ou des transactions failed. + +Rapport associé : `docs/reports/RAYDIUM_CPMM_CLMM_RECHECK_REPORT_0_7_50_PRE_R2.md`. ### 6.083. Version `0.7.51` — `raydium_amm_v4` event coverage Objectif : hisser AMM v4 legacy au niveau de couverture CPMM/CLMM. @@ -1306,52 +1315,57 @@ Objectif : reprendre Raydium Stable comme troisième tranche Raydium post-CLMM. À 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. -### 6.085. Version `0.7.53` — `pump_swap` event coverage +### 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. + +À faire : confirmer s'il correspond à un program id distinct, confirmer son rôle exact par rapport à `raydium_amm_v4`, chercher un corpus exploitable, puis décider seulement ensuite si une surface dédiée est nécessaire. + +### 6.086. Version `0.7.54` — `pump_swap` event coverage Objectif : compléter `pump_swap` au-delà de `buy/sell`. À faire : couvrir fees, cashback, volume accumulator, admin/config et autres events upstream disponibles, tout en maintenant l’invariant non-trade = zéro trade/candle. -### 6.086. Version `0.7.54` — `pump_fun` launch/bonding/migration +### 6.087. Version `0.7.55` — `pump_fun` launch/bonding/migration Objectif : séparer launch/bonding de DEX effectif et valider migration vers PumpSwap ou autre surface tradable. À faire : traiter create, buy/sell bonding, update/config, mint/burn éventuels, migration/graduate et rattachement au pool tradable. -### 6.087. Version `0.7.55` — `meteora_dbc` séparé +### 6.088. Version `0.7.56` — `meteora_dbc` séparé Objectif : reprendre Meteora après les tranches Raydium et Pump, en séparant bonding/launch, swap effectif, migration et attribution d’origine. À faire : vérifier swaps exploitables, migration, lifecycle, mint/burn éventuels, launch attribution, fees/admin, sans candle artificielle sur events non pricés. -### 6.088. Version `0.7.56` — `meteora_dlmm` parité upstream finale +### 6.089. Version `0.7.57` — `meteora_dlmm` parité upstream finale Objectif : comparer la couverture locale DLMM déjà avancée avec toutes les sources Git/IDL et documenter ou fermer les audits résiduels. À faire : revalider swaps, liquidity, positions, lifecycle, fees/rewards/admin, et garder les discriminants non mappés en audit documenté. -### 6.089. Version `0.7.57` — `meteora_damm_v1` parité upstream finale +### 6.090. Version `0.7.58` — `meteora_damm_v1` parité upstream finale Objectif : compléter la tranche DAMM v1 déjà engagée, résoudre les surfaces non observées et améliorer le rattachement pool/pair quand possible. À faire : vérifier toutes les instructions upstream restantes, matérialiser uniquement les events prouvés et documenter les cas sans pool/pair local. -### 6.090. Version `0.7.58` — `meteora_damm_v2` séparé +### 6.091. Version `0.7.59` — `meteora_damm_v2` séparé Objectif : reprendre DAMM v2 comme DEX effectif séparé après disponibilité du ledger de coverage. À faire : consolider create_pool, swaps exploitables, configs dynamiques, liquidity, fees/admin, lifecycle ; conserver les swaps sans payload montant/prix fiable comme `non_actionable_trade`. -### 6.091. Version `0.7.59` — `phoenix_v1` audit-only complet +### 6.092. Version `0.7.60` — `phoenix_v1` audit-only complet Objectif : finir tous les events Git disponibles en audit, sans activer de trade/candle. À faire : couvrir `Fill`, `FillSummary`, `Fee`, `Evict`, `ExpiredOrder` et autres logs/events disponibles ; préparer le futur modèle orderbook sans matérialisation marché par défaut. -### 6.092. Version `0.7.60` — `openbook_v2` audit-only complet +### 6.093. Version `0.7.61` — `openbook_v2` audit-only complet Objectif : finir les layouts logs/events OpenBook v2 et définir les conditions futures de matérialisation orderbook/trade. À faire : vérifier fills, settle, consume events, open orders create/close, maker/taker, lots/decimals et sens économique avant toute promotion. -### 6.093. Version `0.7.61` — `orca_whirlpools` event coverage +### 6.094. Version `0.7.62` — `orca_whirlpools` event coverage Objectif : reprendre Whirlpools depuis IDL/source avec corpus dédié. À faire : swaps, pools, positions, liquidity, fees/rewards, tick arrays, mint/burn/Token-2022 si applicable. -### 6.094. Version `0.7.62+` — Launch surfaces, DEX historiques/candidats et validation consolidée +### 6.095. Version `0.7.63+` — Launch surfaces, DEX historiques/candidats et validation consolidée Objectif : traiter les surfaces restantes puis rejouer une base neuve multi-DEX. À faire : Moonshot/Moonit, Boop, Heaven, Bags, LetsBonk, FluxBeam, DexLab, Lifinity, Stabble, BonkSwap, GooseFX, Obric, SolFi et autres entrées Vybe/registry ; rapport coverage par DEX/event, zéro faux trade/candle, corpus documentés, matrices cohérentes, diagnostics bloquants à zéro. @@ -1550,19 +1564,20 @@ Ordre de travail recommandé pour la suite : 5. `0.7.48-pre` : event coverage + DB model checkpoint — clos après table, sync upstream, refresh counts, diagnostics et profil validation ; 6. `0.7.48` : `raydium_cpmm` — clos ; 7. `0.7.49` : `raydium_clmm` — clos ; -8. `0.7.50` : `raydium_launchpad` ; +8. `0.7.50-pre-r2` : `raydium_launchpad` clos + re-vérification CPMM/CLMM ; 9. `0.7.51` : `raydium_amm_v4` ; 10. `0.7.52` : `raydium_stable` ; -11. `0.7.53` : `pump_swap` ; -12. `0.7.54` : `pump_fun` ; -13. `0.7.55` : `meteora_dbc` ; -14. `0.7.56` : `meteora_dlmm` parité upstream finale ; -15. `0.7.57` : `meteora_damm_v1` parité upstream finale ; -16. `0.7.58` : `meteora_damm_v2` ; -17. `0.7.59` : `phoenix_v1` audit-only complet ; -18. `0.7.60` : `openbook_v2` audit-only complet ; -19. `0.7.61` : `orca_whirlpools` ; -20. `0.7.62+` : launch surfaces, DEX candidats/historiques et validation consolidée. +11. `0.7.53` : `raydium_pool_v4` audit conditionnel ; +12. `0.7.54` : `pump_swap` ; +13. `0.7.55` : `pump_fun` ; +14. `0.7.56` : `meteora_dbc` ; +15. `0.7.57` : `meteora_dlmm` parité upstream finale ; +16. `0.7.58` : `meteora_damm_v1` parité upstream finale ; +17. `0.7.59` : `meteora_damm_v2` ; +18. `0.7.60` : `phoenix_v1` audit-only complet ; +19. `0.7.61` : `openbook_v2` audit-only complet ; +20. `0.7.62` : `orca_whirlpools` ; +21. `0.7.63+` : launch surfaces, DEX candidats/historiques et validation consolidée. Garde-fous constants : @@ -1634,4 +1649,8 @@ 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. -La suite après `0.7.49 raydium_clmm` reprend en `0.7.50` par `raydium_launchpad`, puis `0.7.51 raydium_amm_v4` et `0.7.52 raydium_stable`, 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` 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. + +### Note `0.7.50-final` — clôture Launchpad + recheck Raydium + +`0.7.50` se clôture avec `raydium_launchpad` et la re-vérification CPMM/CLMM. Le dernier correctif cible le cleanup FK-safe des anciens `raydium_cpmm.instruction_audit` `40f4bc78a7e9690a`, maintenant remplacés par `raydium_cpmm.anchor_idl_instruction` decoded-only. La suite planifiée reste `0.7.51 raydium_amm_v4`, puis `0.7.52 raydium_stable`, avec découverte accélérée par Solscan `instruction=` et validation obligatoire par corpus local. diff --git a/docs/DB_EVENT_MODEL_REVIEW.md b/docs/DB_EVENT_MODEL_REVIEW.md index 86b6556..3a2bbaf 100644 --- a/docs/DB_EVENT_MODEL_REVIEW.md +++ b/docs/DB_EVENT_MODEL_REVIEW.md @@ -320,3 +320,28 @@ Aucune nouvelle table transversale n'est ajoutée dans ce delta : La table `k_sol_instruction_observations` reste technique : elle sert à trouver des signatures et discriminants observés localement, sans être une preuve métier. + +## Note `0.7.50-pre-r2` — `k_sol_token_account_events` + +La re-vérification Raydium CLMM introduit une table dédiée `k_sol_token_account_events` pour les événements de cycle de vie de comptes token qui ne sont ni des swaps, ni de la liquidité, ni du lifecycle pool. Le premier cas d'usage est `raydium_clmm.create_support_mint_associated`. + +### Règle de matérialisation + +- `create_support_mint_associated` cible `k_sol_token_account_events`. +- `cpi_event` reste `k_sol_dex_decoded_events_only` : c'est un transport Anchor/CPI, pas un event métier autonome. +- `liquidity_calculate_event` reste `k_sol_dex_decoded_events_only` : c'est un event de calcul/diagnostic, pas une mutation de liquidité fiable. +- `create_operation_account` et `update_operation_account` relèvent de `k_sol_pool_admin_events` quand le corpus le permet. + +### Colonnes principales + +`k_sol_token_account_events` conserve `transaction_id`, `decoded_event_id`, `dex_id`, `pool_id`, `pair_id`, `signature`, `instruction_index`, `slot`, `protocol_name`, `program_id`, `event_kind`, `token_account`, `token_mint`, `owner_wallet`, `account_action`, `payload_json`, `executed_at` et `created_at`. + +Cette table permet de suivre les événements Token-2022/ATA significatifs sans les confondre avec les trades ou les liquidités. + +## Note `0.7.50-final` — FK-safe cleanup for instruction observations + +`k_sol_instruction_observations` is a technical index table. When a legacy `*.instruction_audit` decoded event is replaced by a local specialized event, existing observation rows can still point to the old decoded event id on already-created SQLite databases. + +The final 0.7.50 cleanup therefore unlinks `k_sol_instruction_observations.decoded_event_id` before deleting replaced CPMM instruction-audit rows. New databases define the `decoded_event_id` foreign key with `ON DELETE SET NULL` for the same reason. + +This is not a business-table promotion. It only keeps the technical observation index consistent with decoded event cleanup. diff --git a/docs/DEX_DECODER_MATRIX.md b/docs/DEX_DECODER_MATRIX.md index 69bb1e4..319f1fa 100644 --- a/docs/DEX_DECODER_MATRIX.md +++ b/docs/DEX_DECODER_MATRIX.md @@ -1,4 +1,4 @@ -# DEX Decoder Matrix — `khadhroony-bobobot` `0.7.47-1FE5` +# DEX Decoder Matrix — `khadhroony-bobobot` `0.7.50-pre-r2` Cette matrice complète `kb_lib/src/dex_support_matrix.rs`. Elle documente **ce qui est fait**, **ce qui reste à faire**, et **le niveau de preuve attendu** par DEX/version. @@ -28,24 +28,25 @@ Cette matrice complète `kb_lib/src/dex_support_matrix.rs`. Elle documente **ce | Ordre | DEX/version | État actuel | Fait | Reste à faire | |---:|---|---|---|---| -| 1 | `raydium_cpmm` | `supported / 0.7.48 closed` | Couverture CPMM clôturée : swaps, lifecycle, fees, admin/config, deposit/withdraw, `lp_change_event`, `swap_event` audit-only. | Réouvrir seulement en cas de nouveau corpus ou divergence upstream. | -| 2 | `raydium_clmm` | `supported / 0.7.49 closed` | Couverture CLMM clôturée : 45 entrées listées, 33 instructions observées/décodées, 25 matérialisées, orderbook events, fallback upstream nettoyé. | 11 Anchor Program-data events restent préparés mais `upstream_git_unverified` faute de corpus local. | -| 3 | `raydium_launchpad` | `planned / 0.7.50` | Prochaine tranche Raydium. | Identifier program ids/IDL, launch/pool creation, migration, fees/admin/config, corpus Solscan/Demo3 puis decoder spécialisé. | +| 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. | +| 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 planned` | Swaps AMM v4 legacy matérialisés. | Reprendre AMM v4 au niveau CPMM/CLMM : pool lifecycle, liquidity, fees/admin, side effects, fallback cleanup. | | 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. | -| 6 | `pump_swap` | `supported / 0.7.53 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_fun` | `partial / 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 | `meteora_dbc` | `partial` | Swaps/instruction audits observés ; Demo3 donne du corpus. | Couverture complète DBC : launch/bonding curve, swap, migration, config/admin, fees ; matérialiser seulement ce qui est prouvé. | -| 9 | `meteora_dlmm` | `supported` | Couverture avancée validée en `0.7.45` : swaps, liquidity, positions, lifecycle, fees ; non-trade matérialisé. | Résoudre les audits résiduels non mappés ; comparer Carbon/IDL pour events rewards/admin restants ; revalidation base neuve. | -| 10 | `meteora_damm_v1` | `supported / partial events` | Couverture `0.7.46` : swap, create_pool, add/remove liquidity, claim_fee, create_lock_escrow, lock_liquidity. | Vérifier les surfaces upstream non observées ; améliorer rattachement pool/pair pour remove_liquidity non matérialisés ; revalidation stricte. | -| 11 | `meteora_damm_v2` | `partial` | `swap`, `instruction_audit`, registry/discriminants et corpus Demo3 existent. | Couvrir tous les events Carbon/source : create pool, liquidity, fees, dynamic config, admin ; déterminer actionability des swaps ; matérialiser si montants fiables. | -| 12 | `phoenix_v1` | `audit-only` | Decoder local audit-only ; `log_audit`, order place/cancel, withdraw ; parsing strict `0x0f`; events `Reduce`, `Place`, `TimeInForce` observés ; `trade_count=0`. | Terminer tous les events Git : `Fill`, `FillSummary`, `Fee`, `Evict`, `ExpiredOrder`, etc. ; ajouter counts/flags audit ; seulement ensuite étudier trade materialization. | -| 13 | `openbook_v2` | `audit-only` | Decoder local audit-only ; instructions order/cancel/consume/settle ; `Program data` mappé : `FillLog`, `OpenOrdersPositionLog`, `TotalOrderFillEvent`, `SettleFundsLog`; `trade_count=0`. | Vérifier layouts fill/out et sens maker/taker/base/quote ; ajouter table audit éventuelle ; ne matérialiser trades qu’après validation du sens économique. | -| 14 | `orca_whirlpools` | `partial` | Premier decoder historique présent ; swaps/create_pool partiels. | Comparer Carbon/IDL complet ; couvrir liquidity, positions, fees/rewards, tick arrays ; valider swaps exploitables et non-trades. | -| 15 | `legacy_launch_candidates` | `planned launch` | Anciennes entrées launch à réévaluer après `raydium_launchpad`. | Ne pas confondre Launchpad, LaunchLab, CPMM/CLMM/AMM v4 ; rattacher aux pools tradables seulement après corpus. | -| 16 | `meteora_vault` | `to_verify` | Présent comme indice upstream / compte associé. | Corpus direct obligatoire ; decoder séparé si events vault réels ; aucune promotion via DAMM indirect. | -| 17 | `fluxbeam` | `partial/to_verify` | Decoder initial existant ; Demo3 peut produire des candidats. | Vérifier source/IDL ; compléter swap, pool, liquidity, fees/admin ; matérialisation uniquement après corpus. | -| 18 | `dexlab` | `partial/to_verify` | Decoder initial historique ; ancienne entrée beta supprimée. | Reconfirmer program id/source ; décoder events disponibles ; distinguer DexLab natif et liens OpenBook/market. | +| 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. | +| 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. | +| 9 | `meteora_dbc` | `partial / 0.7.56 planned` | Swaps/instruction audits observés ; Demo3 donne du corpus. | Couverture complète DBC : launch/bonding curve, swap, migration, config/admin, fees ; matérialiser seulement ce qui est prouvé. | +| 10 | `meteora_dlmm` | `supported / 0.7.57 parity` | Couverture avancée validée en `0.7.45` : swaps, liquidity, positions, lifecycle, fees ; non-trade matérialisé. | Résoudre les audits résiduels non mappés ; comparer Carbon/IDL pour events rewards/admin restants ; revalidation base neuve. | +| 11 | `meteora_damm_v1` | `supported / 0.7.58 parity` | Couverture `0.7.46` : swap, create_pool, add/remove liquidity, claim_fee, create_lock_escrow, lock_liquidity. | Vérifier les surfaces upstream non observées ; améliorer rattachement pool/pair pour remove_liquidity non matérialisés ; revalidation stricte. | +| 12 | `meteora_damm_v2` | `partial / 0.7.59 planned` | `swap`, `instruction_audit`, registry/discriminants et corpus Demo3 existent. | Couvrir tous les events Carbon/source : create pool, liquidity, fees, dynamic config, admin ; déterminer actionability des swaps ; matérialiser si montants fiables. | +| 13 | `phoenix_v1` | `audit-only / 0.7.60 planned` | Decoder local audit-only ; `log_audit`, order place/cancel, withdraw ; parsing strict `0x0f`; events `Reduce`, `Place`, `TimeInForce` observés ; `trade_count=0`. | Terminer tous les events Git : `Fill`, `FillSummary`, `Fee`, `Evict`, `ExpiredOrder`, etc. ; ajouter counts/flags audit ; seulement ensuite étudier trade materialization. | +| 14 | `openbook_v2` | `audit-only / 0.7.61 planned` | Decoder local audit-only ; instructions order/cancel/consume/settle ; `Program data` mappé : `FillLog`, `OpenOrdersPositionLog`, `TotalOrderFillEvent`, `SettleFundsLog`; `trade_count=0`. | Vérifier layouts fill/out et sens maker/taker/base/quote ; ajouter table audit éventuelle ; ne matérialiser trades qu’après validation du sens économique. | +| 15 | `orca_whirlpools` | `partial / 0.7.62 planned` | Premier decoder historique présent ; swaps/create_pool partiels. | Comparer Carbon/IDL complet ; couvrir liquidity, positions, fees/rewards, tick arrays ; valider swaps exploitables et non-trades. | +| 16 | `legacy_launch_candidates` | `planned launch` | Anciennes entrées launch à réévaluer après `raydium_launchpad`. | Ne pas confondre Launchpad, LaunchLab, CPMM/CLMM/AMM v4 ; rattacher aux pools tradables seulement après corpus. | +| 17 | `meteora_vault` | `to_verify` | Présent comme indice upstream / compte associé. | Corpus direct obligatoire ; decoder séparé si events vault réels ; aucune promotion via DAMM indirect. | +| 18 | `fluxbeam` | `partial/to_verify` | Decoder initial existant ; Demo3 peut produire des candidats. | Vérifier source/IDL ; compléter swap, pool, liquidity, fees/admin ; matérialisation uniquement après corpus. | +| 19 | `dexlab` | `partial/to_verify` | Decoder initial historique ; ancienne entrée beta supprimée. | Reconfirmer program id/source ; décoder events disponibles ; distinguer DexLab natif et liens OpenBook/market. | | 19 | `lifinity_v2` | `to_verify` | Program id listé par sources externes/Vybe ; pas de corpus concluant. | Trouver IDL/source ; Demo3 par program/market ; audit-only d’abord. | | 20 | `stabble_stable_swap` / `stabble_weighted_swap` | `to_verify` | Program ids/indices via sources externes ; candidats Demo3 observables. | Source/IDL + corpus + decoder audit-only ; déterminer surface AMM et montants exploitables. | | 21 | `bonkswap` | `to_verify` | Program id/Carbon/Vybe selon registre ; swaps candidats possibles. | Vérifier program id, source et corpus ; décoder tous events ; pas de trade sans montants. | @@ -95,7 +96,7 @@ Un event peut devenir `materialized` uniquement si : | `raydium_cpmm` | `dex_effective` | `AMM` | `known` | oui | oui | oui | `supported` | | | `raydium_clmm` | `dex_effective` | `CLMM` | `known` | oui | oui | oui | `supported` | | | `raydium_amm_v4` | `dex_effective` | `AMM` | `known` | oui | oui | oui | `supported` | | -| `raydium_launchlab` | `launch_surface` | `launch` | `known` | non | non | non | `planned` | decoder_and_materialization_not_enabled | +| `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_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 | @@ -236,3 +237,18 @@ Le registre est complété avec les entrées issues de l'IDL officiel Raydium no Règle de clôture : les positions CLMM, fees/rewards et surfaces limit-order ne doivent produire aucune ligne trade/candle tant que le sens économique, les montants, les comptes et les mints ne sont pas prouvés par replay local. + + +## Note `0.7.50-pre-r2` — CPMM/CLMM source parity + +La clôture `0.7.50-pre-r2` complète les tranches `0.7.48` et `0.7.49` sans rouvrir leur logique trade/candle : + +- `raydium_cpmm.cpi_event` est ajouté comme transport Anchor/CPI decoded-only, distinct de `swap_base_input` / `swap_base_output`. +- Le discriminant CPMM `40f4bc78a7e9690a` est codé comme `raydium_cpmm.anchor_idl_instruction` decoded-only après inspection Solscan : gestion Anchor IDL, aucune matérialisation métier. +- `raydium_clmm.cpi_event` et `raydium_clmm.update_dynamic_fee_config` sont ajoutés depuis Carbon. +- Les Program-data events CLMM reçoivent des `local_event_kind` et familles explicites. +- `create_support_mint_associated` introduit une cible métier spécialisée : `k_sol_token_account_events`. + +## Note `0.7.50-final` — Raydium CPMM post-Launchpad recheck + +`raydium_cpmm` remains closed. The final post-Launchpad cleanup removes the three legacy `raydium_cpmm.instruction_audit` rows for discriminator `40f4bc78a7e9690a` after they have been replaced by the local decoded-only `raydium_cpmm.anchor_idl_instruction` entry. This does not promote the discriminator to a trade, liquidity, fee, admin or lifecycle event. diff --git a/docs/DEX_EVENT_COVERAGE_MATRIX.md b/docs/DEX_EVENT_COVERAGE_MATRIX.md index a714d07..c32a8cc 100644 --- a/docs/DEX_EVENT_COVERAGE_MATRIX.md +++ b/docs/DEX_EVENT_COVERAGE_MATRIX.md @@ -1,4 +1,4 @@ -# DEX Event Coverage Matrix — `khadhroony-bobobot` `0.7.49` +# DEX Event Coverage Matrix — `khadhroony-bobobot` `0.7.50-pre-r2` Cette matrice complète `docs/DEX_DECODER_MATRIX.md` avec une lecture par familles d'événements. Elle ne remplace pas la preuve locale : une entrée Git/IDL reste un indice tant qu'elle n'est pas observée dans le corpus local puis validée par replay et SQL. @@ -96,7 +96,93 @@ Sources inventoriées : Carbon `raydium-clmm-decoder`, fnzero `sol-parser-sdk`, Les 11 Anchor / `Program data` events restent `upstream_git_unverified` et préparés audit-only faute d’observation locale : `collect_personal_fee_event`, `collect_protocol_fee_event`, `config_change_event`, `create_personal_position_event`, `decrease_liquidity_event`, `increase_liquidity_event`, `liquidity_calculate_event`, `liquidity_change_event`, `pool_created_event`, `swap_event`, `update_reward_infos_event`. -## `0.7.50` — `raydium_launchpad` planned +## `0.7.50` — `raydium_launchpad` bootstrap -Prochaine tranche : identifier program ids/IDL, lister instructions/events/discriminants, constituer corpus Demo3/Solscan/Demo2, puis appliquer les mêmes règles de coverage et matérialisation que CPMM/CLMM. +Sources inventoriées : Carbon `raydium-launchpad-decoder`, Solscan Program IDL `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj`, fnzero IDL listings, Raydium SDK Launchpad examples. Code local canonique : `raydium_launchpad`. Aucun alias Rust `raydium_launchlab` ne doit être conservé dans l’API publique. +État de départ préparé : `1` entrée programme et `26` entrées discriminées listées depuis Carbon/IDL. Toutes les entrées Launchpad sont gardées `decoded_events_only` en bootstrap, même si leur famille inférée est `swap`, `pool_create`, `fee`, `admin_config`, `launch` ou `migration`. La matérialisation métier est volontairement différée jusqu'au corpus local. + +| Famille | Entrées Raydium Launchpad | Statut `0.7.50` bootstrap | Cible DB initiale | Justification / règle | +|---|---|---|---|---| +| `swap` | `buy_exact_in`, `buy_exact_out`, `sell_exact_in`, `sell_exact_out`, `trade_event` | `upstream_git_mapped_unverified` puis `decoded/audit-only` si observé | `k_sol_dex_decoded_events_only` | Aucun trade/candle avant preuve locale de montants, sens et absence de doublon avec la surface DEX de migration. | +| `pool_create` | `initialize`, `initialize_v2`, `initialize_with_token_2022`, `pool_create_event` | `upstream_git_mapped_unverified` | `decoded_events_only` | Lifecycle launch/pool non promu tant que le compte pool/mint/quote n'est pas validé localement. | +| `add_liquidity` | `-` direct confirmé | `not_applicable` | `-` | Peut apparaître via DEX de destination ou SPL side effects, pas comme instruction Launchpad directe prouvée. | +| `remove_liquidity` | `-` direct confirmé | `not_applicable` | `-` | Même règle que `add_liquidity`. | +| `position_open` | `-` | `not_applicable` | `-` | Pas de position CLMM/LP NFT directe confirmée. | +| `position_close` | `-` | `not_applicable` | `-` | Pas de position CLMM/LP NFT directe confirmée. | +| `fee` | `claim_creator_fee`, `claim_platform_fee`, `claim_platform_fee_from_vault`, `claim_vested_event`, `claim_vested_token`, `collect_fee`, `collect_migrate_fee` | `upstream_git_mapped_unverified` puis `decoded/audit-only` si observé | `decoded_events_only` | Fee/vesting/migration fee utile pour stratégie, mais pas matérialisé sans corpus. | +| `reward` | `-` direct confirmé | `not_applicable` | `-` | Vesting n'est pas promu en reward transversal dans cette tranche. | +| `admin/config` | `create_config`, `create_platform_config`, `remove_platform_curve_param`, `update_config`, `update_platform_config`, `update_platform_curve_param` | `upstream_git_mapped_unverified` puis `decoded/audit-only` si observé | `decoded_events_only` | Admin/config utile pour audit, pas de table métier promue. | +| `mint` | SPL Token / Token-2022 side effects possibles | `indirect` | `decoded_events_only` | Ne devient pas `raydium_launchpad.*` sans instruction directe du programme. | +| `burn` | SPL Token / Token-2022 side effects possibles | `indirect` | `decoded_events_only` | Même règle que `mint`. | +| `transfer` | SPL Token / Token-2022 side effects attendus | `indirect` | `decoded_events_only` | Même règle que CPMM/CLMM : side effect transversal. | +| `account_create` | `create_vesting_account`, `create_vesting_event` | `upstream_git_mapped_unverified` | `decoded_events_only` | Vesting/account audit uniquement, pas de nouvelle table transversale. | +| `account_close` | `-` direct confirmé | `not_applicable` | `-` | Aucun close direct Launchpad confirmé. | +| `wrap_sol` | Side effect utilisateur/router possible | `indirect` | `decoded_events_only` | Hors programme Launchpad direct. | +| `unwrap_sol` | Side effect utilisateur/router possible | `indirect` | `decoded_events_only` | Hors programme Launchpad direct. | +| `order_place` | `-` | `not_applicable` | `-` | Launchpad n'est pas traité comme orderbook. | +| `order_cancel` | `-` | `not_applicable` | `-` | Launchpad n'est pas traité comme orderbook. | +| `order_fill` | `-` | `not_applicable` | `-` | Launchpad n'est pas traité comme orderbook. | +| `consume_events` | `-` | `not_applicable` | `-` | Launchpad n'est pas traité comme orderbook. | +| `settle_funds` | `-` | `not_applicable` | `-` | Launchpad n'est pas traité comme orderbook. | +| `vault_deposit` | `-` direct confirmé | `not_applicable` | `-` | Aucun vault direct Launchpad confirmé. | +| `vault_withdraw` | `-` direct confirmé | `not_applicable` | `-` | Aucun vault direct Launchpad confirmé. | +| `lock` | `-` direct confirmé | `not_applicable` | `-` | Liquidity locking reste une autre surface Raydium. | +| `unlock` | `-` direct confirmé | `not_applicable` | `-` | Liquidity locking reste une autre surface Raydium. | +| `launch` | `initialize*`, `pool_create_event` | `upstream_git_mapped_unverified` | `decoded_events_only` | La promotion `k_sol_launch_events` est différée. | +| `migration` | `migrate_to_amm`, `migrate_to_cpswap`, `collect_migrate_fee` | `upstream_git_mapped_unverified` | `decoded_events_only` | Destination AMM/CPMM à valider localement avant liaison métier. | +| `stake` | `-` | `not_applicable` | `-` | Aucun stake direct Launchpad confirmé. | +| `unstake` | `-` | `not_applicable` | `-` | Aucun unstake direct Launchpad confirmé. | +| `unknown/unmapped audit` | `raydium_launchpad.instruction_audit` | `audit-only` | `decoded_events_only` | Résidu à réduire par discriminant après backfill ciblé et replay. | + +Rapport détaillé : `docs/reports/RAYDIUM_LAUNCHPAD_EVENT_COVERAGE_REPORT.md`. + +SQL de validation : `validation_sql/SQL_VALIDATION_RAYDIUM_LAUNCHPAD_0_7_50.sql`. + + + +### 0.7.50-pre3 Launchpad note + +pre3 correction: Launchpad self-CPI selector `e445a52e51cb9a1d` is transport only. Known embedded event discriminators are decoded as `raydium_launchpad.trade_event` and `raydium_launchpad.pool_create_event`. The Launchpad `initialize*` instruction family now supplies pool/pair catalog context; `trade_event` remains decoded-only until trade/candle promotion is explicitly proven. + + +## Note `0.7.50-pre-r2` — Raydium CPMM/CLMM closure re-check + +Sources ajoutées à la vérification CPMM/CLMM : Carbon `raydium-cpmm-decoder`, Carbon `raydium-clmm-decoder`, Solscan Program IDL CPMM/CLMM et `sol-parser-sdk` IDL Raydium. + +### CPMM + +- `cpi_event` est ajouté à la matrice CPMM avec `event_family=cpi_transport`, `expected_db_target=k_sol_dex_decoded_events_only`, `local_event_kind=raydium_cpmm.cpi_event` et discriminant `e445a52e51cb9a1d`. +- `swap_event` reste `decoded_events_only` pour éviter le double comptage avec `swap_base_input` / `swap_base_output`. +- Le discriminant local `40f4bc78a7e9690a` est codé comme `raydium_cpmm.anchor_idl_instruction`, `event_family=idl_management`, `expected_db_target=k_sol_dex_decoded_events_only`, après inspection Solscan des logs `IdlCreateAccount` / `IdlCloseAccount`. + +### CLMM + +- `cpi_event` est ajouté à la matrice CLMM avec `event_family=cpi_transport`, `expected_db_target=k_sol_dex_decoded_events_only`, `local_event_kind=raydium_clmm.cpi_event` et discriminant `e445a52e51cb9a1d`. +- `update_dynamic_fee_config` est ajouté avec `event_family=admin_config`, `expected_db_target=k_sol_pool_admin_events`, `local_event_kind=raydium_clmm.update_dynamic_fee_config` et discriminant `0707500802c784f0`. +- Les Program-data events CLMM sont maintenant rattachés localement : `swap_event`, `pool_created_event`, `liquidity_change_event`, `create_personal_position_event`, `decrease_liquidity_event`, `increase_liquidity_event`, `collect_protocol_fee_event`, `config_change_event`, `update_reward_infos_event`. +- `swap_event` et `swap_router_base_in` restent `decoded_events_only` : les trades canoniques sont `swap` / `swap_v2`, afin d'éviter le double comptage et la matérialisation de routes sans pool direct. +- `liquidity_calculate_event` est classé `liquidity_calculation` et reste `decoded_events_only`, car il sert au diagnostic/calcul et non à une mutation de liquidité fiable. +- `create_support_mint_associated` cible la nouvelle table `k_sol_token_account_events`. +- `create_operation_account` et `update_operation_account` sont normalisés en `admin_config`. + +Validation attendue après replay : aucune entrée CPMM/CLMM ne doit rester en `event_family='unknown'` hors `program`, les fallbacks `upstream_git.instruction_match` ne doivent pas dupliquer une entrée locale couverte, et les transactions failed ne doivent matérialiser ni trade, ni liquidity, ni fee/admin/reward/orderbook/token-account. + +Rapport associé : `docs/reports/RAYDIUM_CPMM_CLMM_RECHECK_REPORT_0_7_50_PRE_R2.md`. + +## Note `0.7.50-final` — CPMM audit cleanup after Launchpad recheck + +The `raydium_cpmm` recheck identified a legacy residual `raydium_cpmm.instruction_audit` row for discriminator `40f4bc78a7e9690a`. The discriminator is locally mapped as `raydium_cpmm.anchor_idl_instruction` and belongs to Anchor IDL management, not to AMM business activity. + +Final rule: + +| Decoder | Entry | Family | DB target | Decision | +|---|---|---|---|---| +| `raydium_cpmm` | `anchor_idl_instruction` / `40f4bc78a7e9690a` | `idl_management` | `k_sol_dex_decoded_events_only` | Keep decoded-only; remove legacy `raydium_cpmm.instruction_audit` duplicates after replay/coverage refresh. | + +Expected residual checks after final replay: + +```text +raydium_cpmm.instruction_audit = 0 +raydium_cpmm decoded events missing coverage row = 0 +``` diff --git a/docs/RAYDIUM_LAUNCHPAD_EVENT_COVERAGE_REPORT.md b/docs/RAYDIUM_LAUNCHPAD_EVENT_COVERAGE_REPORT.md new file mode 100644 index 0000000..63f1372 --- /dev/null +++ b/docs/RAYDIUM_LAUNCHPAD_EVENT_COVERAGE_REPORT.md @@ -0,0 +1,211 @@ +# Raydium Launchpad event coverage report — `0.7.50` + +## Scope + +`0.7.50` opens the `raydium_launchpad` tranche after the functional closure of `0.7.49 raydium_clmm`. + +Local canonical decoder/surface code: + +```text +raydium_launchpad +``` + +Canonical program id: + +```text +LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj +``` + +The legacy local name `raydium_launchlab` is not kept in the public Rust API. Coverage rows, upstream registry rows, launch origin entries, and support matrix rows use `raydium_launchpad`. + +## Sources used + +Primary source hints for this tranche: + +- Carbon decoder registry/source: `sevenlabs-hq/carbon/decoders/raydium-launchpad-decoder`. +- Solscan Program IDL/account page: `https://solscan.io/account/LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj#programIdl`. +- fnzero IDL sources: `sol-parser-sdk` / `solana-program-idls` listings, including `raydium_launchpad.json` and separate `raydium_pool_v4.json` as an audit-only source. +- Raydium SDK Launchpad examples for account-shape hints only. + +These sources are not treated as final business proof. Promotion still requires local corpus observation and SQL validation. + +## Bootstrap implementation delta + +Implemented in this delta: + +- `RAYDIUM_LAUNCHPAD_PROGRAM_ID` added as the canonical public constant. +- Upstream generated registry rows normalized from `raydium_launchlab` to `raydium_launchpad`. +- Built-in launch surface code normalized to `raydium_launchpad`. +- DEX support/catalog entries normalized to `raydium_launchpad`. +- Raydium instruction audit fallback now recognizes Launchpad program id. +- Launchpad mapped instruction fallback added for locally listed Launchpad discriminators. +- Coverage target override keeps Launchpad rows `decoded_events_only` until corpus promotion. +- SQL validation file added for the `0.7.50` tranche. + +## Listed Launchpad entries + +The local upstream registry now lists one program entry plus the following 26 discriminator entries. + +| Entry kind | Entry name | Discriminator | Initial family | Initial DB target | +|---|---:|---:|---|---| +| instruction | `buy_exact_in` | `faea0d7bd59c13ec` | swap | decoded_events_only | +| instruction | `buy_exact_out` | `18d3742869039938` | swap | decoded_events_only | +| instruction | `claim_creator_fee` | `1a618acb84ab8dfc` | fee | decoded_events_only | +| instruction | `claim_platform_fee` | `9c27d0874ced3d48` | fee | decoded_events_only | +| instruction | `claim_platform_fee_from_vault` | `75f1c6a8f8da501d` | fee | decoded_events_only | +| event | `claim_vested_event` | `15c2725778d3e220` | fee/vesting audit | decoded_events_only | +| instruction | `claim_vested_token` | `3121681ebd9d4f23` | fee/vesting audit | decoded_events_only | +| instruction | `collect_fee` | `3cadf767045d8230` | fee | decoded_events_only | +| instruction | `collect_migrate_fee` | `ffba96dfeb76c9ba` | fee/migration audit | decoded_events_only | +| instruction | `create_config` | `c9cff3724b6f2fbd` | admin_config | decoded_events_only | +| instruction | `create_platform_config` | `b05ac4affd71dc14` | admin_config | decoded_events_only | +| instruction | `create_vesting_account` | `81b2020dd9ace6da` | account_create/vesting audit | decoded_events_only | +| event | `create_vesting_event` | `96980bb334d2bf7d` | account_create/vesting audit | decoded_events_only | +| instruction | `initialize` | `afaf6d1f0d989bed` | pool_create/launch | decoded_events_only | +| instruction | `initialize_v2` | `4399af27da102620` | pool_create/launch | decoded_events_only | +| instruction | `initialize_with_token_2022` | `25be7ede2c9aab11` | pool_create/launch | decoded_events_only | +| instruction | `migrate_to_amm` | `cf52c091fecf91df` | migration | decoded_events_only | +| instruction | `migrate_to_cpswap` | `885cc8671cda908c` | migration | decoded_events_only | +| event | `pool_create_event` | `97d7e20976a173ae` | pool_create | decoded_events_only | +| instruction | `remove_platform_curve_param` | `1b1e3ea95de01891` | admin_config | decoded_events_only | +| instruction | `sell_exact_in` | `9527de9bd37c981a` | swap | decoded_events_only | +| instruction | `sell_exact_out` | `5fc8472208090ba6` | swap | decoded_events_only | +| event | `trade_event` | `bddb7fd34ee661ee` | swap | decoded_events_only | +| instruction | `update_config` | `1d9efcbf0a53db63` | admin_config | decoded_events_only | +| instruction | `update_platform_config` | `c33c4c81922d438f` | admin_config | decoded_events_only | +| instruction | `update_platform_curve_param` | `8a908afadc800439` | admin_config | decoded_events_only | + +Notes: + +- The buy/sell instruction account hints currently use account index `4` as candidate pool account and indexes `9`/`10` as candidate token mints, based on Carbon/Raydium Launchpad account shape hints. This is an audit helper, not a materialization proof. +- Fee/admin/migration/vesting entries intentionally do not infer pool/token accounts until corpus confirms the account semantics. +- Program-data transport is represented by `cpi_event`; embedded events are decoded by their own discriminators and materialized only when their event family has a validated target. + +## Family audit matrix + +| Family | Launchpad status in `0.7.50` final | Decision | +|---|---|---| +| swap | `trade_event` materialized as trades/candles; buy/sell instructions materialized as launch breadcrumbs. | No duplicate trades from instruction breadcrumbs. | +| pool_create | `initialize`, `initialize_v2`, `initialize_with_token_2022`, `pool_create_event`. | Pool lifecycle/catalogue materialized when transaction succeeded. | +| add_liquidity | No direct Launchpad entry confirmed. | Non-applicable unless local corpus proves direct Launchpad liquidity instruction. | +| remove_liquidity | No direct Launchpad liquidity remove entry confirmed. | Non-applicable unless local corpus proves direct Launchpad liquidity instruction. | +| position_open | No direct Launchpad position instruction confirmed. | Non-applicable. | +| position_close | No direct Launchpad position instruction confirmed. | Non-applicable. | +| fee | claim/collect fee entries listed. | Fee table materialization enabled for observed successful transactions. | +| reward | No direct reward instruction confirmed. | Non-applicable unless local corpus proves otherwise. | +| admin/config | create/update config and platform curve/config entries listed. | Pool admin materialization enabled for observed successful transactions. | +| mint | Token minting may appear as SPL Token/Token-2022 side effect. | Not `raydium_launchpad.*` without direct program proof. | +| burn | Token burn may appear as SPL Token/Token-2022 side effect. | Not `raydium_launchpad.*` without direct program proof. | +| transfer | Transfers are expected as SPL Token/Token-2022 side effects. | Not `raydium_launchpad.*` without direct program proof. | +| account_create / vesting | `create_vesting_account`, `create_platform_vesting_account`, vesting events. | Launch event materialization enabled for observed successful transactions; unobserved events remain mapped. | +| account_close | No direct Launchpad account close confirmed. | Non-applicable. | +| wrap_sol | No direct Launchpad wrap SOL confirmed. | Side effect only unless corpus proves direct instruction. | +| unwrap_sol | No direct Launchpad unwrap SOL confirmed. | Side effect only unless corpus proves direct instruction. | +| order_place | No orderbook surface confirmed. | Non-applicable. | +| order_cancel | No orderbook surface confirmed. | Non-applicable. | +| order_fill | No orderbook surface confirmed. | Non-applicable. | +| consume_events | No orderbook surface confirmed. | Non-applicable. | +| settle_funds | No orderbook surface confirmed. | Non-applicable. | +| vault_deposit | No direct vault deposit confirmed. | Non-applicable. | +| vault_withdraw | No direct vault withdraw confirmed. | Non-applicable. | +| lock | No direct lock confirmed. | Non-applicable. | +| unlock | No direct unlock confirmed. | Non-applicable. | +| launch | initialize/pool_create path listed. | Decode/audit only. | +| migration | `migrate_to_amm`, `migrate_to_cpswap`, `collect_migrate_fee` listed. | Decode/audit only. Destination DEX materialization must be proven locally. | +| stake | No direct stake confirmed. | Non-applicable. | +| unstake | No direct unstake confirmed. | Non-applicable. | +| unknown/unmapped audit | `raydium_launchpad.instruction_audit` retained for unmatched program instructions. | Must trend toward zero for locally covered discriminators after backfill/replay. | + +## SQL validation expectations + +After targeted backfill and replay: + +1. `k_sol_dex_event_coverage_entries` should contain the Launchpad program entry and discriminator entries. +2. Mapped entries should have `local_event_kind = raydium_launchpad.` and initial `proof_status = upstream_git_mapped_unverified` until observed. +3. Locally observed instructions should increment `k_sol_instruction_observations` for `decoder_code = raydium_launchpad`. +4. `upstream_git.instruction_match` fallback rows for `upstreamDecoderCode = raydium_launchpad` should be zero for locally covered instruction discriminators. +5. `raydium_launchpad.*` rows must not produce trades/candles unless a later corpus-backed patch explicitly promotes a specific event. +6. Failed transactions may be decoded/audited, but must not be materialized in trade/candle tables. + +Validation file: + +```text +validation_sql/SQL_VALIDATION_RAYDIUM_LAUNCHPAD_0_7_50.sql +``` + +## Suggested targeted Solscan discovery loop + +For each discriminator: + +```text +https://solscan.io/account/LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj?instruction=&hide_spam=true&hide_failed=true&show_related=false&sort=desc +``` + +Then: + +1. copy a small batch of recent non-failed signatures; +2. backfill through Demo2 textarea batch; +3. replay with `forceDexDecode=yes` and `deferInstructionObservations=yes`; +4. run the validation SQL; +5. promote only entries whose local payload and account semantics are proven. + +## `raydium_pool_v4.json` audit status + +The current workspace archive does not contain a local `raydium_pool_v4.json` copy. External fnzero IDL listings expose a separate `raydium_pool_v4.json` beside `raydium_launchpad.json`, but this delta does not confirm its program id or business role. + +Decision for `0.7.50`: + +- do not promote `raydium_pool_v4` as a DEX/surface; +- keep `0.7.53 raydium_pool_v4 audit / program-id decision` conditional; +- require program id confirmation and local corpus before any roadmap promotion. + +## Current limitations + +This delta was prepared from the provided archive only. No live RPC backfill, fresh SQLite replay, `cargo fmt`, `cargo test`, or `cargo clippy` could be executed in the current environment because the Rust toolchain is unavailable here. The SQL and code paths are prepared for local validation in the normal project environment. + +## Local corpus snapshot from first 0.7.50 backfill + +Observed after targeted Demo2 backfills and pool backfill on a fresh 0.7.50 DB: + +- coverage listed entries: `27`; +- decoded/local mapped entries: `26`; +- observed entries: `21`; +- materialized entries: `0`; +- total observed coverage count: `672`; +- total materialized count: `0`; +- trade count: `0`; +- residual `upstream_git.instruction_match` for `raydium_launchpad`: `0`; +- residual `raydium_launchpad.instruction_audit`: `287`; +- residual audit discriminators: `e445a52e51cb9a1d` (`276`), `9247ad4562130f6a` (`10`), `a25b92c75d85eaed` (`1`). + +The `e445a52e51cb9a1d` selector is handled as Anchor self-CPI event transport. It is not promoted as a Raydium Launchpad business instruction. The two low-count residual discriminators remain local-corpus audit-only until an IDL/upstream mapping is confirmed. + + +## pre3 correction — Demo3 preset and Launchpad pool catalog + +The first replay after pre2 confirmed that Anchor self-CPI selector `e445a52e51cb9a1d` carries Launchpad `trade_event` (`bddb7fd34ee661ee`) and `pool_create_event` (`97d7e20976a173ae`). pre3 therefore decodes those two self-CPI event rows as direct `raydium_launchpad.*` events instead of leaving them under `raydium_launchpad.instruction_audit`. `trade_event` remains audit/decoded-only and is still not promoted to `k_sol_trade_events` or candles. + +pre3 also fixes the Launchpad `initialize`, `initialize_v2` and `initialize_with_token_2022` account mapping using the Carbon account shape: `pool_state` index 5, `base_mint` index 6 and `quote_mint` index 7. These initialize rows are now routed to business-level pool detection as `raydium_launchpad` bonding-curve pools with pending status, which should allow pool `6HLQPoLrzX6LqePRiXQ1GGs2Dd9K3dp9VhTSHBugYzzZ` to appear in the local catalog after a forced replay when its initialize transaction is present locally. + +Demo3 now exposes a `Raydium Launchpad` preset with program id `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj`. + + +## Final `0.7.50` closure snapshot + +Validated closure state reported by local replay: + +- `cargo test -p kb_lib`: 404 passed, 0 failed. +- Local replay: 437 replayed, 437 ledger upserts, 30 unsafe ledger rows, 256 trades, 115 lifecycle rows, 1024 candle upserts, 6205 instruction observations. +- Launchpad catalogue: 58 tokens, 58 pools, 58 pairs after replay. +- Coverage normalization: no ambiguous `unknown` family remains; only the synthetic `program` row may have an empty family. +- `trade_event`: 260 decoded, 250 successful materialized trades; 10 failed transactions intentionally not materialized. +- `buy_exact_*` / `sell_exact_*`: materialized as `k_sol_launch_events` swap-instruction breadcrumbs, not as trades. +- `cpi_event`: kept as `cpi_transport` / decoded-only; embedded events are decoded by direct event discriminator. +- Successful `trade_event` rows without materialized `k_sol_trade_events`: zero. + +Post-closure recheck assets added: + +- `validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_0_7_50_PRE_R2.sql` +- `validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_50_PRE_R2.sql` +- `docs/SOLSCAN_ACCOUNT_SOURCE_MATRIX.md` +- `kb_lib::SOLSCAN_ACCOUNT_SOURCES` diff --git a/docs/SOLSCAN_ACCOUNT_SOURCE_MATRIX.md b/docs/SOLSCAN_ACCOUNT_SOURCE_MATRIX.md new file mode 100644 index 0000000..5b880fb --- /dev/null +++ b/docs/SOLSCAN_ACCOUNT_SOURCE_MATRIX.md @@ -0,0 +1,160 @@ +# Solscan account source matrix + +This file records the manual Solscan account inventory added during the `0.7.50` Raydium Launchpad closure. It is a source catalogue, not a support guarantee. Entries with `solscan_program_idl` can be used as IDL candidates; entries with `no_idl` require source/corpus work before decoder promotion. + +| Label | Account id | IDL status | Source | +|---|---|---|---| +| `1Dex Program` | `DEXYosS6oEGvk8uCDayvwEZz4qEyDJRf9nFgYCaqPMTm` | `no_idl` | https://solscan.io/account/DEXYosS6oEGvk8uCDayvwEZz4qEyDJRf9nFgYCaqPMTm | +| `AlphaQ` | `ALPHAQmeA7bjrVuccPsYPiCvsi428SNwte66Srvs4pHA` | `no_idl` | https://solscan.io/account/ALPHAQmeA7bjrVuccPsYPiCvsi428SNwte66Srvs4pHA | +| `Aldrin AMM` | `AMM55ShdkoGRB5jVYPjWziwk8m5MpwyDgsMWHaMSQWH6` | `no_idl` | https://solscan.io/account/AMM55ShdkoGRB5jVYPjWziwk8m5MpwyDgsMWHaMSQWH6 | +| `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 | +| `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 | +| `Bags: Token Authority` | `BAGSB9TpGrZxQbEsrEznv5jXXdwyP6AXerN8aVRiAmcv` | `solscan_account` | https://solscan.io/account/BAGSB9TpGrZxQbEsrEznv5jXXdwyP6AXerN8aVRiAmcv | +| `Believe : Token Authority` | `5qWya6UjwWnGVhdSBL3hyZ7B45jbk6Byt1hwd7ohEGXE` | `no_idl` | https://solscan.io/account/5qWya6UjwWnGVhdSBL3hyZ7B45jbk6Byt1hwd7ohEGXE | +| `BisonFi` | `BiSoNHVpsVZW2F7rx2eQ59yQwKxzU5NvBcmKshCSUypi` | `no_idl` | https://solscan.io/account/BiSoNHVpsVZW2F7rx2eQ59yQwKxzU5NvBcmKshCSUypi | +| `BonkSwap` | `BSwp6bEBihVLdqJRKGgzjcGLHkcTuzmSo1TQkHepzH8p` | `solscan_program_idl` | https://solscan.io/account/BSwp6bEBihVLdqJRKGgzjcGLHkcTuzmSo1TQkHepzH8p#programIdl | +| `Boop.fun` | `boop8hVGQGqehUK2iVEMEnMrL5RbjywRzHKBmBE7ry4` | `solscan_program_idl` | https://solscan.io/account/boop8hVGQGqehUK2iVEMEnMrL5RbjywRzHKBmBE7ry4#programIdl | +| `Byreal: CLMM` | `REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2` | `solscan_program_idl` | https://solscan.io/account/REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2#programIdl | +| `Bubblegum` | `BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY` | `solscan_program_idl` | https://solscan.io/account/BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY#programIdl | +| `Carrot DeFi` | `CarrotwivhMpDnm27EHmRLeQ683Z1PufuqEmBZvD282s` | `solscan_program_idl` | https://solscan.io/account/CarrotwivhMpDnm27EHmRLeQ683Z1PufuqEmBZvD282s#programIdl | +| `CCTP TokenMessengerMinter` | `CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3` | `solscan_program_idl` | https://solscan.io/account/CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3#programIdl | +| `CCTP TokenMessengerMinterV2` | `CCTPV2vPZJS2u2BBsUoscuikbYjnpFmbFsvVuJdgUMQe` | `solscan_program_idl` | https://solscan.io/account/CCTPV2vPZJS2u2BBsUoscuikbYjnpFmbFsvVuJdgUMQe#programIdl | +| `Clone` | `C1onEW2kPetmHmwe74YC1ESx3LnFEpVau6g2pg4fHycr` | `solscan_program_idl` | https://solscan.io/account/C1onEW2kPetmHmwe74YC1ESx3LnFEpVau6g2pg4fHycr#programIdl | +| `Crema Finance Program` | `CLMM9tUoggJu2wagPkkqs9eFG4BWhVBZWkP1qv3Sp7tR` | `no_idl` | https://solscan.io/account/CLMM9tUoggJu2wagPkkqs9eFG4BWhVBZWkP1qv3Sp7tR | +| `Cropper Finance` | `CTMAxxk34HjKWxQ3QLZK1HpaLXmBveao3ESePXbiyfzh` | `no_idl` | https://solscan.io/account/CTMAxxk34HjKWxQ3QLZK1HpaLXmBveao3ESePXbiyfzh | +| `Cropper Whirlpool` | `H8W3ctz92svYg6mkn1UtGfu2aQr2fnUFHM1RhScEtQDt` | `no_idl` | https://solscan.io/account/H8W3ctz92svYg6mkn1UtGfu2aQr2fnUFHM1RhScEtQDt | +| `deBridge Destination` | `dst5MGcFPoBeREFAA5E3tU5ij8m5uVYwkzkSAbsLbNo` | `no_idl` | https://solscan.io/account/dst5MGcFPoBeREFAA5E3tU5ij8m5uVYwkzkSAbsLbNo | +| `deBridge Source` | `src5qyZHqTqecJV4aY6Cb6zDZLMDzrDKKezs22MPHr4` | `no_idl` | https://solscan.io/account/src5qyZHqTqecJV4aY6Cb6zDZLMDzrDKKezs22MPHr4 | +| `Dexlab Swap` | `DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N` | `no_idl` | https://solscan.io/account/DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N | +| `DFlow Aggregator v4` | `DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH` | `solscan_program_idl` | https://solscan.io/account/DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH#programIdl | +| `Drift V2 Program` | `dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH` | `solscan_program_idl` | https://solscan.io/account/dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH#programIdl | +| `Fluxbeam Program` | `FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X` | `no_idl` | https://solscan.io/account/FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X | +| `Fusion AMM` | `fUSioN9YKKSa3CUC2YUc4tPkHJ5Y6XW1yz8y6F7qWz9` | `solscan_program_idl` | https://solscan.io/account/fUSioN9YKKSa3CUC2YUc4tPkHJ5Y6XW1yz8y6F7qWz9#programIdl | +| `Futarchy AMM` | `FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq` | `solscan_program_idl` | https://solscan.io/account/FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq#programIdl | +| `Gavel` | `srAMMzfVHVAtgSJc8iH6CfKzuWuUTzLHVCE81QU1rgi` | `no_idl` | https://solscan.io/account/srAMMzfVHVAtgSJc8iH6CfKzuWuUTzLHVCE81QU1rgi | +| `GoonFi` | `goonERTdGsjnkZqWuVjs73BZ3Pb9qoCUdBUL17BnS5j` | `no_idl` | https://solscan.io/account/goonERTdGsjnkZqWuVjs73BZ3Pb9qoCUdBUL17BnS5j | +| `GoonFi V2` | `goonuddtQRrWqqn5nFyczVKaie28f3kDkHWkHtURSLE` | `no_idl` | https://solscan.io/account/goonuddtQRrWqqn5nFyczVKaie28f3kDkHWkHtURSLE | +| `GooseFX: GAMMA` | `GAMMA7meSFWaBXF25oSUgmGRwaW6sCMFLmBNiMSdbHVT` | `solscan_program_idl` | https://solscan.io/account/GAMMA7meSFWaBXF25oSUgmGRwaW6sCMFLmBNiMSdbHVT#programIdl | +| `GooseFX V2` | `GFXsSL5sSaDfNFQUYsHekbWBW1TsFdjDYzACh62tEHxn` | `solscan_program_idl` | https://solscan.io/account/GFXsSL5sSaDfNFQUYsHekbWBW1TsFdjDYzACh62tEHxn#programIdl | +| `Guac Swap` | `Gswppe6ERWKpUTXvRPfXdzHhiCyJvLadVvXGfdpBqcE1` | `solscan_program_idl` | https://solscan.io/account/Gswppe6ERWKpUTXvRPfXdzHhiCyJvLadVvXGfdpBqcE1#programIdl | +| `HawkFi Program` | `FqGg2Y1FNxMiGd51Q6UETixQWkF5fB92MysbYogRJb3P` | `no_idl` | https://solscan.io/account/FqGg2Y1FNxMiGd51Q6UETixQWkF5fB92MysbYogRJb3P | +| `Heaven DEX` | `HEAVENoP2qxoeuF8Dj2oT1GHEnu49U5mJYkdeC8BAX2o` | `no_idl` | https://solscan.io/account/HEAVENoP2qxoeuF8Dj2oT1GHEnu49U5mJYkdeC8BAX2o | +| `Helium Treasury Management` | `treaf4wWBBty3fHdyBpo35Mz84M8k3heKXmjmi9vFt5` | `solscan_program_idl` | https://solscan.io/account/treaf4wWBBty3fHdyBpo35Mz84M8k3heKXmjmi9vFt5#programIdl | +| `HumidiFi` | `9H6tua7jkLhdm3w8BvgpTn5LZNU7g4ZynDmCiNN3q6Rp` | `no_idl` | https://solscan.io/account/9H6tua7jkLhdm3w8BvgpTn5LZNU7g4ZynDmCiNN3q6Rp | +| `Hylo Exchange` | `HYEXCHtHkBagdStcJCp3xbbb9B7sdMdWXFNj6mdsG4hn` | `solscan_program_idl` | https://solscan.io/account/HYEXCHtHkBagdStcJCp3xbbb9B7sdMdWXFNj6mdsG4hn#programIdl | +| `Hylo Stability Pool` | `HysTabVUfmQBFcmzu1ctRd1Y1fxd66RBpboy1bmtDSQQ` | `solscan_program_idl` | https://solscan.io/account/HysTabVUfmQBFcmzu1ctRd1Y1fxd66RBpboy1bmtDSQQ#programIdl | +| `Invariant Swap` | `HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt` | `no_idl` | https://solscan.io/account/HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt | +| `Jito Tip Distribution` | `4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7` | `solscan_program_idl` | https://solscan.io/account/4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7#programIdl | +| `Jupiter Aggregator v6` | `JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4` | `solscan_program_idl` | https://solscan.io/account/JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4#programIdl | +| `Jupiter Aggregator v4` | `JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB` | `solscan_program_idl` | https://solscan.io/account/JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB#programIdl | +| `Jupiter DCA program` | `DCA265Vj8a9CEuX1eb1LWRnDT7uK6q1xMipnNyatn23M` | `solscan_program_idl` | https://solscan.io/account/DCA265Vj8a9CEuX1eb1LWRnDT7uK6q1xMipnNyatn23M#programIdl | +| `Jupiter Lend Borrow` | `jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi` | `no_idl` | https://solscan.io/account/jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi | +| `Jupiter Lend Earn` | `jup3YeL8QhtSx1e253b2FDvsMNC87fDrgQZivbrndc9` | `solscan_program_idl` | https://solscan.io/account/jup3YeL8QhtSx1e253b2FDvsMNC87fDrgQZivbrndc9#programIdl | +| `Jupiter Lend Flash Loan` | `jupgfSgfuAXv4B6R2Uxu85Z1qdzgju79s6MfZekN6XS` | `no_idl` | https://solscan.io/account/jupgfSgfuAXv4B6R2Uxu85Z1qdzgju79s6MfZekN6XS | +| `Jupiter Lend Liquidity` | `jupeiUmn818Jg1ekPURTpr4mFo29p46vygyykFJ3wZC` | `no_idl` | https://solscan.io/account/jupeiUmn818Jg1ekPURTpr4mFo29p46vygyykFJ3wZC | +| `Jupiter Limit Order` | `jupoNjAxXgZ4rjzxzPMP4oxduvQsQtZzyknqvzYNrNu` | `solscan_program_idl` | https://solscan.io/account/jupoNjAxXgZ4rjzxzPMP4oxduvQsQtZzyknqvzYNrNu#programIdl | +| `Jupiter Limit Order V2` | `j1o2qRpjcyUwEvwtcfhEQefh773ZgjxcVRry7LDqg5X` | `solscan_program_idl` | https://solscan.io/account/j1o2qRpjcyUwEvwtcfhEQefh773ZgjxcVRry7LDqg5X#programIdl | +| `Jupiter Lock` | `LocpQgucEQHbqNABEYvBvwoxCPsSbG91A1QaQhQQqjn` | `solscan_program_idl` | https://solscan.io/account/LocpQgucEQHbqNABEYvBvwoxCPsSbG91A1QaQhQQqjn#programIdl | +| `Jupiter Perpetuals` | `PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu` | `solscan_program_idl` | https://solscan.io/account/PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu#programIdl | +| `Jupiter Prediction Market` | `3ZZuTbwC6aJbvteyVxXUS7gtFYdf7AuXeitx6VyvjvUp` | `no_idl` | https://solscan.io/account/3ZZuTbwC6aJbvteyVxXUS7gtFYdf7AuXeitx6VyvjvUp | +| `Jupiter Stable Program` | `JUPUSDecMzAVgztLe6eGhwUBj1Pn3j9WAXwmtHmfbRr` | `solscan_program_idl` | https://solscan.io/account/JUPUSDecMzAVgztLe6eGhwUBj1Pn3j9WAXwmtHmfbRr#programIdl | +| `Jup Studio: Authority` | `8rE9CtCjwhSmbwL5fbJBtRFsS3ohfMcDFeTTC7t4ciUA` | `no_idl` | https://solscan.io/account/8rE9CtCjwhSmbwL5fbJBtRFsS3ohfMcDFeTTC7t4ciUA | +| `Kamino Program` | `6LtLpnUFNByNXLyCoK9wA2MykKAmQNZKBdY8s47dehDc` | `solscan_program_idl` | https://solscan.io/account/6LtLpnUFNByNXLyCoK9wA2MykKAmQNZKBdY8s47dehDc#programIdl | +| `Kamino Lending Program` | `KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD` | `solscan_program_idl` | https://solscan.io/account/KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD#programIdl | +| `Kamino Farm` | `FarmsPZpWu9i7Kky8tPN37rs2TpmMrAZrC7S7vJa91Hr` | `solscan_program_idl` | https://solscan.io/account/FarmsPZpWu9i7Kky8tPN37rs2TpmMrAZrC7S7vJa91Hr#programIdl | +| `Kamino Vault Program` | `kvauTFR8qm1dhniz6pYuBZkuene3Hfrs1VQhVRgCNrr` | `solscan_program_idl` | https://solscan.io/account/kvauTFR8qm1dhniz6pYuBZkuene3Hfrs1VQhVRgCNrr#programIdl | +| `Kvault Program` | `KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd` | `solscan_program_idl` | https://solscan.io/account/KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd#programIdl | +| `LayerZero Endpoint` | `76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6` | `solscan_program_idl` | https://solscan.io/account/76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6#programIdl | +| `LayerZero Executor` | `6doghB248px58JSSwG4qejQ46kFMW4AMj7vzJnWZHNZn` | `solscan_program_idl` | https://solscan.io/account/6doghB248px58JSSwG4qejQ46kFMW4AMj7vzJnWZHNZn#programIdl | +| `letsbonk.fun: PlatformConfig` | `FfYek5vEz23cMkWsdJwG2oa6EphsvXSHrGpdALN4g6W1` | `no_idl` | https://solscan.io/account/FfYek5vEz23cMkWsdJwG2oa6EphsvXSHrGpdALN4g6W1 | +| `Lifinity Swap` | `EewxydAPCCVuNEyrVN68PuSYdQ7wKn27V9Gjeoi8dy3S` | `no_idl` | https://solscan.io/account/EewxydAPCCVuNEyrVN68PuSYdQ7wKn27V9Gjeoi8dy3S | +| `Lifinity Swap V2` | `2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c` | `solscan_program_idl` | https://solscan.io/account/2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c#programIdl | +| `Manifest` | `MNFSTqtC93rEfYHB6hF82sKdZpUDFWkViLByLd1k1Ms` | `no_idl` | https://solscan.io/account/MNFSTqtC93rEfYHB6hF82sKdZpUDFWkViLByLd1k1Ms | +| `Marcopolo Swap` | `9tKE7Mbmj4mxDjWatikzGAtkoWosiiZX9y6J4Hfm2R8H` | `no_idl` | https://solscan.io/account/9tKE7Mbmj4mxDjWatikzGAtkoWosiiZX9y6J4Hfm2R8H | +| `Marginfi` | `MFLQPPPPjNinkdKoy2odNFBhvpY43XtCDZjBwG2fwn5` | `no_idl` | https://solscan.io/account/MFLQPPPPjNinkdKoy2odNFBhvpY43XtCDZjBwG2fwn5 | +| `Marginfi V2` | `MFv2hWf31Z9kbCa1snEPYctwafyhdvnV7FZnsebVacA` | `solscan_program_idl` | https://solscan.io/account/MFv2hWf31Z9kbCa1snEPYctwafyhdvnV7FZnsebVacA#programIdl | +| `Marinade Finance` | `MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD` | `solscan_program_idl` | https://solscan.io/account/MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD#programIdl | +| `Mercurial Stable Swap` | `MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky` | `no_idl` | https://solscan.io/account/MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky | +| `MetaDAO AMM Program v0.5` | `AMMJdEiCCa8mdugg6JPF7gFirmmxisTfDJoSNSUi5zDJ` | `solscan_program_idl` | https://solscan.io/account/AMMJdEiCCa8mdugg6JPF7gFirmmxisTfDJoSNSUi5zDJ#programIdl | +| `MetaDAO Bid Wall Program` | `WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx` | `solscan_program_idl` | https://solscan.io/account/WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx#programIdl | +| `MetaDAO ICO` | `moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM` | `solscan_program_idl` | https://solscan.io/account/moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM#programIdl | +| `Metaplex Token Metadata` | `metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s` | `no_idl` | https://solscan.io/account/metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s | +| `Meteora DLMM Program` | `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo` | `solscan_program_idl` | https://solscan.io/account/LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo#programIdl | +| `Meteora Pools Program` | `Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB` | `solscan_program_idl` | https://solscan.io/account/Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB#programIdl | +| `Meteora Dynamic Bonding Curve` | `dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN` | `solscan_program_idl` | https://solscan.io/account/dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN#programIdl | +| `Meteora DAMM v2` | `cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG` | `solscan_program_idl` | https://solscan.io/account/cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG#programIdl | +| `Meteora Vault Program` | `24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi` | `solscan_program_idl` | https://solscan.io/account/24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi#programIdl | +| `Moonit` | `MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG` | `solscan_program_idl` | https://solscan.io/account/MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG#programIdl | +| `Moonshot : Token Authority` | `7rtiKSUDLBm59b1SBmD9oajcP8xE64vAGSMbAN5CXy1q` | `no_idl` | https://solscan.io/account/7rtiKSUDLBm59b1SBmD9oajcP8xE64vAGSMbAN5CXy1q | +| `Mpl Core` | `CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d` | `no_idl` | https://solscan.io/account/CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d | +| `Name Service Program` | `namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX` | `no_idl` | https://solscan.io/account/namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX | +| `Numeraire` | `NUMERUNsFCP3kuNmWZuXtm1AaQCPj9uw6Guv2Ekoi5P` | `solscan_program_idl` | https://solscan.io/account/NUMERUNsFCP3kuNmWZuXtm1AaQCPj9uw6Guv2Ekoi5P#programIdl | +| `Obric V2` | `obriQD1zbpyLz95G5n7nJe6a4DPjpFwa5XYPoNm113y` | `no_idl` | https://solscan.io/account/obriQD1zbpyLz95G5n7nJe6a4DPjpFwa5XYPoNm113y | +| `OKX Labs 1` | `6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma` | `solscan_program_idl` | https://solscan.io/account/6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma#programIdl | +| `OKX: DEX Router` | `proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u` | `solscan_program_idl` | https://solscan.io/account/proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u#programIdl | +| `Ondo Global Markets` | `XzTT4XB8m7sLD2xi6snefSasaswsKCxx5Tifjondogm` | `solscan_program_idl` | https://solscan.io/account/XzTT4XB8m7sLD2xi6snefSasaswsKCxx5Tifjondogm#programIdl | +| `Openbook V2` | `opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb` | `solscan_program_idl` | https://solscan.io/account/opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb#programIdl | +| `Orca Whirlpools Program` | `whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc` | `solscan_program_idl` | https://solscan.io/account/whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc#programIdl | +| `Orca Wavebreak` | `waveQX2yP3H1pVU8djGvEHmYg8uamQ84AuyGtpsrXTF` | `solscan_program_idl` | https://solscan.io/account/waveQX2yP3H1pVU8djGvEHmYg8uamQ84AuyGtpsrXTF#programIdl | +| `Orca Token Swap` | `DjVE6JNiYqPL2QXyCUUh8rNjHrbz9hXHNYt99MQ59qw1` | `no_idl` | https://solscan.io/account/DjVE6JNiYqPL2QXyCUUh8rNjHrbz9hXHNYt99MQ59qw1 | +| `Orca Token Swap V2` | `9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP` | `no_idl` | https://solscan.io/account/9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP | +| `Ore V3 Program` | `oreV3EG1i9BEgiAJ8b177Z2S2rMarzak4NMv1kULvWv` | `no_idl` | https://solscan.io/account/oreV3EG1i9BEgiAJ8b177Z2S2rMarzak4NMv1kULvWv | +| `PancakeSwap` | `HpNfyc2Saw7RKkQd8nEL4khUcuPhQ7WwY1B2qjx8jxFq` | `solscan_program_idl` | https://solscan.io/account/HpNfyc2Saw7RKkQd8nEL4khUcuPhQ7WwY1B2qjx8jxFq#programIdl | +| `Penguin Finance` | `PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP` | `no_idl` | https://solscan.io/account/PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP | +| `Phoenix` | `PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY` | `no_idl` | https://solscan.io/account/PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY | +| `Printr` | `T8HsGYv7sMk3kTnyaRqZrbRPuntYzdh12evXBkprint` | `solscan_program_idl` | https://solscan.io/account/T8HsGYv7sMk3kTnyaRqZrbRPuntYzdh12evXBkprint#programIdl | +| `Pump.fun` | `6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P` | `solscan_program_idl` | https://solscan.io/account/6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P#programIdl | +| `Pump.fun AMM` | `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA` | `solscan_program_idl` | https://solscan.io/account/pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA#programIdl | +| `Pump Fees Program` | `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` | `solscan_program_idl` | https://solscan.io/account/pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ#programIdl | +| `pumpup.ai` | `PdMDrKEMaX8q7CCJb7NvUCxerBCcsFUa4LjBEynTtEd` | `solscan_program_idl` | https://solscan.io/account/PdMDrKEMaX8q7CCJb7NvUCxerBCcsFUa4LjBEynTtEd#programIdl | +| `Raydium AMM Routing` | `routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS` | `no_idl` | https://solscan.io/account/routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS | +| `Raydium Concentrated Liquidity` | `CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK` | `solscan_program_idl` | https://solscan.io/account/CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK#programIdl | +| `Raydium CPMM` | `CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C` | `solscan_program_idl` | https://solscan.io/account/CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C#programIdl | +| `Raydium LaunchLab` | `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj` | `solscan_program_idl` | https://solscan.io/account/LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj#programIdl | +| `Raydium LaunchLab: PlatformConfig` | `4Bu96XjU84XjPDSpveTVf6LYGCkfW5FK7SNkREWcEfV4` | `no_idl` | https://solscan.io/account/4Bu96XjU84XjPDSpveTVf6LYGCkfW5FK7SNkREWcEfV4 | +| `Raydium liquidity pool AMM` | `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h` | `no_idl` | https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h | +| `Raydium Liquidity Pool V2` | `RVKd61ztZW9GUwhRbbLoYVRE5Xf1B2tVscKqwZqXgEr` | `no_idl` | https://solscan.io/account/RVKd61ztZW9GUwhRbbLoYVRE5Xf1B2tVscKqwZqXgEr | +| `Raydium Liquidity Pool V3` | `27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv` | `no_idl` | https://solscan.io/account/27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv | +| `Raydium Liquidity Pool V4` | `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8` | `no_idl` | https://solscan.io/account/675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 | +| `Raydium Lock LP` | `LockrWmn6K5twhz3y9w1dQERbmgSaRkfnTeTKbpofwE` | `solscan_program_idl` | https://solscan.io/account/LockrWmn6K5twhz3y9w1dQERbmgSaRkfnTeTKbpofwE#programIdl | +| `Saber Decimal Wrapper` | `DecZY86MU5Gj7kppfUCEmd4LbXXuyZH1yHaP2NTqdiZB` | `solscan_program_idl` | https://solscan.io/account/DecZY86MU5Gj7kppfUCEmd4LbXXuyZH1yHaP2NTqdiZB#programIdl | +| `Saber Stable Swap` | `SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ` | `no_idl` | https://solscan.io/account/SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ | +| `Saros AMM` | `SSwapUtytfBdBn1b9NUGG6foMVPtcWgpRU32HToDUZr` | `no_idl` | https://solscan.io/account/SSwapUtytfBdBn1b9NUGG6foMVPtcWgpRU32HToDUZr | +| `Sanctum Multi-Validator SPL Stake Pool Program` | `SPMBzsVUuoHA4Jm6KunbsotaahvVikZs1JyTW6iJvbn` | `no_idl` | https://solscan.io/account/SPMBzsVUuoHA4Jm6KunbsotaahvVikZs1JyTW6iJvbn | +| `Sanctum Router Program` | `stkitrT1Uoy18Dk1fTrgPw8W6MVzoCfYoAFT4MLsmhq` | `no_idl` | https://solscan.io/account/stkitrT1Uoy18Dk1fTrgPw8W6MVzoCfYoAFT4MLsmhq | +| `Sanctum: S Controller` | `5ocnV1qiCgaQR8Jb8xWnVbApfaygJ8tNoZfgPwsgx9kx` | `no_idl` | https://solscan.io/account/5ocnV1qiCgaQR8Jb8xWnVbApfaygJ8tNoZfgPwsgx9kx | +| `Sanctum Single Validator SPL Stake Pool Program` | `SP12tWFxD9oJsVWNavTTBZvMbA6gkAmxtVgxdqvyvhY` | `solscan_account` | https://solscan.io/account/SP12tWFxD9oJsVWNavTTBZvMbA6gkAmxtVgxdqvyvhY | +| `SharkyFi` | `SHARKobtfF1bHhxD2eqftjHBdVSCbKo9JtgK71FhELP` | `no_idl` | https://solscan.io/account/SHARKobtfF1bHhxD2eqftjHBdVSCbKo9JtgK71FhELP | +| `Solayer` | `sSo1iU21jBrU9VaJ8PJib1MtorefUV4fzC9GURa2KNn` | `no_idl` | https://solscan.io/account/sSo1iU21jBrU9VaJ8PJib1MtorefUV4fzC9GURa2KNn | +| `Solend Protocol` | `So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo` | `no_idl` | https://solscan.io/account/So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo | +| `SolFi` | `SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe` | `no_idl` | https://solscan.io/account/SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe | +| `SolFi V2` | `SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF` | `no_idl` | https://solscan.io/account/SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF | +| `stabble CLMM` | `6dMXqGZ3ga2dikrYS9ovDXgHGh5RUsb2RTUj6hrQXhk6` | `solscan_program_idl` | https://solscan.io/account/6dMXqGZ3ga2dikrYS9ovDXgHGh5RUsb2RTUj6hrQXhk6#programIdl | +| `stabble Stable Swap` | `swapNyd8XiQwJ6ianp9snpu4brUqFxadzvHebnAXjJZ` | `solscan_program_idl` | https://solscan.io/account/swapNyd8XiQwJ6ianp9snpu4brUqFxadzvHebnAXjJZ#programIdl | +| `stabble Weighted Swap` | `swapFpHZwjELNnjvThjajtiVmkz3yPQEHjLtka2fwHW` | `solscan_program_idl` | https://solscan.io/account/swapFpHZwjELNnjvThjajtiVmkz3yPQEHjLtka2fwHW#programIdl | +| `Stake Pool` | `SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy` | `no_idl` | https://solscan.io/account/SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy | +| `Stake Program` | `Stake11111111111111111111111111111111111111` | `no_idl` | https://solscan.io/account/Stake11111111111111111111111111111111111111 | +| `Step Finance Swap Program` | `SSwpMgqNDsyV7mAgN9ady4bDVu5ySjmmXejXvy2vLt1` | `no_idl` | https://solscan.io/account/SSwpMgqNDsyV7mAgN9ady4bDVu5ySjmmXejXvy2vLt1 | +| `StepN DOOAR Swap` | `Dooar9JkhdZ7J3LHN3A7YCuoGRUggXhQaG4kijfLGU2j` | `no_idl` | https://solscan.io/account/Dooar9JkhdZ7J3LHN3A7YCuoGRUggXhQaG4kijfLGU2j | +| `Scorch` | `SCoRcH8c2dpjvcJD6FiPbCSQyQgu3PcUAWj2Xxx3mqn` | `no_idl` | https://solscan.io/account/SCoRcH8c2dpjvcJD6FiPbCSQyQgu3PcUAWj2Xxx3mqn | +| `Streamflow` | `strmRqUCoQUgGUan5YhzUZa6KqdzwX5L6FpUxfmKg5m` | `solscan_program_idl` | https://solscan.io/account/strmRqUCoQUgGUan5YhzUZa6KqdzwX5L6FpUxfmKg5m#programIdl | +| `Swap Program` | `SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8` | `no_idl` | https://solscan.io/account/SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8 | +| `Swig Program` | `swigypWHEksbC64pWKwah1WTeh9JXwx8H1rJHLdbQMB` | `no_idl` | https://solscan.io/account/swigypWHEksbC64pWKwah1WTeh9JXwx8H1rJHLdbQMB | +| `System Program` | `11111111111111111111111111111111` | `no_idl` | https://solscan.io/account/11111111111111111111111111111111 | +| `Tessera V` | `TessVdML9pBGgG9yGks7o4HewRaXVAMuoVj4x83GLQH` | `no_idl` | https://solscan.io/account/TessVdML9pBGgG9yGks7o4HewRaXVAMuoVj4x83GLQH | +| `Titan Exchange Router` | `T1TANpTeScyeqVzzgNViGDNrkQ6qHz9KrSBS4aNXvGT` | `no_idl` | https://solscan.io/account/T1TANpTeScyeqVzzgNViGDNrkQ6qHz9KrSBS4aNXvGT | +| `Token 2022 Program` | `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb` | `no_idl` | https://solscan.io/account/TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb | +| `Token Program` | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` | `no_idl` | https://solscan.io/account/TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA | +| `Vertigo Program` | `vrTGoBuy5rYSxAfV3jaRJWHH6nN9WK4NRExGxsk1bCJ` | `solscan_program_idl` | https://solscan.io/account/vrTGoBuy5rYSxAfV3jaRJWHH6nN9WK4NRExGxsk1bCJ#programIdl | +| `Virtuals` | `5U3EU2ubXtK84QcRjWVmYt9RaDyA8gKxdUrPFXmZyaki` | `solscan_program_idl` | https://solscan.io/account/5U3EU2ubXtK84QcRjWVmYt9RaDyA8gKxdUrPFXmZyaki#programIdl | +| `Woofi` | `WooFif76YGRNjk1pA8wCsN67aQsD9f9iLsz4NcJ1AVb` | `solscan_program_idl` | https://solscan.io/account/WooFif76YGRNjk1pA8wCsN67aQsD9f9iLsz4NcJ1AVb#programIdl | +| `Wormhole: Bridge` | `wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb` | `no_idl` | https://solscan.io/account/wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb | +| `ZeroFi` | `ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY` | `no_idl` | https://solscan.io/account/ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY | +| `Zeta Matching Engine` | `zDEXqXEG7gAyxb1Kg9mK5fPnUdENCGKzWrM21RMdWRq` | `no_idl` | https://solscan.io/account/zDEXqXEG7gAyxb1Kg9mK5fPnUdENCGKzWrM21RMdWRq | +| `Zeta Program` | `ZETAxsqBRek56DhiGXrn75yj2NHU3aYUnxvHXpkf3aD` | `solscan_program_idl` | https://solscan.io/account/ZETAxsqBRek56DhiGXrn75yj2NHU3aYUnxvHXpkf3aD#programIdl | +| `Zora Program` | `zoRabwLGd5zXaV7Gxacppw8tcceXEiTrSKyNLSaSTUc` | `no_idl` | https://solscan.io/account/zoRabwLGd5zXaV7Gxacppw8tcceXEiTrSKyNLSaSTUc | diff --git a/docs/prompts/PROMPT_REPRISE_khadhroony-bobobot_0.7.51-raydium-amm-v4.md b/docs/prompts/PROMPT_REPRISE_khadhroony-bobobot_0.7.51-raydium-amm-v4.md new file mode 100644 index 0000000..ab999bb --- /dev/null +++ b/docs/prompts/PROMPT_REPRISE_khadhroony-bobobot_0.7.51-raydium-amm-v4.md @@ -0,0 +1,193 @@ + + +# Prompt de reprise — khadhroony-bobobot `0.7.51` / Raydium AMM v4 event coverage + +Reprise du projet `khadhroony-bobobot` après clôture de `0.7.50 raydium_launchpad` et re-vérification finale CPMM/CLMM. + +## Archive de départ + +Utiliser la dernière archive complète du workspace intégrant les deltas validés jusqu'à : + +0.7.50-raydium-launchpad-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_LAUNCHPAD_EVENT_COVERAGE_REPORT.md +validation_sql/SQL_VALIDATION_RAYDIUM_LAUNCHPAD_0_7_50.sql +validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_AUDIT_CLEANUP_0_7_50_FINAL.sql +validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_0_7_50_RECHECK.sql +validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_50_RECHECK.sql +``` + +## État validé avant reprise + +`0.7.50` a clôturé `raydium_launchpad` et consolidé les rechecks CPMM/CLMM. + +Dernier replay local rapporté avant clôture : + +```text +1103 replayed +0 decode skipped +1124 ledger upserts +542 unsafe ledger rows +561 trades +50 liquidity +13 lifecycle +0 tokenAccount +2224 candle upserts +instructionObservations = 7013 +resetDeleted = 1182 +``` + +Points de clôture à préserver : + +```text +raydium_launchpad : surface canonique, program id LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj +Launchpad trade_event matérialisé seulement quand corpus + successful tx le prouvent +Launchpad initialize* fournit le catalogue pool/pair, pas de faux trade/candle +CPMM 40f4bc78a7e9690a est raydium_cpmm.anchor_idl_instruction decoded-only +CPMM residual raydium_cpmm.instruction_audit 40f4bc78a7e9690a doit être nettoyé après replay final +CLMM residual instruction_audit / upstream fallback doivent rester vides +k_sol_instruction_observations reste une table technique, pas une table métier +Solscan instruction= est une aide de découverte, pas une preuve métier +``` + +Requête CPMM post-fix obligatoire : + +```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_cpmm' + AND event_kind = 'raydium_cpmm.instruction_audit' +GROUP BY discriminator_hex +ORDER BY audit_count DESC, discriminator_hex; +``` + +Cette requête doit être vide après replay `forceDexDecode=yes`. + +## Objectif `0.7.51` — `raydium_amm_v4` + +Reprendre Raydium AMM v4 legacy au même niveau de couverture que CPMM/CLMM : + +```text +swaps +pool lifecycle / pool_create +add_liquidity / remove_liquidity +fees / admin/config +side effects SPL Token / Token-2022 documentés mais non promus comme raydium_amm_v4.* directs +fallback instruction_audit nettoyé quand une entrée locale spécialisée couvre l'instruction +coverage entries synchronisées et rafraîchies +``` + +Code local canonique : + +```text +raydium_amm_v4 +``` + +Program id canonique connu : + +```text +675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 +``` + +Solscan Program IDL / recherche par instruction : + +```text +https://solscan.io/account/675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8#programIdl +https://solscan.io/account/675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8?instruction=&hide_spam=true&hide_failed=true&show_related=false&sort=desc +``` + +## Sources Git/IDL à utiliser systématiquement + +```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/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 +``` + +Pour AMM v4, vérifier aussi les IDL/JSON Raydium legacy présents dans fnzero, notamment les fichiers autour de `raydium_amm_v4` / `raydium_pool_v4`, sans promouvoir `raydium_pool_v4` tant que son program id et son rôle métier ne sont pas prouvés localement. + +## 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 +``` + +## Workflow conseillé + +1. Créer une nouvelle base SQLite dédiée `0.7.51`. +2. Inventorier Carbon/fnzero/Pinax/Solscan Program IDL pour `raydium_amm_v4`. +3. Synchroniser `k_sol_dex_event_coverage_entries` avec `decoder_code = raydium_amm_v4`. +4. Utiliser Solscan `instruction=` pour obtenir rapidement des signatures non failed. +5. Backfill Demo2 signature/pool. +6. Replay local avec : + +```text +skipDexDecode = no +forceDexDecode = yes +deferInstructionObservations = yes +``` + +7. Vérifier : + +```text +coverage listed/observed/materialized +residual instruction_audit +residual upstream_git.instruction_match +failed tx materialization = 0 +non-trade trade_count = 0 +trade/candle only for swap events validés +``` + +## 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_AMM_V4_EVENT_COVERAGE_REPORT.md +validation_sql/SQL_VALIDATION_RAYDIUM_AMM_V4_0_7_51.sql +``` + +Validation finale locale : + +```bash +cargo fmt +cargo test -p kb_lib +cargo clippy -p kb_lib --all-targets -- -D warnings +``` diff --git a/docs/reports/RAYDIUM_CLMM_EVENT_COVERAGE_REPORT.md b/docs/reports/RAYDIUM_CLMM_EVENT_COVERAGE_REPORT.md index 6694e5a..0e58e1b 100644 --- a/docs/reports/RAYDIUM_CLMM_EVENT_COVERAGE_REPORT.md +++ b/docs/reports/RAYDIUM_CLMM_EVENT_COVERAGE_REPORT.md @@ -132,3 +132,9 @@ La validation finale est dans : ```text validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_49.sql ``` + +## Addendum `0.7.50-pre-r2` — source parity CLMM + +La re-vérification CLMM ajoute `cpi_event` (`e445a52e51cb9a1d`) et `update_dynamic_fee_config` (`0707500802c784f0`) depuis Carbon. Les Program-data events CLMM reçoivent maintenant un `local_event_kind` et une famille explicite quand ils sont observables localement : `swap_event`, `pool_created_event`, `liquidity_change_event`, `create_personal_position_event`, `decrease_liquidity_event`, `increase_liquidity_event`, `collect_protocol_fee_event`, `config_change_event` et `update_reward_infos_event`. + +`create_support_mint_associated` est matérialisable dans la nouvelle table `k_sol_token_account_events`. `liquidity_calculate_event` reste decoded-only car il représente un calcul/diagnostic et non une mutation de liquidité fiable. `swap_event` et `swap_router_base_in` restent également decoded-only : les trades matérialisés proviennent des instructions `swap` et `swap_v2`, ce qui évite les doublons de Program-data events et les routes sans pool direct. diff --git a/docs/reports/RAYDIUM_CPMM_CLMM_RECHECK_REPORT_0_7_50_PRE_R2.md b/docs/reports/RAYDIUM_CPMM_CLMM_RECHECK_REPORT_0_7_50_PRE_R2.md new file mode 100644 index 0000000..68a445b --- /dev/null +++ b/docs/reports/RAYDIUM_CPMM_CLMM_RECHECK_REPORT_0_7_50_PRE_R2.md @@ -0,0 +1,82 @@ +# Raydium CPMM/CLMM re-check report — `0.7.50-pre-r2` + +## Scope + +This report closes the post-Launchpad re-check for `raydium_cpmm` and `raydium_clmm` using the same discipline applied to `raydium_launchpad`: + +- every observed discriminator must have a named local event kind or an explicit decoded-only decision; +- `event_family='unknown'` is not acceptable outside synthetic `program` rows; +- materializable events must target the matching business table; +- duplicate, transport-only or context-incomplete events remain `k_sol_dex_decoded_events_only` by design; +- failed transactions must not produce business materialization. + +## Sources + +The source comparison for this tranche is based on: + +- Carbon `raydium-cpmm-decoder`; +- Carbon `raydium-clmm-decoder`; +- Pinax `substreams-solana-idls` Raydium CPMM/CLMM trees; +- Solscan Program IDL pages for CPMM and CLMM; +- `0xfnzero/sol-parser-sdk` Raydium IDL snapshots; +- local corpus replay results from the dedicated CPMM and CLMM databases. + +## CPMM decision + +`40f4bc78a7e9690a` is now coded as: + +```text +local_event_kind = raydium_cpmm.anchor_idl_instruction +event_family = idl_management +expected_db_target = k_sol_dex_decoded_events_only +``` + +Manual inspection of the three local signatures showed Anchor IDL management logs: + +- `IdlCreateAccount` on `Hi6MkRTkcgwBi1WpiiudGPHKLuaKXKamNgVsy6YqoQeMRnrkpGjNx75ymrY59tJ3NN1GCn6nrndz9thMmwALLcY`; +- `IdlCloseAccount` on `Kch9bYneKzyPg13txxpu151QHX4EgQhFqXUHxqYLXE3BbSrMt56bNMx9JbMAZzs4fbuCLLibHAtrRHdrn7u2VUD`; +- `IdlCreateAccount` on `fsKqwEAiRCQyXvCBjBX4XGzkZXyz4DeNL1Kdw9BeyGYYAcTKPEP9sP4WXVNB2FRkvBXc3YjuGhUcihLZm3Y7Znu`. + +This is not a CPMM business instruction. It must not produce trade, candle, liquidity, fee, admin or lifecycle rows. + +## CPMM expected post-replay invariants + +After replay on the CPMM database: + +- observed discriminator coverage gap should be empty; +- residual `raydium_cpmm.instruction_audit` should be empty; +- decoded event kinds without coverage should be empty; +- materialization shortfall should be empty after excluding `k_sol_dex_decoded_events_only` and failed transactions; +- `swap_event` remains decoded-only because canonical trades come from `swap_base_input` / `swap_base_output`. + +## CLMM decision + +CLMM has no remaining unknown family in the coverage matrix. The re-check keeps the following entries decoded-only by design: + +- `raydium_clmm.swap_event`: Program-data corroboration of swaps; canonical trade materialization remains on `swap` / `swap_v2`. +- `raydium_clmm.swap_router_base_in`: router instruction; no single direct pool surface should be inferred without hop-level resolution. +- `raydium_clmm.liquidity_calculate_event`: calculation/diagnostic event. +- `raydium_clmm.close_position` and `raydium_clmm.close_protocol_position`: decoded, but not materialized unless a reliable pool/pair context is available. +- `raydium_clmm.cpi_event`: Anchor transport only. + +Materialization was strengthened for CLMM liquidity events by accepting snake_case amount keys (`amount0_raw`, `amount1_raw`, `liquidity_raw`) and by resolving pool/pair context from sibling decoded events in the same transaction when the current decoded event carries useful amounts but lacks direct pair context. + +## CLMM expected post-replay invariants + +After replay on the CLMM database: + +- observed discriminator coverage gap should be empty; +- residual `raydium_clmm.instruction_audit` should be empty; +- decoded event kinds without coverage should be empty; +- failed transactions should not produce business rows; +- the validation SQL must count `k_sol_token_account_events`, otherwise `create_support_mint_associated` is falsely reported as a materialization gap. + +## Validation files + +Use: + +```text +validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_0_7_50_PRE_R2.sql +validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_50_PRE_R2.sql +validation_sql/SQL_TRACE_RAYDIUM_CPMM_AUDIT_40F_0_7_50_PRE_R2.sql +``` diff --git a/docs/reports/RAYDIUM_CPMM_EVENT_COVERAGE_REPORT.md b/docs/reports/RAYDIUM_CPMM_EVENT_COVERAGE_REPORT.md index 03c18dc..6602979 100644 --- a/docs/reports/RAYDIUM_CPMM_EVENT_COVERAGE_REPORT.md +++ b/docs/reports/RAYDIUM_CPMM_EVENT_COVERAGE_REPORT.md @@ -202,3 +202,11 @@ update_pool_status ``` La prochaine tranche fonctionnelle est `0.7.49 raydium_clmm`. + +## Addendum `0.7.50-pre-r2` — cpi_event et audit `40f4bc78a7e9690a` + +La re-vérification CPMM ajoute explicitement `cpi_event` à la matrice coverage locale avec le discriminant Carbon `e445a52e51cb9a1d`. Cette entrée est un transport Anchor/CPI et reste `k_sol_dex_decoded_events_only`. + +Le discriminant observé localement `40f4bc78a7e9690a` est classé comme `raydium_cpmm.anchor_idl_instruction`, avec `event_family=idl_management` et `expected_db_target=k_sol_dex_decoded_events_only`. Les signatures inspectées montrent `Program log: Instruction: IdlCreateAccount` et `Program log: Instruction: IdlCloseAccount` sur le compte `anchor:idl`; il ne correspond donc pas au `cpi_event` Carbon et ne doit pas être matérialisé dans les tables métier. + +Trace SQL ajouté : `validation_sql/SQL_TRACE_RAYDIUM_CPMM_AUDIT_40F_0_7_50_PRE_R2.sql` liste les signatures, slots, index d'instruction, comptes et données associées à `40f4bc78a7e9690a`. diff --git a/docs/reports/RAYDIUM_LAUNCHPAD_EVENT_COVERAGE_REPORT.md b/docs/reports/RAYDIUM_LAUNCHPAD_EVENT_COVERAGE_REPORT.md new file mode 100644 index 0000000..76ed3d0 --- /dev/null +++ b/docs/reports/RAYDIUM_LAUNCHPAD_EVENT_COVERAGE_REPORT.md @@ -0,0 +1,231 @@ +# Raydium Launchpad event coverage report — `0.7.50` + +## Scope + +`0.7.50` opens the `raydium_launchpad` tranche after the functional closure of `0.7.49 raydium_clmm`. + +Local canonical decoder/surface code: + +```text +raydium_launchpad +``` + +Canonical program id: + +```text +LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj +``` + +The legacy local name `raydium_launchlab` is not kept in the public Rust API. Coverage rows, upstream registry rows, launch origin entries, and support matrix rows use `raydium_launchpad`. + +## Sources used + +Primary source hints for this tranche: + +- Carbon decoder registry/source: `sevenlabs-hq/carbon/decoders/raydium-launchpad-decoder`. +- Solscan Program IDL/account page: `https://solscan.io/account/LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj#programIdl`. +- fnzero IDL sources: `sol-parser-sdk` / `solana-program-idls` listings, including `raydium_launchpad.json` and separate `raydium_pool_v4.json` as an audit-only source. +- Raydium SDK Launchpad examples for account-shape hints only. + +These sources are not treated as final business proof. Promotion still requires local corpus observation and SQL validation. + +## Bootstrap implementation delta + +Implemented in this delta: + +- `RAYDIUM_LAUNCHPAD_PROGRAM_ID` added as the canonical public constant. +- Upstream generated registry rows normalized from `raydium_launchlab` to `raydium_launchpad`. +- Built-in launch surface code normalized to `raydium_launchpad`. +- DEX support/catalog entries normalized to `raydium_launchpad`. +- Raydium instruction audit fallback now recognizes Launchpad program id. +- Launchpad mapped instruction fallback added for locally listed Launchpad discriminators. +- Coverage target override keeps Launchpad rows `decoded_events_only` until corpus promotion. +- SQL validation file added for the `0.7.50` tranche. + +## Listed Launchpad entries + +The local upstream registry now lists one program entry plus the following 26 discriminator entries. + +| Entry kind | Entry name | Discriminator | Initial family | Initial DB target | +|---|---:|---:|---|---| +| instruction | `buy_exact_in` | `faea0d7bd59c13ec` | swap | decoded_events_only | +| instruction | `buy_exact_out` | `18d3742869039938` | swap | decoded_events_only | +| instruction | `claim_creator_fee` | `1a618acb84ab8dfc` | fee | decoded_events_only | +| instruction | `claim_platform_fee` | `9c27d0874ced3d48` | fee | decoded_events_only | +| instruction | `claim_platform_fee_from_vault` | `75f1c6a8f8da501d` | fee | decoded_events_only | +| event | `claim_vested_event` | `15c2725778d3e220` | fee/vesting audit | decoded_events_only | +| instruction | `claim_vested_token` | `3121681ebd9d4f23` | fee/vesting audit | decoded_events_only | +| instruction | `collect_fee` | `3cadf767045d8230` | fee | decoded_events_only | +| instruction | `collect_migrate_fee` | `ffba96dfeb76c9ba` | fee/migration audit | decoded_events_only | +| instruction | `create_config` | `c9cff3724b6f2fbd` | admin_config | decoded_events_only | +| instruction | `create_platform_config` | `b05ac4affd71dc14` | admin_config | decoded_events_only | +| instruction | `create_vesting_account` | `81b2020dd9ace6da` | account_create/vesting audit | decoded_events_only | +| event | `create_vesting_event` | `96980bb334d2bf7d` | account_create/vesting audit | decoded_events_only | +| instruction | `initialize` | `afaf6d1f0d989bed` | pool_create/launch | decoded_events_only | +| instruction | `initialize_v2` | `4399af27da102620` | pool_create/launch | decoded_events_only | +| instruction | `initialize_with_token_2022` | `25be7ede2c9aab11` | pool_create/launch | decoded_events_only | +| instruction | `migrate_to_amm` | `cf52c091fecf91df` | migration | decoded_events_only | +| instruction | `migrate_to_cpswap` | `885cc8671cda908c` | migration | decoded_events_only | +| event | `pool_create_event` | `97d7e20976a173ae` | pool_create | decoded_events_only | +| instruction | `remove_platform_curve_param` | `1b1e3ea95de01891` | admin_config | decoded_events_only | +| instruction | `sell_exact_in` | `9527de9bd37c981a` | swap | decoded_events_only | +| instruction | `sell_exact_out` | `5fc8472208090ba6` | swap | decoded_events_only | +| event | `trade_event` | `bddb7fd34ee661ee` | swap | decoded_events_only | +| instruction | `update_config` | `1d9efcbf0a53db63` | admin_config | decoded_events_only | +| instruction | `update_platform_config` | `c33c4c81922d438f` | admin_config | decoded_events_only | +| instruction | `update_platform_curve_param` | `8a908afadc800439` | admin_config | decoded_events_only | + +Notes: + +- The buy/sell instruction account hints currently use account index `4` as candidate pool account and indexes `9`/`10` as candidate token mints, based on Carbon/Raydium Launchpad account shape hints. This is an audit helper, not a materialization proof. +- Fee/admin/migration/vesting entries intentionally do not infer pool/token accounts until corpus confirms the account semantics. +- Program-data transport is represented by `cpi_event`; embedded events are decoded by their own discriminators and materialized only when their event family has a validated target. + +## Family audit matrix + +| Family | Launchpad status in `0.7.50` final | Decision | +|---|---|---| +| swap | `trade_event` materialized as trades/candles; buy/sell instructions materialized as launch breadcrumbs. | No duplicate trades from instruction breadcrumbs. | +| pool_create | `initialize`, `initialize_v2`, `initialize_with_token_2022`, `pool_create_event`. | Pool lifecycle/catalogue materialized when transaction succeeded. | +| add_liquidity | No direct Launchpad entry confirmed. | Non-applicable unless local corpus proves direct Launchpad liquidity instruction. | +| remove_liquidity | No direct Launchpad liquidity remove entry confirmed. | Non-applicable unless local corpus proves direct Launchpad liquidity instruction. | +| position_open | No direct Launchpad position instruction confirmed. | Non-applicable. | +| position_close | No direct Launchpad position instruction confirmed. | Non-applicable. | +| fee | claim/collect fee entries listed. | Fee table materialization enabled for observed successful transactions. | +| reward | No direct reward instruction confirmed. | Non-applicable unless local corpus proves otherwise. | +| admin/config | create/update config and platform curve/config entries listed. | Pool admin materialization enabled for observed successful transactions. | +| mint | Token minting may appear as SPL Token/Token-2022 side effect. | Not `raydium_launchpad.*` without direct program proof. | +| burn | Token burn may appear as SPL Token/Token-2022 side effect. | Not `raydium_launchpad.*` without direct program proof. | +| transfer | Transfers are expected as SPL Token/Token-2022 side effects. | Not `raydium_launchpad.*` without direct program proof. | +| account_create / vesting | `create_vesting_account`, `create_platform_vesting_account`, vesting events. | Launch event materialization enabled for observed successful transactions; unobserved events remain mapped. | +| account_close | No direct Launchpad account close confirmed. | Non-applicable. | +| wrap_sol | No direct Launchpad wrap SOL confirmed. | Side effect only unless corpus proves direct instruction. | +| unwrap_sol | No direct Launchpad unwrap SOL confirmed. | Side effect only unless corpus proves direct instruction. | +| order_place | No orderbook surface confirmed. | Non-applicable. | +| order_cancel | No orderbook surface confirmed. | Non-applicable. | +| order_fill | No orderbook surface confirmed. | Non-applicable. | +| consume_events | No orderbook surface confirmed. | Non-applicable. | +| settle_funds | No orderbook surface confirmed. | Non-applicable. | +| vault_deposit | No direct vault deposit confirmed. | Non-applicable. | +| vault_withdraw | No direct vault withdraw confirmed. | Non-applicable. | +| lock | No direct lock confirmed. | Non-applicable. | +| unlock | No direct unlock confirmed. | Non-applicable. | +| launch | initialize/pool_create path listed. | Decode/audit only. | +| migration | `migrate_to_amm`, `migrate_to_cpswap`, `collect_migrate_fee` listed. | Decode/audit only. Destination DEX materialization must be proven locally. | +| stake | No direct stake confirmed. | Non-applicable. | +| unstake | No direct unstake confirmed. | Non-applicable. | +| unknown/unmapped audit | `raydium_launchpad.instruction_audit` retained for unmatched program instructions. | Must trend toward zero for locally covered discriminators after backfill/replay. | + +## SQL validation expectations + +After targeted backfill and replay: + +1. `k_sol_dex_event_coverage_entries` should contain the Launchpad program entry and discriminator entries. +2. Mapped entries should have `local_event_kind = raydium_launchpad.` and initial `proof_status = upstream_git_mapped_unverified` until observed. +3. Locally observed instructions should increment `k_sol_instruction_observations` for `decoder_code = raydium_launchpad`. +4. `upstream_git.instruction_match` fallback rows for `upstreamDecoderCode = raydium_launchpad` should be zero for locally covered instruction discriminators. +5. `raydium_launchpad.*` rows must not produce trades/candles unless a later corpus-backed patch explicitly promotes a specific event. +6. Failed transactions may be decoded/audited, but must not be materialized in trade/candle tables. + +Validation file: + +```text +validation_sql/SQL_VALIDATION_RAYDIUM_LAUNCHPAD_0_7_50.sql +``` + +## Suggested targeted Solscan discovery loop + +For each discriminator: + +```text +https://solscan.io/account/LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj?instruction=&hide_spam=true&hide_failed=true&show_related=false&sort=desc +``` + +Then: + +1. copy a small batch of recent non-failed signatures; +2. backfill through Demo2 textarea batch; +3. replay with `forceDexDecode=yes` and `deferInstructionObservations=yes`; +4. run the validation SQL; +5. promote only entries whose local payload and account semantics are proven. + +## `raydium_pool_v4.json` audit status + +The current workspace archive does not contain a local `raydium_pool_v4.json` copy. External fnzero IDL listings expose a separate `raydium_pool_v4.json` beside `raydium_launchpad.json`, but this delta does not confirm its program id or business role. + +Decision for `0.7.50`: + +- do not promote `raydium_pool_v4` as a DEX/surface; +- keep `0.7.53 raydium_pool_v4 audit / program-id decision` conditional; +- require program id confirmation and local corpus before any roadmap promotion. + +## Current limitations + +This delta was prepared from the provided archive only. No live RPC backfill, fresh SQLite replay, `cargo fmt`, `cargo test`, or `cargo clippy` could be executed in the current environment because the Rust toolchain is unavailable here. The SQL and code paths are prepared for local validation in the normal project environment. + +## Local corpus snapshot from first 0.7.50 backfill + +Observed after targeted Demo2 backfills and pool backfill on a fresh 0.7.50 DB: + +- coverage listed entries: `27`; +- decoded/local mapped entries: `26`; +- observed entries: `21`; +- materialized entries: `0`; +- total observed coverage count: `672`; +- total materialized count: `0`; +- trade count: `0`; +- residual `upstream_git.instruction_match` for `raydium_launchpad`: `0`; +- residual `raydium_launchpad.instruction_audit`: `287`; +- residual audit discriminators: `e445a52e51cb9a1d` (`276`), `9247ad4562130f6a` (`10`), `a25b92c75d85eaed` (`1`). + +The `e445a52e51cb9a1d` selector is handled as Anchor self-CPI event transport. It is not promoted as a Raydium Launchpad business instruction. The two low-count residual discriminators remain local-corpus audit-only until an IDL/upstream mapping is confirmed. + + +## pre3 correction — Demo3 preset and Launchpad pool catalog + +The first replay after pre2 confirmed that Anchor self-CPI selector `e445a52e51cb9a1d` carries Launchpad `trade_event` (`bddb7fd34ee661ee`) and `pool_create_event` (`97d7e20976a173ae`). pre3 therefore decodes those two self-CPI event rows as direct `raydium_launchpad.*` events instead of leaving them under `raydium_launchpad.instruction_audit`. `trade_event` remains audit/decoded-only and is still not promoted to `k_sol_trade_events` or candles. + +pre3 also fixes the Launchpad `initialize`, `initialize_v2` and `initialize_with_token_2022` account mapping using the Carbon account shape: `pool_state` index 5, `base_mint` index 6 and `quote_mint` index 7. These initialize rows are now routed to business-level pool detection as `raydium_launchpad` bonding-curve pools with pending status, which should allow pool `6HLQPoLrzX6LqePRiXQ1GGs2Dd9K3dp9VhTSHBugYzzZ` to appear in the local catalog after a forced replay when its initialize transaction is present locally. + +Demo3 now exposes a `Raydium Launchpad` preset with program id `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj`. + + +## Final `0.7.50` closure snapshot + +Validated closure state reported by local replay: + +- `cargo test -p kb_lib`: 404 passed, 0 failed. +- Local replay: 437 replayed, 437 ledger upserts, 30 unsafe ledger rows, 256 trades, 115 lifecycle rows, 1024 candle upserts, 6205 instruction observations. +- Launchpad catalogue: 58 tokens, 58 pools, 58 pairs after replay. +- Coverage normalization: no ambiguous `unknown` family remains; only the synthetic `program` row may have an empty family. +- `trade_event`: 260 decoded, 250 successful materialized trades; 10 failed transactions intentionally not materialized. +- `buy_exact_*` / `sell_exact_*`: materialized as `k_sol_launch_events` swap-instruction breadcrumbs, not as trades. +- `cpi_event`: kept as `cpi_transport` / decoded-only; embedded events are decoded by direct event discriminator. +- Successful `trade_event` rows without materialized `k_sol_trade_events`: zero. + +Post-closure recheck assets added: + +- `validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_0_7_50_PRE_R2.sql` +- `validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_50_PRE_R2.sql` +- `docs/SOLSCAN_ACCOUNT_SOURCE_MATRIX.md` +- `kb_lib::SOLSCAN_ACCOUNT_SOURCES` + +## Final cleanup note — CPMM residual audit after 0.7.50 recheck + +During the 0.7.50 post-Launchpad recheck, the CPMM residual audit query still showed three `raydium_cpmm.instruction_audit` rows for discriminator `40f4bc78a7e9690a` even though the same discriminator is now locally mapped as `raydium_cpmm.anchor_idl_instruction`. + +The final cleanup patch makes the deletion FK-safe by unlinking `k_sol_instruction_observations.decoded_event_id` before deleting the legacy audit rows. It also repeats the CPMM cleanup after coverage refresh and refreshes coverage again if rows were removed. + +Expected final state after replay: + +```text +raydium_cpmm.instruction_audit / 40f4bc78a7e9690a = 0 +raydium_cpmm decoded events without coverage row = 0 +raydium_cpmm.anchor_idl_instruction remains decoded-only / idl_management +``` + +Validation SQL: + +```text +validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_AUDIT_CLEANUP_0_7_50_FINAL.sql +``` diff --git a/kb_demo_app/frontend/ts/demo3.ts b/kb_demo_app/frontend/ts/demo3.ts index 7aa6577..5f968ee 100644 --- a/kb_demo_app/frontend/ts/demo3.ts +++ b/kb_demo_app/frontend/ts/demo3.ts @@ -28,6 +28,7 @@ const presets: Demo3Preset[] = [ { label: "PumpSwap", dexCode: "pump_swap", programId: "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", description: "DEX effectif PumpSwap." }, { label: "Raydium CPMM", dexCode: "raydium_cpmm", programId: "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C", description: "Raydium CPMM." }, { label: "Raydium CLMM", dexCode: "raydium_clmm", programId: "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK", description: "Raydium CLMM." }, + { label: "Raydium Launchpad", dexCode: "raydium_launchpad", programId: "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj", description: "Raydium Launchpad / LaunchLab. Surface de lancement Raydium 0.7.50." }, { label: "Raydium AMM v4", dexCode: "raydium_amm_v4", programId: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", description: "Raydium AMM v4 legacy. À prouver par corpus avant décodage swap." }, { label: "Raydium Stable Swap", dexCode: "raydium_stable_swap", programId: "5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h", description: "Stable Swap Raydium à vérifier par corpus." }, { label: "Meteora DLMM", dexCode: "meteora_dlmm", programId: "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo", description: "Meteora DLMM." }, diff --git a/kb_demo_app/frontend/ts/demo_pipeline2.ts b/kb_demo_app/frontend/ts/demo_pipeline2.ts index d45cd48..61564c3 100644 --- a/kb_demo_app/frontend/ts/demo_pipeline2.ts +++ b/kb_demo_app/frontend/ts/demo_pipeline2.ts @@ -69,6 +69,7 @@ interface LocalPipelineReplayResult { feeEventCount: number; rewardEventCount: number; poolAdminEventCount: number; + tokenAccountEventCount: number; pairCandleUpsertCount: number; analyticSignalUpsertCount: number; transactionClassificationCount: number; @@ -805,7 +806,7 @@ document.addEventListener("DOMContentLoaded", async () => { appendLogLine( logTextarea, - `[ui] local pipeline replay completed: ${result.replayedTransactionCount.toString()} replayed, ${result.decodeSkippedCount.toString()} decode skipped, ${result.decodeLedgerUpsertCount.toString()} ledger upserts, ${result.decodeLedgerUnsafeCount.toString()} unsafe ledger rows, ${result.tradeEventCount.toString()} trades, ${result.liquidityEventCount.toString()} liquidity, ${result.poolLifecycleEventCount.toString()} lifecycle, ${result.pairCandleUpsertCount.toString()} candle upserts, instructionObservations='${result.instructionObservationUpsertedCount.toString()}', resetDeleted='${result.resetMarketMaterializationDeletedCount.toString()}'`, + `[ui] local pipeline replay completed: ${result.replayedTransactionCount.toString()} replayed, ${result.decodeSkippedCount.toString()} decode skipped, ${result.decodeLedgerUpsertCount.toString()} ledger upserts, ${result.decodeLedgerUnsafeCount.toString()} unsafe ledger rows, ${result.tradeEventCount.toString()} trades, ${result.liquidityEventCount.toString()} liquidity, ${result.poolLifecycleEventCount.toString()} lifecycle, ${result.tokenAccountEventCount.toString()} tokenAccount, ${result.pairCandleUpsertCount.toString()} candle upserts, instructionObservations='${result.instructionObservationUpsertedCount.toString()}', resetDeleted='${result.resetMarketMaterializationDeletedCount.toString()}'`, ); await refreshCatalog(); diff --git a/kb_demo_app/package.json b/kb_demo_app/package.json index 3125ccf..62a83df 100644 --- a/kb_demo_app/package.json +++ b/kb_demo_app/package.json @@ -1,7 +1,7 @@ { "name": "kb-demo-app", "private": true, - "version": "0.7.49", + "version": "0.7.50", "type": "module", "scripts": { "dev": "vite", @@ -14,15 +14,15 @@ "@fortawesome/fontawesome-free": "^7.2", "@tauri-apps/api": "^2.11", "bootstrap": "^5.3", - "echarts": "^6.0", + "echarts": "^6.1", "resize-observer-polyfill": "^1.5", "simplebar": "^6.3" }, "devDependencies": { "@tauri-apps/cli": "^2.11", "@types/bootstrap": "^5.2", - "@types/node": "^25.6", - "sass-embedded": "^1.99", + "@types/node": "^25.9", + "sass-embedded": "^1.100", "typescript": "^5.9", "vite": "^8.0" } diff --git a/kb_demo_app/tauri.conf.json b/kb_demo_app/tauri.conf.json index 2d58366..1579680 100644 --- a/kb_demo_app/tauri.conf.json +++ b/kb_demo_app/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "kb-demo-app", - "version": "0.7.49", + "version": "0.7.50", "identifier": "com.sasedev.kb-demo-app", "build": { "beforeDevCommand": "npm run dev", diff --git a/kb_lib/src/constants.rs b/kb_lib/src/constants.rs index 7d35263..82c85c5 100644 --- a/kb_lib/src/constants.rs +++ b/kb_lib/src/constants.rs @@ -276,7 +276,13 @@ pub const RAYDIUM_CLMM_PROGRAM_ID: &str = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7gr pub const RAYDIUM_CPMM_PROGRAM_ID: &str = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"; /// Raydium LaunchLab / Launchpad program id. ("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"). -pub const RAYDIUM_LAUNCHLAB_PROGRAM_ID: &str = "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"; +pub const RAYDIUM_LAUNCHPAD_PROGRAM_ID: &str = "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"; + +/// Raydium LaunchLab platform config account observed on Solscan. +/// +/// This is an account source, not a decoder program id. +pub const RAYDIUM_LAUNCHPAD_PLATFORM_CONFIG_ACCOUNT_ID: &str = + "4Bu96XjU84XjPDSpveTVf6LYGCkfW5FK7SNkREWcEfV4"; /// Raydium AMM routing program id. ("routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS"). pub const RAYDIUM_AMM_ROUTING_PROGRAM_ID: &str = "routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS"; @@ -341,9 +347,14 @@ pub const MARINADE_FINANCE_PROGRAM_ID: &str = "MarBmsSgKXdrN1egZf5sqe1TMai9K1rCh /// Meteora Vault program id extracted from upstream Git decoder source. pub const METEORA_VAULT_PROGRAM_ID: &str = "24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi"; -/// Moonshot program id extracted from upstream Git decoder source. +/// Moonshot program id kept for upstream `moonshot` decoder compatibility. +/// +/// Moonit / Moonshot-family program id observed with a Solscan Program IDL. pub const MOONSHOT_PROGRAM_ID: &str = "MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG"; +/// Moonshot token authority account observed on Solscan, not a decoder program. +pub const MOONSHOT_TOKEN_AUTHORITY_ID: &str = "7rtiKSUDLBm59b1SBmD9oajcP8xE64vAGSMbAN5CXy1q"; + /// OKX DEX program id extracted from upstream Git decoder source. pub const OKX_DEX_PROGRAM_ID: &str = "6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma"; @@ -415,3 +426,953 @@ pub const NAME_SERVICE_PROGRAM_ID: &str = "namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8 /// This is not treated as a DEX program. It is used only to tag protocol /// candidates with `candidate_surface = "arbitrage_bot"`. pub const ARBITRAGE_BOT_6MWVT_PROGRAM_ID: &str = "6MWVTis8rmmk6Vt9zmAJJbmb3VuLpzoQ1aHH4N6wQEGh"; + +/// One Solscan account source collected during manual DEX/program inventory. +/// +/// This catalogue is intentionally broader than supported DEXes: it also contains +/// routers, token authorities, bot programs, lending programs and system programs. +/// Promotion into a decoder/support-matrix entry still requires source review and +/// local corpus verification. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SolscanAccountSource { + /// Human-readable label used in the manual source inventory. + pub label: &'static str, + /// Solana account or program id shown by Solscan. + pub account_id: &'static str, + /// Solscan account URL used as the discovery source. + pub solscan_url: &'static str, + /// IDL status observed in the manual Solscan pass. + /// + /// Values currently used: `solscan_program_idl`, `no_idl`, `solscan_account`. + pub idl_status: &'static str, +} + +/// Manual Solscan account-source inventory collected for post-`0.7.50` DEX discovery. +/// +/// This is a source catalogue only. It must not be interpreted as decoder support. +pub const SOLSCAN_ACCOUNT_SOURCES: &[SolscanAccountSource] = &[ + SolscanAccountSource { + label: "1Dex Program", + account_id: "DEXYosS6oEGvk8uCDayvwEZz4qEyDJRf9nFgYCaqPMTm", + solscan_url: "https://solscan.io/account/DEXYosS6oEGvk8uCDayvwEZz4qEyDJRf9nFgYCaqPMTm", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "AlphaQ", + account_id: "ALPHAQmeA7bjrVuccPsYPiCvsi428SNwte66Srvs4pHA", + solscan_url: "https://solscan.io/account/ALPHAQmeA7bjrVuccPsYPiCvsi428SNwte66Srvs4pHA", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Aldrin AMM", + account_id: "AMM55ShdkoGRB5jVYPjWziwk8m5MpwyDgsMWHaMSQWH6", + solscan_url: "https://solscan.io/account/AMM55ShdkoGRB5jVYPjWziwk8m5MpwyDgsMWHaMSQWH6", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Aldrin AMM V2", + account_id: "CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4", + solscan_url: "https://solscan.io/account/CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "ApePro Smart Wallet Program", + account_id: "JSW99DKmxNyREQM14SQLDykeBvEUG63TeohrvmofEiw", + solscan_url: "https://solscan.io/account/JSW99DKmxNyREQM14SQLDykeBvEUG63TeohrvmofEiw#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Aquifer", + account_id: "AQU1FRd7papthgdrwPTTq5JacJh8YtwEXaBfKU3bTz45", + solscan_url: "https://solscan.io/account/AQU1FRd7papthgdrwPTTq5JacJh8YtwEXaBfKU3bTz45", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Arbitrage Bot (3s1rA)", + account_id: "3s1rAymURnacreXreMy718GfqW6kygQsLNka1xDyW8pC", + solscan_url: "https://solscan.io/account/3s1rAymURnacreXreMy718GfqW6kygQsLNka1xDyW8pC", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Arbitrage Bot (6MWVT)", + account_id: "6MWVTis8rmmk6Vt9zmAJJbmb3VuLpzoQ1aHH4N6wQEGh", + solscan_url: "https://solscan.io/account/6MWVTis8rmmk6Vt9zmAJJbmb3VuLpzoQ1aHH4N6wQEGh", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Arbitrage Bot (9Zzf9)", + account_id: "9Zzf9QqTy3TkyXysvJBsXyuRjda5aXCEJ9vXfL2HKSYv", + solscan_url: "https://solscan.io/account/9Zzf9QqTy3TkyXysvJBsXyuRjda5aXCEJ9vXfL2HKSYv", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Axiom Trade", + account_id: "FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9", + solscan_url: "https://solscan.io/account/FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Bags: Token Authority", + account_id: "BAGSB9TpGrZxQbEsrEznv5jXXdwyP6AXerN8aVRiAmcv", + solscan_url: "https://solscan.io/account/BAGSB9TpGrZxQbEsrEznv5jXXdwyP6AXerN8aVRiAmcv", + idl_status: "solscan_account", + }, + SolscanAccountSource { + label: "Believe : Token Authority", + account_id: "5qWya6UjwWnGVhdSBL3hyZ7B45jbk6Byt1hwd7ohEGXE", + solscan_url: "https://solscan.io/account/5qWya6UjwWnGVhdSBL3hyZ7B45jbk6Byt1hwd7ohEGXE", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "BisonFi", + account_id: "BiSoNHVpsVZW2F7rx2eQ59yQwKxzU5NvBcmKshCSUypi", + solscan_url: "https://solscan.io/account/BiSoNHVpsVZW2F7rx2eQ59yQwKxzU5NvBcmKshCSUypi", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "BonkSwap", + account_id: "BSwp6bEBihVLdqJRKGgzjcGLHkcTuzmSo1TQkHepzH8p", + solscan_url: "https://solscan.io/account/BSwp6bEBihVLdqJRKGgzjcGLHkcTuzmSo1TQkHepzH8p#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Boop.fun", + account_id: "boop8hVGQGqehUK2iVEMEnMrL5RbjywRzHKBmBE7ry4", + solscan_url: "https://solscan.io/account/boop8hVGQGqehUK2iVEMEnMrL5RbjywRzHKBmBE7ry4#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Byreal: CLMM", + account_id: "REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2", + solscan_url: "https://solscan.io/account/REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Bubblegum", + account_id: "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY", + solscan_url: "https://solscan.io/account/BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Carrot DeFi", + account_id: "CarrotwivhMpDnm27EHmRLeQ683Z1PufuqEmBZvD282s", + solscan_url: "https://solscan.io/account/CarrotwivhMpDnm27EHmRLeQ683Z1PufuqEmBZvD282s#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "CCTP TokenMessengerMinter", + account_id: "CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3", + solscan_url: "https://solscan.io/account/CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "CCTP TokenMessengerMinterV2", + account_id: "CCTPV2vPZJS2u2BBsUoscuikbYjnpFmbFsvVuJdgUMQe", + solscan_url: "https://solscan.io/account/CCTPV2vPZJS2u2BBsUoscuikbYjnpFmbFsvVuJdgUMQe#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Clone", + account_id: "C1onEW2kPetmHmwe74YC1ESx3LnFEpVau6g2pg4fHycr", + solscan_url: "https://solscan.io/account/C1onEW2kPetmHmwe74YC1ESx3LnFEpVau6g2pg4fHycr#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Crema Finance Program", + account_id: "CLMM9tUoggJu2wagPkkqs9eFG4BWhVBZWkP1qv3Sp7tR", + solscan_url: "https://solscan.io/account/CLMM9tUoggJu2wagPkkqs9eFG4BWhVBZWkP1qv3Sp7tR", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Cropper Finance", + account_id: "CTMAxxk34HjKWxQ3QLZK1HpaLXmBveao3ESePXbiyfzh", + solscan_url: "https://solscan.io/account/CTMAxxk34HjKWxQ3QLZK1HpaLXmBveao3ESePXbiyfzh", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Cropper Whirlpool", + account_id: "H8W3ctz92svYg6mkn1UtGfu2aQr2fnUFHM1RhScEtQDt", + solscan_url: "https://solscan.io/account/H8W3ctz92svYg6mkn1UtGfu2aQr2fnUFHM1RhScEtQDt", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "deBridge Destination", + account_id: "dst5MGcFPoBeREFAA5E3tU5ij8m5uVYwkzkSAbsLbNo", + solscan_url: "https://solscan.io/account/dst5MGcFPoBeREFAA5E3tU5ij8m5uVYwkzkSAbsLbNo", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "deBridge Source", + account_id: "src5qyZHqTqecJV4aY6Cb6zDZLMDzrDKKezs22MPHr4", + solscan_url: "https://solscan.io/account/src5qyZHqTqecJV4aY6Cb6zDZLMDzrDKKezs22MPHr4", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Dexlab Swap", + account_id: "DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N", + solscan_url: "https://solscan.io/account/DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "DFlow Aggregator v4", + account_id: "DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH", + solscan_url: "https://solscan.io/account/DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Drift V2 Program", + account_id: "dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH", + solscan_url: "https://solscan.io/account/dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Fluxbeam Program", + account_id: "FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X", + solscan_url: "https://solscan.io/account/FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Fusion AMM", + account_id: "fUSioN9YKKSa3CUC2YUc4tPkHJ5Y6XW1yz8y6F7qWz9", + solscan_url: "https://solscan.io/account/fUSioN9YKKSa3CUC2YUc4tPkHJ5Y6XW1yz8y6F7qWz9#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Futarchy AMM", + account_id: "FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq", + solscan_url: "https://solscan.io/account/FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Gavel", + account_id: "srAMMzfVHVAtgSJc8iH6CfKzuWuUTzLHVCE81QU1rgi", + solscan_url: "https://solscan.io/account/srAMMzfVHVAtgSJc8iH6CfKzuWuUTzLHVCE81QU1rgi", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "GoonFi", + account_id: "goonERTdGsjnkZqWuVjs73BZ3Pb9qoCUdBUL17BnS5j", + solscan_url: "https://solscan.io/account/goonERTdGsjnkZqWuVjs73BZ3Pb9qoCUdBUL17BnS5j", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "GoonFi V2", + account_id: "goonuddtQRrWqqn5nFyczVKaie28f3kDkHWkHtURSLE", + solscan_url: "https://solscan.io/account/goonuddtQRrWqqn5nFyczVKaie28f3kDkHWkHtURSLE", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "GooseFX: GAMMA", + account_id: "GAMMA7meSFWaBXF25oSUgmGRwaW6sCMFLmBNiMSdbHVT", + solscan_url: "https://solscan.io/account/GAMMA7meSFWaBXF25oSUgmGRwaW6sCMFLmBNiMSdbHVT#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "GooseFX V2", + account_id: "GFXsSL5sSaDfNFQUYsHekbWBW1TsFdjDYzACh62tEHxn", + solscan_url: "https://solscan.io/account/GFXsSL5sSaDfNFQUYsHekbWBW1TsFdjDYzACh62tEHxn#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Guac Swap", + account_id: "Gswppe6ERWKpUTXvRPfXdzHhiCyJvLadVvXGfdpBqcE1", + solscan_url: "https://solscan.io/account/Gswppe6ERWKpUTXvRPfXdzHhiCyJvLadVvXGfdpBqcE1#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "HawkFi Program", + account_id: "FqGg2Y1FNxMiGd51Q6UETixQWkF5fB92MysbYogRJb3P", + solscan_url: "https://solscan.io/account/FqGg2Y1FNxMiGd51Q6UETixQWkF5fB92MysbYogRJb3P", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Heaven DEX", + account_id: "HEAVENoP2qxoeuF8Dj2oT1GHEnu49U5mJYkdeC8BAX2o", + solscan_url: "https://solscan.io/account/HEAVENoP2qxoeuF8Dj2oT1GHEnu49U5mJYkdeC8BAX2o", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Helium Treasury Management", + account_id: "treaf4wWBBty3fHdyBpo35Mz84M8k3heKXmjmi9vFt5", + solscan_url: "https://solscan.io/account/treaf4wWBBty3fHdyBpo35Mz84M8k3heKXmjmi9vFt5#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "HumidiFi", + account_id: "9H6tua7jkLhdm3w8BvgpTn5LZNU7g4ZynDmCiNN3q6Rp", + solscan_url: "https://solscan.io/account/9H6tua7jkLhdm3w8BvgpTn5LZNU7g4ZynDmCiNN3q6Rp", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Hylo Exchange", + account_id: "HYEXCHtHkBagdStcJCp3xbbb9B7sdMdWXFNj6mdsG4hn", + solscan_url: "https://solscan.io/account/HYEXCHtHkBagdStcJCp3xbbb9B7sdMdWXFNj6mdsG4hn#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Hylo Stability Pool", + account_id: "HysTabVUfmQBFcmzu1ctRd1Y1fxd66RBpboy1bmtDSQQ", + solscan_url: "https://solscan.io/account/HysTabVUfmQBFcmzu1ctRd1Y1fxd66RBpboy1bmtDSQQ#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Invariant Swap", + account_id: "HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt", + solscan_url: "https://solscan.io/account/HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Jito Tip Distribution", + account_id: "4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7", + solscan_url: "https://solscan.io/account/4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Jupiter Aggregator v6", + account_id: "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", + solscan_url: "https://solscan.io/account/JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Jupiter Aggregator v4", + account_id: "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB", + solscan_url: "https://solscan.io/account/JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Jupiter DCA program", + account_id: "DCA265Vj8a9CEuX1eb1LWRnDT7uK6q1xMipnNyatn23M", + solscan_url: "https://solscan.io/account/DCA265Vj8a9CEuX1eb1LWRnDT7uK6q1xMipnNyatn23M#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Jupiter Lend Borrow", + account_id: "jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi", + solscan_url: "https://solscan.io/account/jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Jupiter Lend Earn", + account_id: "jup3YeL8QhtSx1e253b2FDvsMNC87fDrgQZivbrndc9", + solscan_url: "https://solscan.io/account/jup3YeL8QhtSx1e253b2FDvsMNC87fDrgQZivbrndc9#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Jupiter Lend Flash Loan", + account_id: "jupgfSgfuAXv4B6R2Uxu85Z1qdzgju79s6MfZekN6XS", + solscan_url: "https://solscan.io/account/jupgfSgfuAXv4B6R2Uxu85Z1qdzgju79s6MfZekN6XS", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Jupiter Lend Liquidity", + account_id: "jupeiUmn818Jg1ekPURTpr4mFo29p46vygyykFJ3wZC", + solscan_url: "https://solscan.io/account/jupeiUmn818Jg1ekPURTpr4mFo29p46vygyykFJ3wZC", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Jupiter Limit Order", + account_id: "jupoNjAxXgZ4rjzxzPMP4oxduvQsQtZzyknqvzYNrNu", + solscan_url: "https://solscan.io/account/jupoNjAxXgZ4rjzxzPMP4oxduvQsQtZzyknqvzYNrNu#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Jupiter Limit Order V2", + account_id: "j1o2qRpjcyUwEvwtcfhEQefh773ZgjxcVRry7LDqg5X", + solscan_url: "https://solscan.io/account/j1o2qRpjcyUwEvwtcfhEQefh773ZgjxcVRry7LDqg5X#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Jupiter Lock", + account_id: "LocpQgucEQHbqNABEYvBvwoxCPsSbG91A1QaQhQQqjn", + solscan_url: "https://solscan.io/account/LocpQgucEQHbqNABEYvBvwoxCPsSbG91A1QaQhQQqjn#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Jupiter Perpetuals", + account_id: "PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu", + solscan_url: "https://solscan.io/account/PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Jupiter Prediction Market", + account_id: "3ZZuTbwC6aJbvteyVxXUS7gtFYdf7AuXeitx6VyvjvUp", + solscan_url: "https://solscan.io/account/3ZZuTbwC6aJbvteyVxXUS7gtFYdf7AuXeitx6VyvjvUp", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Jupiter Stable Program", + account_id: "JUPUSDecMzAVgztLe6eGhwUBj1Pn3j9WAXwmtHmfbRr", + solscan_url: "https://solscan.io/account/JUPUSDecMzAVgztLe6eGhwUBj1Pn3j9WAXwmtHmfbRr#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Jup Studio: Authority", + account_id: "8rE9CtCjwhSmbwL5fbJBtRFsS3ohfMcDFeTTC7t4ciUA", + solscan_url: "https://solscan.io/account/8rE9CtCjwhSmbwL5fbJBtRFsS3ohfMcDFeTTC7t4ciUA", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Kamino Program", + account_id: "6LtLpnUFNByNXLyCoK9wA2MykKAmQNZKBdY8s47dehDc", + solscan_url: "https://solscan.io/account/6LtLpnUFNByNXLyCoK9wA2MykKAmQNZKBdY8s47dehDc#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Kamino Lending Program", + account_id: "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD", + solscan_url: "https://solscan.io/account/KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Kamino Farm", + account_id: "FarmsPZpWu9i7Kky8tPN37rs2TpmMrAZrC7S7vJa91Hr", + solscan_url: "https://solscan.io/account/FarmsPZpWu9i7Kky8tPN37rs2TpmMrAZrC7S7vJa91Hr#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Kamino Vault Program", + account_id: "kvauTFR8qm1dhniz6pYuBZkuene3Hfrs1VQhVRgCNrr", + solscan_url: "https://solscan.io/account/kvauTFR8qm1dhniz6pYuBZkuene3Hfrs1VQhVRgCNrr#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Kvault Program", + account_id: "KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd", + solscan_url: "https://solscan.io/account/KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "LayerZero Endpoint", + account_id: "76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6", + solscan_url: "https://solscan.io/account/76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "LayerZero Executor", + account_id: "6doghB248px58JSSwG4qejQ46kFMW4AMj7vzJnWZHNZn", + solscan_url: "https://solscan.io/account/6doghB248px58JSSwG4qejQ46kFMW4AMj7vzJnWZHNZn#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "letsbonk.fun: PlatformConfig", + account_id: "FfYek5vEz23cMkWsdJwG2oa6EphsvXSHrGpdALN4g6W1", + solscan_url: "https://solscan.io/account/FfYek5vEz23cMkWsdJwG2oa6EphsvXSHrGpdALN4g6W1", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Lifinity Swap", + account_id: "EewxydAPCCVuNEyrVN68PuSYdQ7wKn27V9Gjeoi8dy3S", + solscan_url: "https://solscan.io/account/EewxydAPCCVuNEyrVN68PuSYdQ7wKn27V9Gjeoi8dy3S", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Lifinity Swap V2", + account_id: "2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c", + solscan_url: "https://solscan.io/account/2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Manifest", + account_id: "MNFSTqtC93rEfYHB6hF82sKdZpUDFWkViLByLd1k1Ms", + solscan_url: "https://solscan.io/account/MNFSTqtC93rEfYHB6hF82sKdZpUDFWkViLByLd1k1Ms", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Marcopolo Swap", + account_id: "9tKE7Mbmj4mxDjWatikzGAtkoWosiiZX9y6J4Hfm2R8H", + solscan_url: "https://solscan.io/account/9tKE7Mbmj4mxDjWatikzGAtkoWosiiZX9y6J4Hfm2R8H", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Marginfi", + account_id: "MFLQPPPPjNinkdKoy2odNFBhvpY43XtCDZjBwG2fwn5", + solscan_url: "https://solscan.io/account/MFLQPPPPjNinkdKoy2odNFBhvpY43XtCDZjBwG2fwn5", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Marginfi V2", + account_id: "MFv2hWf31Z9kbCa1snEPYctwafyhdvnV7FZnsebVacA", + solscan_url: "https://solscan.io/account/MFv2hWf31Z9kbCa1snEPYctwafyhdvnV7FZnsebVacA#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Marinade Finance", + account_id: "MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD", + solscan_url: "https://solscan.io/account/MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Mercurial Stable Swap", + account_id: "MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky", + solscan_url: "https://solscan.io/account/MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "MetaDAO AMM Program v0.5", + account_id: "AMMJdEiCCa8mdugg6JPF7gFirmmxisTfDJoSNSUi5zDJ", + solscan_url: "https://solscan.io/account/AMMJdEiCCa8mdugg6JPF7gFirmmxisTfDJoSNSUi5zDJ#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "MetaDAO Bid Wall Program", + account_id: "WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx", + solscan_url: "https://solscan.io/account/WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "MetaDAO ICO", + account_id: "moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM", + solscan_url: "https://solscan.io/account/moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Metaplex Token Metadata", + account_id: "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s", + solscan_url: "https://solscan.io/account/metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Meteora DLMM Program", + account_id: "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo", + solscan_url: "https://solscan.io/account/LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Meteora Pools Program", + account_id: "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB", + solscan_url: "https://solscan.io/account/Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Meteora Dynamic Bonding Curve", + account_id: "dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN", + solscan_url: "https://solscan.io/account/dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Meteora DAMM v2", + account_id: "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG", + solscan_url: "https://solscan.io/account/cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Meteora Vault Program", + account_id: "24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi", + solscan_url: "https://solscan.io/account/24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Moonit", + account_id: "MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG", + solscan_url: "https://solscan.io/account/MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Moonshot : Token Authority", + account_id: "7rtiKSUDLBm59b1SBmD9oajcP8xE64vAGSMbAN5CXy1q", + solscan_url: "https://solscan.io/account/7rtiKSUDLBm59b1SBmD9oajcP8xE64vAGSMbAN5CXy1q", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Mpl Core", + account_id: "CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d", + solscan_url: "https://solscan.io/account/CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Name Service Program", + account_id: "namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX", + solscan_url: "https://solscan.io/account/namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Numeraire", + account_id: "NUMERUNsFCP3kuNmWZuXtm1AaQCPj9uw6Guv2Ekoi5P", + solscan_url: "https://solscan.io/account/NUMERUNsFCP3kuNmWZuXtm1AaQCPj9uw6Guv2Ekoi5P#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Obric V2", + account_id: "obriQD1zbpyLz95G5n7nJe6a4DPjpFwa5XYPoNm113y", + solscan_url: "https://solscan.io/account/obriQD1zbpyLz95G5n7nJe6a4DPjpFwa5XYPoNm113y", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "OKX Labs 1", + account_id: "6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma", + solscan_url: "https://solscan.io/account/6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "OKX: DEX Router", + account_id: "proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u", + solscan_url: "https://solscan.io/account/proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Ondo Global Markets", + account_id: "XzTT4XB8m7sLD2xi6snefSasaswsKCxx5Tifjondogm", + solscan_url: "https://solscan.io/account/XzTT4XB8m7sLD2xi6snefSasaswsKCxx5Tifjondogm#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Openbook V2", + account_id: "opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb", + solscan_url: "https://solscan.io/account/opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Orca Whirlpools Program", + account_id: "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc", + solscan_url: "https://solscan.io/account/whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Orca Wavebreak", + account_id: "waveQX2yP3H1pVU8djGvEHmYg8uamQ84AuyGtpsrXTF", + solscan_url: "https://solscan.io/account/waveQX2yP3H1pVU8djGvEHmYg8uamQ84AuyGtpsrXTF#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Orca Token Swap", + account_id: "DjVE6JNiYqPL2QXyCUUh8rNjHrbz9hXHNYt99MQ59qw1", + solscan_url: "https://solscan.io/account/DjVE6JNiYqPL2QXyCUUh8rNjHrbz9hXHNYt99MQ59qw1", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Orca Token Swap V2", + account_id: "9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP", + solscan_url: "https://solscan.io/account/9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Ore V3 Program", + account_id: "oreV3EG1i9BEgiAJ8b177Z2S2rMarzak4NMv1kULvWv", + solscan_url: "https://solscan.io/account/oreV3EG1i9BEgiAJ8b177Z2S2rMarzak4NMv1kULvWv", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "PancakeSwap", + account_id: "HpNfyc2Saw7RKkQd8nEL4khUcuPhQ7WwY1B2qjx8jxFq", + solscan_url: "https://solscan.io/account/HpNfyc2Saw7RKkQd8nEL4khUcuPhQ7WwY1B2qjx8jxFq#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Penguin Finance", + account_id: "PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP", + solscan_url: "https://solscan.io/account/PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Phoenix", + account_id: "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY", + solscan_url: "https://solscan.io/account/PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Printr", + account_id: "T8HsGYv7sMk3kTnyaRqZrbRPuntYzdh12evXBkprint", + solscan_url: "https://solscan.io/account/T8HsGYv7sMk3kTnyaRqZrbRPuntYzdh12evXBkprint#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Pump.fun", + account_id: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", + solscan_url: "https://solscan.io/account/6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Pump.fun AMM", + account_id: "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", + solscan_url: "https://solscan.io/account/pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Pump Fees Program", + account_id: "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ", + solscan_url: "https://solscan.io/account/pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "pumpup.ai", + account_id: "PdMDrKEMaX8q7CCJb7NvUCxerBCcsFUa4LjBEynTtEd", + solscan_url: "https://solscan.io/account/PdMDrKEMaX8q7CCJb7NvUCxerBCcsFUa4LjBEynTtEd#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Raydium AMM Routing", + account_id: "routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS", + solscan_url: "https://solscan.io/account/routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Raydium Concentrated Liquidity", + account_id: "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK", + solscan_url: "https://solscan.io/account/CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Raydium CPMM", + account_id: "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C", + solscan_url: "https://solscan.io/account/CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Raydium LaunchLab", + account_id: "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj", + solscan_url: "https://solscan.io/account/LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Raydium LaunchLab: PlatformConfig", + account_id: "4Bu96XjU84XjPDSpveTVf6LYGCkfW5FK7SNkREWcEfV4", + solscan_url: "https://solscan.io/account/4Bu96XjU84XjPDSpveTVf6LYGCkfW5FK7SNkREWcEfV4", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Raydium liquidity pool AMM", + account_id: "5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h", + solscan_url: "https://solscan.io/account/5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Raydium Liquidity Pool V2", + account_id: "RVKd61ztZW9GUwhRbbLoYVRE5Xf1B2tVscKqwZqXgEr", + solscan_url: "https://solscan.io/account/RVKd61ztZW9GUwhRbbLoYVRE5Xf1B2tVscKqwZqXgEr", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Raydium Liquidity Pool V3", + account_id: "27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv", + solscan_url: "https://solscan.io/account/27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Raydium Liquidity Pool V4", + account_id: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", + solscan_url: "https://solscan.io/account/675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Raydium Lock LP", + account_id: "LockrWmn6K5twhz3y9w1dQERbmgSaRkfnTeTKbpofwE", + solscan_url: "https://solscan.io/account/LockrWmn6K5twhz3y9w1dQERbmgSaRkfnTeTKbpofwE#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Saber Decimal Wrapper", + account_id: "DecZY86MU5Gj7kppfUCEmd4LbXXuyZH1yHaP2NTqdiZB", + solscan_url: "https://solscan.io/account/DecZY86MU5Gj7kppfUCEmd4LbXXuyZH1yHaP2NTqdiZB#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Saber Stable Swap", + account_id: "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + solscan_url: "https://solscan.io/account/SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Saros AMM", + account_id: "SSwapUtytfBdBn1b9NUGG6foMVPtcWgpRU32HToDUZr", + solscan_url: "https://solscan.io/account/SSwapUtytfBdBn1b9NUGG6foMVPtcWgpRU32HToDUZr", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Sanctum Multi-Validator SPL Stake Pool Program", + account_id: "SPMBzsVUuoHA4Jm6KunbsotaahvVikZs1JyTW6iJvbn", + solscan_url: "https://solscan.io/account/SPMBzsVUuoHA4Jm6KunbsotaahvVikZs1JyTW6iJvbn", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Sanctum Router Program", + account_id: "stkitrT1Uoy18Dk1fTrgPw8W6MVzoCfYoAFT4MLsmhq", + solscan_url: "https://solscan.io/account/stkitrT1Uoy18Dk1fTrgPw8W6MVzoCfYoAFT4MLsmhq", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Sanctum: S Controller", + account_id: "5ocnV1qiCgaQR8Jb8xWnVbApfaygJ8tNoZfgPwsgx9kx", + solscan_url: "https://solscan.io/account/5ocnV1qiCgaQR8Jb8xWnVbApfaygJ8tNoZfgPwsgx9kx", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Sanctum Single Validator SPL Stake Pool Program", + account_id: "SP12tWFxD9oJsVWNavTTBZvMbA6gkAmxtVgxdqvyvhY", + solscan_url: "https://solscan.io/account/SP12tWFxD9oJsVWNavTTBZvMbA6gkAmxtVgxdqvyvhY", + idl_status: "solscan_account", + }, + SolscanAccountSource { + label: "SharkyFi", + account_id: "SHARKobtfF1bHhxD2eqftjHBdVSCbKo9JtgK71FhELP", + solscan_url: "https://solscan.io/account/SHARKobtfF1bHhxD2eqftjHBdVSCbKo9JtgK71FhELP", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Solayer", + account_id: "sSo1iU21jBrU9VaJ8PJib1MtorefUV4fzC9GURa2KNn", + solscan_url: "https://solscan.io/account/sSo1iU21jBrU9VaJ8PJib1MtorefUV4fzC9GURa2KNn", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Solend Protocol", + account_id: "So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo", + solscan_url: "https://solscan.io/account/So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "SolFi", + account_id: "SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe", + solscan_url: "https://solscan.io/account/SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "SolFi V2", + account_id: "SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF", + solscan_url: "https://solscan.io/account/SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "stabble CLMM", + account_id: "6dMXqGZ3ga2dikrYS9ovDXgHGh5RUsb2RTUj6hrQXhk6", + solscan_url: "https://solscan.io/account/6dMXqGZ3ga2dikrYS9ovDXgHGh5RUsb2RTUj6hrQXhk6#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "stabble Stable Swap", + account_id: "swapNyd8XiQwJ6ianp9snpu4brUqFxadzvHebnAXjJZ", + solscan_url: "https://solscan.io/account/swapNyd8XiQwJ6ianp9snpu4brUqFxadzvHebnAXjJZ#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "stabble Weighted Swap", + account_id: "swapFpHZwjELNnjvThjajtiVmkz3yPQEHjLtka2fwHW", + solscan_url: "https://solscan.io/account/swapFpHZwjELNnjvThjajtiVmkz3yPQEHjLtka2fwHW#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Stake Pool", + account_id: "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy", + solscan_url: "https://solscan.io/account/SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Stake Program", + account_id: "Stake11111111111111111111111111111111111111", + solscan_url: "https://solscan.io/account/Stake11111111111111111111111111111111111111", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Step Finance Swap Program", + account_id: "SSwpMgqNDsyV7mAgN9ady4bDVu5ySjmmXejXvy2vLt1", + solscan_url: "https://solscan.io/account/SSwpMgqNDsyV7mAgN9ady4bDVu5ySjmmXejXvy2vLt1", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "StepN DOOAR Swap", + account_id: "Dooar9JkhdZ7J3LHN3A7YCuoGRUggXhQaG4kijfLGU2j", + solscan_url: "https://solscan.io/account/Dooar9JkhdZ7J3LHN3A7YCuoGRUggXhQaG4kijfLGU2j", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Scorch", + account_id: "SCoRcH8c2dpjvcJD6FiPbCSQyQgu3PcUAWj2Xxx3mqn", + solscan_url: "https://solscan.io/account/SCoRcH8c2dpjvcJD6FiPbCSQyQgu3PcUAWj2Xxx3mqn", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Streamflow", + account_id: "strmRqUCoQUgGUan5YhzUZa6KqdzwX5L6FpUxfmKg5m", + solscan_url: "https://solscan.io/account/strmRqUCoQUgGUan5YhzUZa6KqdzwX5L6FpUxfmKg5m#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Swap Program", + account_id: "SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8", + solscan_url: "https://solscan.io/account/SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Swig Program", + account_id: "swigypWHEksbC64pWKwah1WTeh9JXwx8H1rJHLdbQMB", + solscan_url: "https://solscan.io/account/swigypWHEksbC64pWKwah1WTeh9JXwx8H1rJHLdbQMB", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "System Program", + account_id: "11111111111111111111111111111111", + solscan_url: "https://solscan.io/account/11111111111111111111111111111111", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Tessera V", + account_id: "TessVdML9pBGgG9yGks7o4HewRaXVAMuoVj4x83GLQH", + solscan_url: "https://solscan.io/account/TessVdML9pBGgG9yGks7o4HewRaXVAMuoVj4x83GLQH", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Titan Exchange Router", + account_id: "T1TANpTeScyeqVzzgNViGDNrkQ6qHz9KrSBS4aNXvGT", + solscan_url: "https://solscan.io/account/T1TANpTeScyeqVzzgNViGDNrkQ6qHz9KrSBS4aNXvGT", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Token 2022 Program", + account_id: "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + solscan_url: "https://solscan.io/account/TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Token Program", + account_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + solscan_url: "https://solscan.io/account/TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Vertigo Program", + account_id: "vrTGoBuy5rYSxAfV3jaRJWHH6nN9WK4NRExGxsk1bCJ", + solscan_url: "https://solscan.io/account/vrTGoBuy5rYSxAfV3jaRJWHH6nN9WK4NRExGxsk1bCJ#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Virtuals", + account_id: "5U3EU2ubXtK84QcRjWVmYt9RaDyA8gKxdUrPFXmZyaki", + solscan_url: "https://solscan.io/account/5U3EU2ubXtK84QcRjWVmYt9RaDyA8gKxdUrPFXmZyaki#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Woofi", + account_id: "WooFif76YGRNjk1pA8wCsN67aQsD9f9iLsz4NcJ1AVb", + solscan_url: "https://solscan.io/account/WooFif76YGRNjk1pA8wCsN67aQsD9f9iLsz4NcJ1AVb#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Wormhole: Bridge", + account_id: "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb", + solscan_url: "https://solscan.io/account/wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "ZeroFi", + account_id: "ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY", + solscan_url: "https://solscan.io/account/ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Zeta Matching Engine", + account_id: "zDEXqXEG7gAyxb1Kg9mK5fPnUdENCGKzWrM21RMdWRq", + solscan_url: "https://solscan.io/account/zDEXqXEG7gAyxb1Kg9mK5fPnUdENCGKzWrM21RMdWRq", + idl_status: "no_idl", + }, + SolscanAccountSource { + label: "Zeta Program", + account_id: "ZETAxsqBRek56DhiGXrn75yj2NHU3aYUnxvHXpkf3aD", + solscan_url: "https://solscan.io/account/ZETAxsqBRek56DhiGXrn75yj2NHU3aYUnxvHXpkf3aD#programIdl", + idl_status: "solscan_program_idl", + }, + SolscanAccountSource { + label: "Zora Program", + account_id: "zoRabwLGd5zXaV7Gxacppw8tcceXEiTrSKyNLSaSTUc", + solscan_url: "https://solscan.io/account/zoRabwLGd5zXaV7Gxacppw8tcceXEiTrSKyNLSaSTUc", + idl_status: "no_idl", + }, +]; diff --git a/kb_lib/src/db.rs b/kb_lib/src/db.rs index 18d6ffd..38b8d92 100644 --- a/kb_lib/src/db.rs +++ b/kb_lib/src/db.rs @@ -78,6 +78,7 @@ pub use dtos::ProtocolCandidateDto; pub use dtos::ProtocolCandidateSummaryDto; pub use dtos::RewardEventDto; pub use dtos::SwapDto; +pub use dtos::TokenAccountEventDto; pub use dtos::TokenBurnEventDto; pub use dtos::TokenDto; pub use dtos::TokenMintEventDto; @@ -124,6 +125,7 @@ pub use entities::ProtocolCandidateEntity; pub use entities::ProtocolCandidateSummaryEntity; pub use entities::RewardEventEntity; pub use entities::SwapEntity; +pub use entities::TokenAccountEventEntity; pub use entities::TokenBurnEventEntity; pub use entities::TokenEntity; pub use entities::TokenMintEventEntity; @@ -155,6 +157,7 @@ pub use queries::query_dex_decode_replay_ledger_get_by_transaction; pub use queries::query_dex_decode_replay_ledger_upsert; pub use queries::query_dex_decoded_events_delete_by_key; pub use queries::query_dex_decoded_events_delete_locally_covered_upstream_instruction_matches; +pub use queries::query_dex_decoded_events_delete_replaced_raydium_cpmm_instruction_audits; pub use queries::query_dex_decoded_events_delete_meteora_dlmm_anchor_swap_instruction_audits; pub use queries::query_dex_decoded_events_delete_related_instruction_audit; pub use queries::query_dex_decoded_events_delete_replaced_raydium_clmm_instruction_audits; @@ -264,6 +267,7 @@ pub use queries::query_reward_events_list_recent; pub use queries::query_reward_events_upsert; pub use queries::query_swaps_list_recent; pub use queries::query_swaps_upsert; +pub use queries::query_token_account_events_upsert; pub use queries::query_token_burn_events_list_recent; pub use queries::query_token_burn_events_upsert; pub use queries::query_token_mint_events_list_recent; diff --git a/kb_lib/src/db/dtos.rs b/kb_lib/src/db/dtos.rs index 7cafdb0..f718063 100644 --- a/kb_lib/src/db/dtos.rs +++ b/kb_lib/src/db/dtos.rs @@ -42,6 +42,7 @@ mod protocol_candidate_summary; mod reward_event; mod swap; mod token; +mod token_account_event; mod token_burn_event; mod token_mint_event; mod trade_event; @@ -136,6 +137,7 @@ pub use protocol_candidate_summary::ProtocolCandidateSummaryDto; pub use reward_event::RewardEventDto; pub use swap::SwapDto; pub use token::TokenDto; +pub use token_account_event::TokenAccountEventDto; pub use token_burn_event::TokenBurnEventDto; pub use token_mint_event::TokenMintEventDto; pub use trade_event::TradeEventDto; diff --git a/kb_lib/src/db/dtos/token_account_event.rs b/kb_lib/src/db/dtos/token_account_event.rs new file mode 100644 index 0000000..797505f --- /dev/null +++ b/kb_lib/src/db/dtos/token_account_event.rs @@ -0,0 +1,140 @@ +// file: kb_lib/src/db/dtos/token_account_event.rs + +//! Token account event DTO. + +/// Application-facing normalized token-account event DTO. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct TokenAccountEventDto { + /// Optional numeric primary key. + pub id: std::option::Option, + /// Related transaction id. + pub transaction_id: std::option::Option, + /// Related decoded event id. + pub decoded_event_id: std::option::Option, + /// Related DEX id. + pub dex_id: std::option::Option, + /// Related pool id, when known. + pub pool_id: std::option::Option, + /// Related pair id, when known. + pub pair_id: std::option::Option, + /// Transaction signature. + pub signature: std::string::String, + /// Instruction index inside the transaction. + pub instruction_index: i64, + /// Optional slot number. + pub slot: std::option::Option, + /// Protocol name. + pub protocol_name: std::string::String, + /// Program id. + pub program_id: std::option::Option, + /// Event kind. + pub event_kind: std::string::String, + /// Token account, when known. + pub token_account: std::option::Option, + /// Token mint, when known. + pub token_mint: std::option::Option, + /// Owner wallet, when known. + pub owner_wallet: std::option::Option, + /// Token-account action. + pub account_action: std::option::Option, + /// Raw decoded payload. + pub payload_json: std::option::Option, + /// Execution timestamp. + pub executed_at: chrono::DateTime, +} + +impl TokenAccountEventDto { + /// Creates a new token-account event DTO. + #[allow(clippy::too_many_arguments)] + pub fn new( + transaction_id: std::option::Option, + decoded_event_id: std::option::Option, + dex_id: std::option::Option, + pool_id: std::option::Option, + pair_id: std::option::Option, + signature: std::string::String, + instruction_index: i64, + slot: std::option::Option, + protocol_name: std::string::String, + program_id: std::option::Option, + event_kind: std::string::String, + token_account: std::option::Option, + token_mint: std::option::Option, + owner_wallet: std::option::Option, + account_action: std::option::Option, + payload_json: std::option::Option, + ) -> Self { + return Self { + id: None, + transaction_id, + decoded_event_id, + dex_id, + pool_id, + pair_id, + signature, + instruction_index, + slot, + protocol_name, + program_id, + event_kind, + token_account, + token_mint, + owner_wallet, + account_action, + payload_json, + executed_at: chrono::Utc::now(), + }; + } +} + +impl TryFrom for TokenAccountEventDto { + type Error = crate::Error; + + fn try_from(entity: crate::TokenAccountEventEntity) -> Result { + let executed_at_result = chrono::DateTime::parse_from_rfc3339(&entity.executed_at); + let executed_at = match executed_at_result { + Ok(executed_at) => executed_at.with_timezone(&chrono::Utc), + Err(error) => { + return Err(crate::Error::Db(format!( + "cannot parse token account event executed_at '{}': {}", + entity.executed_at, error + ))); + }, + }; + let slot = match entity.slot { + Some(slot) => { + let slot_result = u64::try_from(slot); + match slot_result { + Ok(slot) => Some(slot), + Err(error) => { + return Err(crate::Error::Db(format!( + "cannot convert token account event slot '{}' to u64: {}", + slot, error + ))); + }, + } + }, + None => None, + }; + return Ok(Self { + id: Some(entity.id), + transaction_id: entity.transaction_id, + decoded_event_id: entity.decoded_event_id, + dex_id: entity.dex_id, + pool_id: entity.pool_id, + pair_id: entity.pair_id, + signature: entity.signature, + instruction_index: entity.instruction_index, + slot, + protocol_name: entity.protocol_name, + program_id: entity.program_id, + event_kind: entity.event_kind, + token_account: entity.token_account, + token_mint: entity.token_mint, + owner_wallet: entity.owner_wallet, + account_action: entity.account_action, + payload_json: entity.payload_json, + executed_at, + }); + } +} diff --git a/kb_lib/src/db/entities.rs b/kb_lib/src/db/entities.rs index d705364..286835c 100644 --- a/kb_lib/src/db/entities.rs +++ b/kb_lib/src/db/entities.rs @@ -42,6 +42,7 @@ mod protocol_candidate_summary; mod reward_event; mod swap; mod token; +mod token_account_event; mod token_burn_event; mod token_mint_event; mod trade_event; @@ -89,6 +90,7 @@ pub use protocol_candidate_summary::ProtocolCandidateSummaryEntity; pub use reward_event::RewardEventEntity; pub use swap::SwapEntity; pub use token::TokenEntity; +pub use token_account_event::TokenAccountEventEntity; pub use token_burn_event::TokenBurnEventEntity; pub use token_mint_event::TokenMintEventEntity; pub use trade_event::TradeEventEntity; diff --git a/kb_lib/src/db/entities/token_account_event.rs b/kb_lib/src/db/entities/token_account_event.rs new file mode 100644 index 0000000..d401094 --- /dev/null +++ b/kb_lib/src/db/entities/token_account_event.rs @@ -0,0 +1,46 @@ +// file: kb_lib/src/db/entities/token_account_event.rs + +//! Token account event entity. + +/// Persisted normalized token-account event row. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)] +pub struct TokenAccountEventEntity { + /// Numeric primary key. + pub id: i64, + /// Related transaction id. + pub transaction_id: std::option::Option, + /// Related decoded event id. + pub decoded_event_id: std::option::Option, + /// Related DEX id. + pub dex_id: std::option::Option, + /// Related pool id, when known. + pub pool_id: std::option::Option, + /// Related pair id, when known. + pub pair_id: std::option::Option, + /// Transaction signature. + pub signature: std::string::String, + /// Instruction index inside the transaction. + pub instruction_index: i64, + /// Optional slot number. + pub slot: std::option::Option, + /// Protocol name. + pub protocol_name: std::string::String, + /// Program id. + pub program_id: std::option::Option, + /// Event kind. + pub event_kind: std::string::String, + /// Token account, when known. + pub token_account: std::option::Option, + /// Token mint, when known. + pub token_mint: std::option::Option, + /// Owner wallet, when known. + pub owner_wallet: std::option::Option, + /// Token-account action. + pub account_action: std::option::Option, + /// Raw decoded payload. + pub payload_json: std::option::Option, + /// Execution timestamp encoded as RFC3339 UTC text. + pub executed_at: std::string::String, + /// Creation timestamp encoded as RFC3339 UTC text. + pub created_at: std::option::Option, +} diff --git a/kb_lib/src/db/queries.rs b/kb_lib/src/db/queries.rs index 1cdb913..69f955f 100644 --- a/kb_lib/src/db/queries.rs +++ b/kb_lib/src/db/queries.rs @@ -40,6 +40,7 @@ mod protocol_candidate; mod reward_event; mod swap; mod token; +mod token_account_event; mod token_burn_event; mod token_mint_event; mod trade_event; @@ -77,6 +78,7 @@ pub use dex_decoded_event::query_dex_decoded_events_delete_locally_covered_upstr pub use dex_decoded_event::query_dex_decoded_events_delete_meteora_dlmm_anchor_swap_instruction_audits; pub use dex_decoded_event::query_dex_decoded_events_delete_related_instruction_audit; pub use dex_decoded_event::query_dex_decoded_events_delete_replaced_raydium_clmm_instruction_audits; +pub use dex_decoded_event::query_dex_decoded_events_delete_replaced_raydium_cpmm_instruction_audits; pub use dex_decoded_event::query_dex_decoded_events_get_by_key; pub use dex_decoded_event::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint; pub use dex_decoded_event::query_dex_decoded_events_list_by_transaction_id; @@ -185,6 +187,7 @@ pub use token::query_tokens_get_by_mint; pub use token::query_tokens_list; pub use token::query_tokens_list_missing_metadata; pub use token::query_tokens_upsert; +pub use token_account_event::query_token_account_events_upsert; pub use token_burn_event::query_token_burn_events_list_recent; pub use token_burn_event::query_token_burn_events_upsert; pub use token_mint_event::query_token_mint_events_list_recent; diff --git a/kb_lib/src/db/queries/dex_decoded_event.rs b/kb_lib/src/db/queries/dex_decoded_event.rs index b6be598..aaf1488 100644 --- a/kb_lib/src/db/queries/dex_decoded_event.rs +++ b/kb_lib/src/db/queries/dex_decoded_event.rs @@ -191,6 +191,136 @@ WHERE transaction_id = ? } } +/// Deletes Raydium CPMM instruction-audit rows for locally mapped CPMM instructions. +/// +/// CPMM has one Anchor IDL-management instruction (`40f4bc78a7e9690a`) that is +/// decoded by the local Raydium audit-preservation pass rather than by the +/// CPMM instruction decoder itself because its payload is shorter than normal +/// CPMM instruction layouts. Once the named `raydium_cpmm.*` row exists, the +/// old `raydium_cpmm.instruction_audit` row is redundant and must be removed. +/// The allow-list also includes the regular CPMM instruction discriminators so +/// future replays do not leave duplicate audit rows for already covered entries. +pub async fn query_dex_decoded_events_delete_replaced_raydium_cpmm_instruction_audits( + database: &crate::Database, + transaction_id: std::option::Option, +) -> Result { + match database.connection() { + crate::DatabaseConnection::Sqlite(pool) => { + let unlink_result = sqlx::query( + r#" +UPDATE k_sol_instruction_observations +SET decoded_event_id = NULL +WHERE decoded_event_id IN ( + SELECT id + FROM k_sol_dex_decoded_events + WHERE protocol_name = 'raydium_cpmm' + AND event_kind = 'raydium_cpmm.instruction_audit' + AND (? IS NULL OR transaction_id = ?) + AND ( + COALESCE( + json_extract(payload_json, '$.discriminatorHex'), + json_extract(payload_json, '$.discriminator_hex'), + json_extract(payload_json, '$.instructionDiscriminatorHex'), + json_extract(payload_json, '$.instruction_discriminator_hex') + ) IN ( + '40f4bc78a7e9690a', + '9c5420764587467b', + '1416567bc61cdb84', + 'a78a4e95dfc2067e', + '8888fcddc2427e59', + 'e445a52e51cb9a1d', + '8934edd4d7756c68', + '878802d889a9b5ca', + 'f223c68952e1f2b6', + 'afaf6d1f0d989bed', + '3f37fe4131b25979', + '8fbe5adac41e33de', + '37d96256a34ab4ad', + '313cae889a1c74c8', + '82576c062ee0757b', + 'b712469c946da122' + ) + OR instr(lower(COALESCE(payload_json, '')), '40f4bc78a7e9690a') > 0 + OR EXISTS ( + SELECT 1 + FROM k_sol_dex_decoded_events named + WHERE named.transaction_id = k_sol_dex_decoded_events.transaction_id + AND named.protocol_name = 'raydium_cpmm' + AND named.event_kind = 'raydium_cpmm.anchor_idl_instruction' + ) + ) +) + "#, + ) + .bind(transaction_id) + .bind(transaction_id) + .execute(pool) + .await; + if let Err(error) = unlink_result { + return Err(crate::Error::Db(format!( + "cannot unlink mapped Raydium CPMM instruction audit observations on sqlite: {}", + error + ))); + } + + let query_result = sqlx::query( + r#" +DELETE FROM k_sol_dex_decoded_events +WHERE protocol_name = 'raydium_cpmm' + AND event_kind = 'raydium_cpmm.instruction_audit' + AND (? IS NULL OR transaction_id = ?) + AND ( + COALESCE( + json_extract(payload_json, '$.discriminatorHex'), + json_extract(payload_json, '$.discriminator_hex'), + json_extract(payload_json, '$.instructionDiscriminatorHex'), + json_extract(payload_json, '$.instruction_discriminator_hex') + ) IN ( + '40f4bc78a7e9690a', + '9c5420764587467b', + '1416567bc61cdb84', + 'a78a4e95dfc2067e', + '8888fcddc2427e59', + 'e445a52e51cb9a1d', + '8934edd4d7756c68', + '878802d889a9b5ca', + 'f223c68952e1f2b6', + 'afaf6d1f0d989bed', + '3f37fe4131b25979', + '8fbe5adac41e33de', + '37d96256a34ab4ad', + '313cae889a1c74c8', + '82576c062ee0757b', + 'b712469c946da122' + ) + OR instr(lower(COALESCE(payload_json, '')), '40f4bc78a7e9690a') > 0 + OR EXISTS ( + SELECT 1 + FROM k_sol_dex_decoded_events named + WHERE named.transaction_id = k_sol_dex_decoded_events.transaction_id + AND named.protocol_name = 'raydium_cpmm' + AND named.event_kind = 'raydium_cpmm.anchor_idl_instruction' + ) + ) + "#, + ) + .bind(transaction_id) + .bind(transaction_id) + .execute(pool) + .await; + match query_result { + Ok(result) => return Ok(result.rows_affected()), + Err(error) => { + return Err(crate::Error::Db(format!( + "cannot delete mapped Raydium CPMM instruction audit events on sqlite: {}", + error + ))); + }, + } + }, + } +} + /// Deletes Raydium CLMM instruction-audit rows for locally mapped CLMM instructions. /// /// The CLMM specialized decoder now emits named `raydium_clmm.*` rows for all diff --git a/kb_lib/src/db/queries/dex_event_coverage_entry.rs b/kb_lib/src/db/queries/dex_event_coverage_entry.rs index 1077f52..35c7ac4 100644 --- a/kb_lib/src/db/queries/dex_event_coverage_entry.rs +++ b/kb_lib/src/db/queries/dex_event_coverage_entry.rs @@ -323,7 +323,8 @@ SET ) ) ), - trade_count = ( + trade_count = CASE + WHEN expected_db_target = 'k_sol_trade_events' THEN ( SELECT COUNT(te.id) FROM k_sol_dex_decoded_events de JOIN k_sol_trade_events te ON te.decoded_event_id = de.id @@ -357,7 +358,10 @@ SET ) ) ) - ), + ) + ELSE 0 + END, + materialized_count = CASE WHEN expected_db_target = 'k_sol_trade_events' THEN ( SELECT COUNT(te.id) @@ -604,6 +608,41 @@ SET ) ) ) + WHEN expected_db_target = 'k_sol_launch_events' THEN ( + SELECT COUNT(le.id) + FROM k_sol_dex_decoded_events de + JOIN k_sol_launch_events le ON le.decoded_event_id = de.id + WHERE ( + (k_sol_dex_event_coverage_entries.program_id IS NULL OR de.program_id = k_sol_dex_event_coverage_entries.program_id) + AND ( + ( + k_sol_dex_event_coverage_entries.local_event_kind IS NOT NULL + AND k_sol_dex_event_coverage_entries.local_event_kind <> '' + AND de.event_kind = k_sol_dex_event_coverage_entries.local_event_kind + ) + OR ( + k_sol_dex_event_coverage_entries.entry_name IS NOT NULL + AND ( + json_extract(de.payload_json, '$.upstreamEntryName') = k_sol_dex_event_coverage_entries.entry_name + OR json_extract(de.payload_json, '$.upstreamInstructionName') = k_sol_dex_event_coverage_entries.entry_name + OR json_extract(de.payload_json, '$.upstreamEventName') = k_sol_dex_event_coverage_entries.entry_name + OR json_extract(de.payload_json, '$.entryName') = k_sol_dex_event_coverage_entries.entry_name + ) + ) + OR ( + k_sol_dex_event_coverage_entries.discriminator_hex IS NOT NULL + AND k_sol_dex_event_coverage_entries.discriminator_hex <> '' + AND ( + json_extract(de.payload_json, '$.upstreamDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex + OR json_extract(de.payload_json, '$.instructionDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex + OR json_extract(de.payload_json, '$.anchorEventDiscriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex + OR json_extract(de.payload_json, '$.anchorEventDiscriminator') = k_sol_dex_event_coverage_entries.discriminator_hex + OR json_extract(de.payload_json, '$.discriminatorHex') = k_sol_dex_event_coverage_entries.discriminator_hex + ) + ) + ) + ) + ) ELSE materialized_count END, first_signature = ( diff --git a/kb_lib/src/db/queries/token_account_event.rs b/kb_lib/src/db/queries/token_account_event.rs new file mode 100644 index 0000000..a283f49 --- /dev/null +++ b/kb_lib/src/db/queries/token_account_event.rs @@ -0,0 +1,176 @@ +// file: kb_lib/src/db/queries/token_account_event.rs + +//! Queries for `k_sol_token_account_events`. + +/// Inserts or updates one normalized token-account event row. +pub async fn query_token_account_events_upsert( + database: &crate::Database, + dto: &crate::TokenAccountEventDto, +) -> Result { + let slot_i64 = match dto.slot { + Some(slot) => { + let slot_result = i64::try_from(slot); + match slot_result { + Ok(slot) => Some(slot), + Err(error) => { + return Err(crate::Error::Db(format!( + "cannot convert token account event slot '{}' to i64: {}", + slot, error + ))); + }, + } + }, + None => None, + }; + match database.connection() { + crate::DatabaseConnection::Sqlite(pool) => { + let existing_id = match dto.decoded_event_id { + Some(decoded_event_id) => { + let existing_result = sqlx::query_scalar::( + r#" +SELECT id +FROM k_sol_token_account_events +WHERE decoded_event_id = ? +LIMIT 1 + "#, + ) + .bind(decoded_event_id) + .fetch_optional(pool) + .await; + match existing_result { + Ok(existing_id) => existing_id, + Err(error) => { + return Err(crate::Error::Db(format!( + "cannot fetch k_sol_token_account_events id for decoded_event_id '{}' on sqlite: {}", + decoded_event_id, error + ))); + }, + } + }, + None => None, + }; + if let Some(id) = existing_id { + let update_result = sqlx::query( + r#" +UPDATE k_sol_token_account_events +SET + transaction_id = ?, + dex_id = ?, + pool_id = ?, + pair_id = ?, + signature = ?, + instruction_index = ?, + slot = ?, + protocol_name = ?, + program_id = ?, + event_kind = ?, + token_account = ?, + token_mint = ?, + owner_wallet = ?, + account_action = ?, + payload_json = ?, + executed_at = ? +WHERE id = ? + "#, + ) + .bind(dto.transaction_id) + .bind(dto.dex_id) + .bind(dto.pool_id) + .bind(dto.pair_id) + .bind(dto.signature.clone()) + .bind(dto.instruction_index) + .bind(slot_i64) + .bind(dto.protocol_name.clone()) + .bind(dto.program_id.clone()) + .bind(dto.event_kind.clone()) + .bind(dto.token_account.clone()) + .bind(dto.token_mint.clone()) + .bind(dto.owner_wallet.clone()) + .bind(dto.account_action.clone()) + .bind(dto.payload_json.clone()) + .bind(dto.executed_at.to_rfc3339()) + .bind(id) + .execute(pool) + .await; + if let Err(error) = update_result { + return Err(crate::Error::Db(format!( + "cannot update k_sol_token_account_events id '{}' on sqlite: {}", + id, error + ))); + } + return Ok(id); + } + let query_result = sqlx::query( + r#" +INSERT INTO k_sol_token_account_events ( + transaction_id, + decoded_event_id, + dex_id, + pool_id, + pair_id, + signature, + instruction_index, + slot, + protocol_name, + program_id, + event_kind, + token_account, + token_mint, + owner_wallet, + account_action, + payload_json, + executed_at, + created_at +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + "#, + ) + .bind(dto.transaction_id) + .bind(dto.decoded_event_id) + .bind(dto.dex_id) + .bind(dto.pool_id) + .bind(dto.pair_id) + .bind(dto.signature.clone()) + .bind(dto.instruction_index) + .bind(slot_i64) + .bind(dto.protocol_name.clone()) + .bind(dto.program_id.clone()) + .bind(dto.event_kind.clone()) + .bind(dto.token_account.clone()) + .bind(dto.token_mint.clone()) + .bind(dto.owner_wallet.clone()) + .bind(dto.account_action.clone()) + .bind(dto.payload_json.clone()) + .bind(dto.executed_at.to_rfc3339()) + .bind(dto.executed_at.to_rfc3339()) + .execute(pool) + .await; + if let Err(error) = query_result { + return Err(crate::Error::Db(format!( + "cannot insert k_sol_token_account_events on sqlite: {}", + error + ))); + } + let id_result = sqlx::query_scalar::( + r#" +SELECT id +FROM k_sol_token_account_events +WHERE decoded_event_id = ? +LIMIT 1 + "#, + ) + .bind(dto.decoded_event_id) + .fetch_one(pool) + .await; + match id_result { + Ok(id) => return Ok(id), + Err(error) => { + return Err(crate::Error::Db(format!( + "cannot fetch k_sol_token_account_events id for decoded_event_id '{:?}' on sqlite: {}", + dto.decoded_event_id, error + ))); + }, + } + }, + } +} diff --git a/kb_lib/src/db/schema.rs b/kb_lib/src/db/schema.rs index e01ba08..952fb14 100644 --- a/kb_lib/src/db/schema.rs +++ b/kb_lib/src/db/schema.rs @@ -390,6 +390,42 @@ pub(crate) async fn ensure_schema(database: &crate::Database) -> Result<(), crat if let Err(error) = result { return Err(error); } + let result = create_tbl_token_account_events(pool).await; + if let Err(error) = result { + return Err(error); + } + let result = create_idx_token_account_events_transaction_id(pool).await; + if let Err(error) = result { + return Err(error); + } + let result = create_idx_token_account_events_pool_id(pool).await; + if let Err(error) = result { + return Err(error); + } + let result = create_uix_token_account_events_decoded_event_id(pool).await; + if let Err(error) = result { + return Err(error); + } + let result = create_tbl_launch_events(pool).await; + if let Err(error) = result { + return Err(error); + } + let result = create_idx_launch_events_transaction_id(pool).await; + if let Err(error) = result { + return Err(error); + } + let result = create_idx_launch_events_pool_id(pool).await; + if let Err(error) = result { + return Err(error); + } + let result = create_idx_launch_events_event_kind(pool).await; + if let Err(error) = result { + return Err(error); + } + let result = create_uix_launch_events_decoded_event_id(pool).await; + if let Err(error) = result { + return Err(error); + } let result = create_tbl_launch_surfaces(pool).await; if let Err(error) = result { return Err(error); @@ -1490,7 +1526,7 @@ CREATE TABLE IF NOT EXISTS k_sol_instruction_observations ( updated_at TEXT NOT NULL, FOREIGN KEY(transaction_id) REFERENCES k_sol_chain_transactions(id), FOREIGN KEY(instruction_id) REFERENCES k_sol_chain_instructions(id), - FOREIGN KEY(decoded_event_id) REFERENCES k_sol_dex_decoded_events(id) + FOREIGN KEY(decoded_event_id) REFERENCES k_sol_dex_decoded_events(id) ON DELETE SET NULL ) "#, ) @@ -1786,6 +1822,94 @@ ON k_sol_dex_event_coverage_entries (event_family, expected_db_target) .await; } +async fn create_tbl_launch_events(pool: &sqlx::SqlitePool) -> Result<(), crate::Error> { + return execute_sqlite_schema_statement( + pool, + "create_tbl_launch_events", + r#" +CREATE TABLE IF NOT EXISTS k_sol_launch_events ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + transaction_id INTEGER NOT NULL, + decoded_event_id INTEGER NOT NULL UNIQUE, + dex_id INTEGER NULL, + pool_id INTEGER NULL, + pair_id INTEGER NULL, + signature TEXT NOT NULL, + slot INTEGER NULL, + protocol_name TEXT NOT NULL, + program_id TEXT NULL, + event_kind TEXT NOT NULL, + pool_account TEXT NULL, + actor_wallet TEXT NULL, + event_role TEXT NOT NULL, + related_account TEXT NULL, + related_mint TEXT NULL, + payload_json TEXT NOT NULL, + executed_at TEXT NOT NULL, + created_at TEXT NOT NULL, + FOREIGN KEY(transaction_id) REFERENCES k_sol_chain_transactions(id) ON DELETE CASCADE, + FOREIGN KEY(decoded_event_id) REFERENCES k_sol_dex_decoded_events(id) ON DELETE CASCADE, + FOREIGN KEY(dex_id) REFERENCES k_sol_dexes(id) ON DELETE SET NULL, + FOREIGN KEY(pool_id) REFERENCES k_sol_pools(id) ON DELETE SET NULL, + FOREIGN KEY(pair_id) REFERENCES k_sol_pairs(id) ON DELETE SET NULL +) + "#, + ) + .await; +} + +async fn create_idx_launch_events_transaction_id( + pool: &sqlx::SqlitePool, +) -> Result<(), crate::Error> { + return execute_sqlite_schema_statement( + pool, + "create_idx_launch_events_transaction_id", + r#" +CREATE INDEX IF NOT EXISTS idx_launch_events_transaction_id +ON k_sol_launch_events (transaction_id) + "#, + ) + .await; +} + +async fn create_idx_launch_events_pool_id(pool: &sqlx::SqlitePool) -> Result<(), crate::Error> { + return execute_sqlite_schema_statement( + pool, + "create_idx_launch_events_pool_id", + r#" +CREATE INDEX IF NOT EXISTS idx_launch_events_pool_id +ON k_sol_launch_events (pool_id) + "#, + ) + .await; +} + +async fn create_idx_launch_events_event_kind(pool: &sqlx::SqlitePool) -> Result<(), crate::Error> { + return execute_sqlite_schema_statement( + pool, + "create_idx_launch_events_event_kind", + r#" +CREATE INDEX IF NOT EXISTS idx_launch_events_event_kind +ON k_sol_launch_events (event_kind) + "#, + ) + .await; +} + +async fn create_uix_launch_events_decoded_event_id( + pool: &sqlx::SqlitePool, +) -> Result<(), crate::Error> { + return execute_sqlite_schema_statement( + pool, + "create_uix_launch_events_decoded_event_id", + r#" +CREATE UNIQUE INDEX IF NOT EXISTS uix_launch_events_decoded_event_id +ON k_sol_launch_events (decoded_event_id) + "#, + ) + .await; +} + async fn create_tbl_launch_surfaces(pool: &sqlx::SqlitePool) -> Result<(), crate::Error> { return execute_sqlite_schema_statement( pool, @@ -2808,3 +2932,86 @@ WHERE decoded_event_id IS NOT NULL ) .await; } + +/// Creates `k_sol_token_account_events`. +async fn create_tbl_token_account_events(pool: &sqlx::SqlitePool) -> Result<(), crate::Error> { + return execute_sqlite_schema_statement( + pool, + "create_tbl_token_account_events", + r#" +CREATE TABLE IF NOT EXISTS k_sol_token_account_events ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + transaction_id INTEGER NULL, + decoded_event_id INTEGER NULL, + dex_id INTEGER NULL, + pool_id INTEGER NULL, + pair_id INTEGER NULL, + signature TEXT NOT NULL, + instruction_index INTEGER NOT NULL, + slot INTEGER NULL, + protocol_name TEXT NOT NULL, + program_id TEXT NULL, + event_kind TEXT NOT NULL, + token_account TEXT NULL, + token_mint TEXT NULL, + owner_wallet TEXT NULL, + account_action TEXT NULL, + payload_json TEXT NULL, + executed_at TEXT NOT NULL, + created_at TEXT NULL, + FOREIGN KEY(transaction_id) REFERENCES k_sol_chain_transactions(id), + FOREIGN KEY(decoded_event_id) REFERENCES k_sol_dex_decoded_events(id), + FOREIGN KEY(dex_id) REFERENCES k_sol_dexes(id), + FOREIGN KEY(pool_id) REFERENCES k_sol_pools(id), + FOREIGN KEY(pair_id) REFERENCES k_sol_pairs(id) +) + "#, + ) + .await; +} + +/// Creates index on `k_sol_token_account_events(transaction_id)`. +async fn create_idx_token_account_events_transaction_id( + pool: &sqlx::SqlitePool, +) -> Result<(), crate::Error> { + return execute_sqlite_schema_statement( + pool, + "create_idx_token_account_events_transaction_id", + r#" +CREATE INDEX IF NOT EXISTS idx_token_account_events_transaction_id +ON k_sol_token_account_events (transaction_id) + "#, + ) + .await; +} + +/// Creates index on `k_sol_token_account_events(pool_id)`. +async fn create_idx_token_account_events_pool_id( + pool: &sqlx::SqlitePool, +) -> Result<(), crate::Error> { + return execute_sqlite_schema_statement( + pool, + "create_idx_token_account_events_pool_id", + r#" +CREATE INDEX IF NOT EXISTS idx_token_account_events_pool_id +ON k_sol_token_account_events (pool_id) + "#, + ) + .await; +} + +/// Creates unique index on `k_sol_token_account_events(decoded_event_id)`. +async fn create_uix_token_account_events_decoded_event_id( + pool: &sqlx::SqlitePool, +) -> Result<(), crate::Error> { + return execute_sqlite_schema_statement( + pool, + "create_uix_token_account_events_decoded_event_id", + r#" +CREATE UNIQUE INDEX IF NOT EXISTS uix_token_account_events_decoded_event_id +ON k_sol_token_account_events (decoded_event_id) +WHERE decoded_event_id IS NOT NULL + "#, + ) + .await; +} diff --git a/kb_lib/src/dex.rs b/kb_lib/src/dex.rs index 346e53a..496c9bc 100644 --- a/kb_lib/src/dex.rs +++ b/kb_lib/src/dex.rs @@ -16,6 +16,7 @@ mod pump_swap; mod raydium_amm_v4; mod raydium_clmm; mod raydium_cpmm; +pub(crate) mod raydium_launchpad; pub use dexlab::DexlabCreatePoolDecoded; pub use dexlab::DexlabDecodedEvent; @@ -74,8 +75,8 @@ pub use raydium_clmm::RaydiumClmmCollectProtocolFeeDecoded; pub use raydium_clmm::RaydiumClmmCreatePoolDecoded; pub use raydium_clmm::RaydiumClmmDecodedEvent; pub use raydium_clmm::RaydiumClmmDecodedInstructionEvent; -pub use raydium_clmm::RaydiumClmmProgramDataEventDecoded; pub use raydium_clmm::RaydiumClmmDecoder; +pub use raydium_clmm::RaydiumClmmProgramDataEventDecoded; pub use raydium_clmm::RaydiumClmmSwapLegacyDecoded; pub use raydium_clmm::RaydiumClmmSwapV2Decoded; pub use raydium_clmm::decode_raydium_clmm_instruction; diff --git a/kb_lib/src/dex/raydium_cpmm.rs b/kb_lib/src/dex/raydium_cpmm.rs index 1d49afe..9cba6d7 100644 --- a/kb_lib/src/dex/raydium_cpmm.rs +++ b/kb_lib/src/dex/raydium_cpmm.rs @@ -56,6 +56,10 @@ const RAYDIUM_CPMM_UPDATE_POOL_STATUS_DISCRIMINATOR: [u8; 8] = [130, 87, 108, 6, /// Anchor self-CPI log selector used by Raydium CPMM events. const RAYDIUM_CPMM_ANCHOR_SELF_CPI_LOG_SELECTOR: [u8; 8] = [228, 69, 165, 46, 81, 203, 154, 29]; +/// Anchor IDL management discriminator observed on Raydium CPMM (`IdlCreateAccount` / `IdlCloseAccount`). +const RAYDIUM_CPMM_ANCHOR_IDL_INSTRUCTION_DISCRIMINATOR: [u8; 8] = + [64, 244, 188, 120, 167, 233, 105, 10]; + /// Raydium CPMM `LpChangeEvent` Anchor event discriminator. const RAYDIUM_CPMM_LP_CHANGE_EVENT_DISCRIMINATOR: [u8; 8] = [121, 163, 205, 201, 57, 218, 117, 60]; @@ -766,6 +770,9 @@ pub fn classify_raydium_cpmm_instruction_data( if discriminator == RAYDIUM_CPMM_UPDATE_POOL_STATUS_DISCRIMINATOR { return Some("update_pool_status"); } + if discriminator == RAYDIUM_CPMM_ANCHOR_IDL_INSTRUCTION_DISCRIMINATOR { + return Some("anchor_idl_instruction"); + } return None; } @@ -1172,7 +1179,6 @@ fn build_raydium_cpmm_swap( }); } - fn normalize_raydium_cpmm_mints( mint_a: &str, mint_b: &str, diff --git a/kb_lib/src/dex/raydium_launchpad.rs b/kb_lib/src/dex/raydium_launchpad.rs new file mode 100644 index 0000000..1746c9e --- /dev/null +++ b/kb_lib/src/dex/raydium_launchpad.rs @@ -0,0 +1,316 @@ +// file: kb_lib/src/dex/raydium_launchpad.rs + +//! Raydium Launchpad instruction and event helper metadata. +//! +//! The specialized `0.7.50` implementation still decodes Launchpad through +//! the shared persistence-oriented decoder. This module centralizes the stable +//! Launchpad account-layout hints so they do not remain hidden inside the +//! generic Raydium audit fallback. + +/// Static account layout for a locally mapped Raydium Launchpad instruction. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) struct RaydiumLaunchpadAccountLayout { + /// IDL instruction name. + pub(crate) instruction_name: &'static str, + /// Local decoded event kind. + pub(crate) event_kind: &'static str, + /// Minimum projected account count required before applying account hints. + pub(crate) minimum_account_count: usize, + /// Candidate pool-state account index in the projected instruction account list. + pub(crate) pool_account_index: std::option::Option, + /// Candidate base mint account index in the projected instruction account list. + pub(crate) base_mint_index: std::option::Option, + /// Candidate quote mint account index in the projected instruction account list. + pub(crate) quote_mint_index: std::option::Option, + /// Whether this instruction creates a Launchpad pool/bonding curve. + pub(crate) creates_pool: bool, +} + +/// Returns local account-layout hints for one Raydium Launchpad instruction discriminator. +pub(crate) fn account_layout( + discriminator_hex: &str, + account_count: usize, +) -> std::option::Option { + let layout = match discriminator_hex { + "faea0d7bd59c13ec" => RaydiumLaunchpadAccountLayout { + instruction_name: "buy_exact_in", + event_kind: "raydium_launchpad.buy_exact_in", + minimum_account_count: 11, + pool_account_index: Some(4), + base_mint_index: Some(9), + quote_mint_index: Some(10), + creates_pool: false, + }, + "18d3742869039938" => RaydiumLaunchpadAccountLayout { + instruction_name: "buy_exact_out", + event_kind: "raydium_launchpad.buy_exact_out", + minimum_account_count: 11, + pool_account_index: Some(4), + base_mint_index: Some(9), + quote_mint_index: Some(10), + creates_pool: false, + }, + "7bb4b8816fb9bb3b" => RaydiumLaunchpadAccountLayout { + instruction_name: "close_platform_global_access", + event_kind: "raydium_launchpad.close_platform_global_access", + minimum_account_count: 4, + pool_account_index: None, + base_mint_index: None, + quote_mint_index: None, + creates_pool: false, + }, + "a25b92c75d85eaed" => RaydiumLaunchpadAccountLayout { + instruction_name: "create_platform_global_access", + event_kind: "raydium_launchpad.create_platform_global_access", + minimum_account_count: 5, + pool_account_index: None, + base_mint_index: None, + quote_mint_index: None, + creates_pool: false, + }, + "9247ad4562130f6a" => RaydiumLaunchpadAccountLayout { + instruction_name: "create_platform_vesting_account", + event_kind: "raydium_launchpad.create_platform_vesting_account", + minimum_account_count: 6, + pool_account_index: Some(3), + base_mint_index: None, + quote_mint_index: None, + creates_pool: false, + }, + "e445a52e51cb9a1d" => no_account_layout("cpi_event", "raydium_launchpad.cpi_event"), + "1a618acb84ab8dfc" => { + no_account_layout("claim_creator_fee", "raydium_launchpad.claim_creator_fee") + }, + "9c27d0874ced3d48" => RaydiumLaunchpadAccountLayout { + instruction_name: "claim_platform_fee", + event_kind: "raydium_launchpad.claim_platform_fee", + minimum_account_count: 7, + pool_account_index: Some(2), + base_mint_index: None, + quote_mint_index: Some(6), + creates_pool: false, + }, + "75f1c6a8f8da501d" => no_account_layout( + "claim_platform_fee_from_vault", + "raydium_launchpad.claim_platform_fee_from_vault", + ), + "3121681ebd9d4f23" => RaydiumLaunchpadAccountLayout { + instruction_name: "claim_vested_token", + event_kind: "raydium_launchpad.claim_vested_token", + minimum_account_count: 7, + pool_account_index: Some(2), + base_mint_index: Some(6), + quote_mint_index: None, + creates_pool: false, + }, + "3cadf767045d8230" => RaydiumLaunchpadAccountLayout { + instruction_name: "collect_fee", + event_kind: "raydium_launchpad.collect_fee", + minimum_account_count: 6, + pool_account_index: Some(2), + base_mint_index: None, + quote_mint_index: Some(5), + creates_pool: false, + }, + "ffba96dfeb76c9ba" => RaydiumLaunchpadAccountLayout { + instruction_name: "collect_migrate_fee", + event_kind: "raydium_launchpad.collect_migrate_fee", + minimum_account_count: 6, + pool_account_index: Some(2), + base_mint_index: None, + quote_mint_index: Some(5), + creates_pool: false, + }, + "c9cff3724b6f2fbd" => no_account_layout("create_config", "raydium_launchpad.create_config"), + "b05ac4affd71dc14" => { + no_account_layout("create_platform_config", "raydium_launchpad.create_platform_config") + }, + "81b2020dd9ace6da" => RaydiumLaunchpadAccountLayout { + instruction_name: "create_vesting_account", + event_kind: "raydium_launchpad.create_vesting_account", + minimum_account_count: 5, + pool_account_index: Some(2), + base_mint_index: None, + quote_mint_index: None, + creates_pool: false, + }, + "afaf6d1f0d989bed" => initialize_layout("initialize", "raydium_launchpad.initialize"), + "4399af27da102620" => initialize_layout("initialize_v2", "raydium_launchpad.initialize_v2"), + "25be7ede2c9aab11" => initialize_layout( + "initialize_with_token_2022", + "raydium_launchpad.initialize_with_token_2022", + ), + "cf52c091fecf91df" => RaydiumLaunchpadAccountLayout { + instruction_name: "migrate_to_amm", + event_kind: "raydium_launchpad.migrate_to_amm", + minimum_account_count: 28, + pool_account_index: Some(23), + base_mint_index: Some(1), + quote_mint_index: Some(2), + creates_pool: false, + }, + "885cc8671cda908c" => RaydiumLaunchpadAccountLayout { + instruction_name: "migrate_to_cpswap", + event_kind: "raydium_launchpad.migrate_to_cpswap", + minimum_account_count: 22, + pool_account_index: Some(17), + base_mint_index: Some(1), + quote_mint_index: Some(2), + creates_pool: false, + }, + "1b1e3ea95de01891" => no_account_layout( + "remove_platform_curve_param", + "raydium_launchpad.remove_platform_curve_param", + ), + "9527de9bd37c981a" => RaydiumLaunchpadAccountLayout { + instruction_name: "sell_exact_in", + event_kind: "raydium_launchpad.sell_exact_in", + minimum_account_count: 11, + pool_account_index: Some(4), + base_mint_index: Some(9), + quote_mint_index: Some(10), + creates_pool: false, + }, + "5fc8472208090ba6" => RaydiumLaunchpadAccountLayout { + instruction_name: "sell_exact_out", + event_kind: "raydium_launchpad.sell_exact_out", + minimum_account_count: 11, + pool_account_index: Some(4), + base_mint_index: Some(9), + quote_mint_index: Some(10), + creates_pool: false, + }, + "1d9efcbf0a53db63" => no_account_layout("update_config", "raydium_launchpad.update_config"), + "c33c4c81922d438f" => { + no_account_layout("update_platform_config", "raydium_launchpad.update_platform_config") + }, + "8a908afadc800439" => no_account_layout( + "update_platform_curve_param", + "raydium_launchpad.update_platform_curve_param", + ), + _ => return None, + }; + return Some(sanitize_account_layout(layout, account_count)); +} + +fn sanitize_account_layout( + layout: RaydiumLaunchpadAccountLayout, + account_count: usize, +) -> RaydiumLaunchpadAccountLayout { + let pool_account_index = retain_existing_index(layout.pool_account_index, account_count); + let base_mint_index = retain_existing_index(layout.base_mint_index, account_count); + let quote_mint_index = retain_existing_index(layout.quote_mint_index, account_count); + return RaydiumLaunchpadAccountLayout { + instruction_name: layout.instruction_name, + event_kind: layout.event_kind, + minimum_account_count: layout.minimum_account_count, + pool_account_index, + base_mint_index, + quote_mint_index, + creates_pool: layout.creates_pool + && pool_account_index.is_some() + && base_mint_index.is_some() + && quote_mint_index.is_some(), + }; +} + +fn retain_existing_index( + index: std::option::Option, + account_count: usize, +) -> std::option::Option { + let index = match index { + Some(index) => index, + None => return None, + }; + if index < account_count { + return Some(index); + } + return None; +} + +fn no_account_layout( + instruction_name: &'static str, + event_kind: &'static str, +) -> RaydiumLaunchpadAccountLayout { + return RaydiumLaunchpadAccountLayout { + instruction_name, + event_kind, + minimum_account_count: 1, + pool_account_index: None, + base_mint_index: None, + quote_mint_index: None, + creates_pool: false, + }; +} + +fn initialize_layout( + instruction_name: &'static str, + event_kind: &'static str, +) -> RaydiumLaunchpadAccountLayout { + return RaydiumLaunchpadAccountLayout { + instruction_name, + event_kind, + minimum_account_count: 18, + pool_account_index: Some(5), + base_mint_index: Some(6), + quote_mint_index: Some(7), + creates_pool: true, + }; +} + +#[cfg(test)] +mod tests { + #[test] + fn initialize_layout_exposes_carbon_account_order() { + let layout = super::account_layout("afaf6d1f0d989bed", 18); + let layout = match layout { + Some(layout) => layout, + None => panic!("initialize layout must resolve"), + }; + assert_eq!(layout.instruction_name, "initialize"); + assert_eq!(layout.pool_account_index, Some(5)); + assert_eq!(layout.base_mint_index, Some(6)); + assert_eq!(layout.quote_mint_index, Some(7)); + assert!(layout.creates_pool); + } + + #[test] + fn platform_vesting_layout_exposes_pool_state() { + let layout = super::account_layout("9247ad4562130f6a", 6); + let layout = match layout { + Some(layout) => layout, + None => panic!("create_platform_vesting_account layout must resolve"), + }; + assert_eq!(layout.instruction_name, "create_platform_vesting_account"); + assert_eq!(layout.pool_account_index, Some(3)); + } + + #[test] + fn buy_layout_keeps_audit_only_pool_hints() { + let layout = super::account_layout("faea0d7bd59c13ec", 11); + let layout = match layout { + Some(layout) => layout, + None => panic!("buy layout must resolve"), + }; + assert_eq!(layout.event_kind, "raydium_launchpad.buy_exact_in"); + assert_eq!(layout.pool_account_index, Some(4)); + assert_eq!(layout.base_mint_index, Some(9)); + assert_eq!(layout.quote_mint_index, Some(10)); + assert!(!layout.creates_pool); + } + + #[test] + fn known_discriminator_still_maps_when_account_list_is_truncated() { + let layout = super::account_layout("cf52c091fecf91df", 1); + let layout = match layout { + Some(layout) => layout, + None => panic!("migrate_to_amm layout must resolve"), + }; + assert_eq!(layout.instruction_name, "migrate_to_amm"); + assert_eq!(layout.event_kind, "raydium_launchpad.migrate_to_amm"); + assert_eq!(layout.pool_account_index, None); + assert_eq!(layout.base_mint_index, None); + assert_eq!(layout.quote_mint_index, None); + assert!(!layout.creates_pool); + } +} diff --git a/kb_lib/src/dex_catalog.rs b/kb_lib/src/dex_catalog.rs index 623f5a8..17e5d15 100644 --- a/kb_lib/src/dex_catalog.rs +++ b/kb_lib/src/dex_catalog.rs @@ -123,7 +123,7 @@ mod tests { #[test] fn planned_launch_surfaces_are_present_but_disabled() { let codes = [ - "raydium_launchlab", + "raydium_launchpad", "letsbonk", "boop_fun", "moonshot", diff --git a/kb_lib/src/dex_decode.rs b/kb_lib/src/dex_decode.rs index a7647cd..c5ed817 100644 --- a/kb_lib/src/dex_decode.rs +++ b/kb_lib/src/dex_decode.rs @@ -93,11 +93,22 @@ impl DexDecodeService { if let Err(error) = append_result { return Err(error); } + let cleanup_result = + self.cleanup_replaced_raydium_cpmm_instruction_audits(&transaction).await; + if let Err(error) = cleanup_result { + return Err(error); + } let cleanup_result = self.cleanup_replaced_raydium_clmm_instruction_audits(&transaction).await; if let Err(error) = cleanup_result { return Err(error); } + let cleanup_result = self + .cleanup_replaced_raydium_launchpad_anchor_self_cpi_audits(&transaction) + .await; + if let Err(error) = cleanup_result { + return Err(error); + } let append_result = append_persisted_events_result( &mut persisted, self.decode_and_persist_pump_fun_events(&transaction, &instructions).await, @@ -202,11 +213,22 @@ impl DexDecodeService { if let Err(error) = append_result { return Err(error); } + let cleanup_result = + self.cleanup_replaced_raydium_cpmm_instruction_audits(&transaction).await; + if let Err(error) = cleanup_result { + return Err(error); + } let cleanup_result = self.cleanup_replaced_raydium_clmm_instruction_audits(&transaction).await; if let Err(error) = cleanup_result { return Err(error); } + let cleanup_result = self + .cleanup_replaced_raydium_launchpad_anchor_self_cpi_audits(&transaction) + .await; + if let Err(error) = cleanup_result { + return Err(error); + } let reconcile_result = self.reconcile_raydium_clmm_confirmed_non_trade_events(&transaction).await; if let Err(error) = reconcile_result { @@ -215,6 +237,26 @@ impl DexDecodeService { return Ok(persisted); } + async fn cleanup_replaced_raydium_cpmm_instruction_audits( + &self, + transaction: &crate::ChainTransactionDto, + ) -> Result<(), crate::Error> { + let transaction_id = match transaction.id { + Some(transaction_id) => transaction_id, + None => return Ok(()), + }; + let cleanup_result = + crate::query_dex_decoded_events_delete_replaced_raydium_cpmm_instruction_audits( + self.database.as_ref(), + Some(transaction_id), + ) + .await; + match cleanup_result { + Ok(_) => return Ok(()), + Err(error) => return Err(error), + } + } + async fn cleanup_replaced_raydium_clmm_instruction_audits( &self, transaction: &crate::ChainTransactionDto, @@ -641,16 +683,72 @@ WHERE transaction_id = ? if event_kind.ends_with(".instruction_audit") { return Ok(()); } - let audit_event_kind = match instruction_audit_event_kind_by_protocol(protocol_name) { - Some(audit_event_kind) => audit_event_kind, - None => return Ok(()), - }; let discriminator_hex = match instruction_discriminator_hex_from_payload(payload_json) { Some(discriminator_hex) => discriminator_hex, None => return Ok(()), }; + return self + .delete_replaced_instruction_audit_by_discriminator_hex( + transaction_id, + protocol_name, + discriminator_hex.as_str(), + ) + .await; + } + + async fn delete_replaced_instruction_audit_by_discriminator_hex( + &self, + transaction_id: i64, + protocol_name: &str, + discriminator_hex: &str, + ) -> Result<(), crate::Error> { + let audit_event_kind = match instruction_audit_event_kind_by_protocol(protocol_name) { + Some(audit_event_kind) => audit_event_kind, + None => return Ok(()), + }; match self.database.connection() { crate::DatabaseConnection::Sqlite(pool) => { + let unlink_result = sqlx::query( + r#" +UPDATE k_sol_instruction_observations +SET decoded_event_id = NULL +WHERE decoded_event_id IN ( + SELECT id + FROM k_sol_dex_decoded_events + WHERE transaction_id = ? + AND protocol_name = ? + AND event_kind = ? + AND ( + json_extract(payload_json, '$.discriminatorHex') = ? + OR json_extract(payload_json, '$.discriminator_hex') = ? + OR json_extract(payload_json, '$.instructionDiscriminatorHex') = ? + OR json_extract(payload_json, '$.instruction_discriminator_hex') = ? + OR json_extract(payload_json, '$.anchorEventDiscriminatorHex') = ? + OR json_extract(payload_json, '$.anchor_event_discriminator_hex') = ? + OR instr(lower(COALESCE(payload_json, '')), lower(?)) > 0 + ) +) + "#, + ) + .bind(transaction_id) + .bind(protocol_name.to_string()) + .bind(audit_event_kind.to_string()) + .bind(discriminator_hex.to_string()) + .bind(discriminator_hex.to_string()) + .bind(discriminator_hex.to_string()) + .bind(discriminator_hex.to_string()) + .bind(discriminator_hex.to_string()) + .bind(discriminator_hex.to_string()) + .bind(discriminator_hex.to_string()) + .execute(pool) + .await; + if let Err(error) = unlink_result { + return Err(crate::Error::Db(format!( + "cannot unlink replaced instruction audit observation by discriminator on sqlite: {}", + error + ))); + } + let delete_result = sqlx::query( r#" DELETE FROM k_sol_dex_decoded_events @@ -664,18 +762,20 @@ WHERE transaction_id = ? OR json_extract(payload_json, '$.instruction_discriminator_hex') = ? OR json_extract(payload_json, '$.anchorEventDiscriminatorHex') = ? OR json_extract(payload_json, '$.anchor_event_discriminator_hex') = ? + OR instr(lower(COALESCE(payload_json, '')), lower(?)) > 0 ) "#, ) .bind(transaction_id) .bind(protocol_name.to_string()) .bind(audit_event_kind.to_string()) - .bind(discriminator_hex.clone()) - .bind(discriminator_hex.clone()) - .bind(discriminator_hex.clone()) - .bind(discriminator_hex.clone()) - .bind(discriminator_hex.clone()) - .bind(discriminator_hex) + .bind(discriminator_hex.to_string()) + .bind(discriminator_hex.to_string()) + .bind(discriminator_hex.to_string()) + .bind(discriminator_hex.to_string()) + .bind(discriminator_hex.to_string()) + .bind(discriminator_hex.to_string()) + .bind(discriminator_hex.to_string()) .execute(pool) .await; match delete_result { @@ -691,6 +791,98 @@ WHERE transaction_id = ? } } + async fn delete_replaced_raydium_launchpad_anchor_self_cpi_audit( + &self, + transaction_id: i64, + _instruction_id: i64, + anchor_event_discriminator_hex: &str, + ) -> Result<(), crate::Error> { + match self.database.connection() { + crate::DatabaseConnection::Sqlite(pool) => { + let delete_result = sqlx::query( + r#" +DELETE FROM k_sol_dex_decoded_events +WHERE transaction_id = ? + AND protocol_name = 'raydium_launchpad' + AND event_kind = 'raydium_launchpad.instruction_audit' + AND json_extract(payload_json, '$.anchorSelfCpiLog') = 1 + AND json_extract(payload_json, '$.anchorSelfCpiLogSelectorHex') = ? + AND json_extract(payload_json, '$.anchorEventDiscriminatorHex') = ? + "#, + ) + .bind(transaction_id) + .bind(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX.to_string()) + .bind(anchor_event_discriminator_hex.to_string()) + .execute(pool) + .await; + match delete_result { + Ok(_) => return Ok(()), + Err(error) => { + return Err(crate::Error::Db(format!( + "cannot delete replaced Raydium Launchpad self-CPI instruction audit on sqlite: {}", + error + ))); + }, + } + }, + } + } + + async fn cleanup_replaced_raydium_launchpad_anchor_self_cpi_audits( + &self, + transaction: &crate::ChainTransactionDto, + ) -> Result<(), crate::Error> { + let transaction_id = match transaction.id { + Some(transaction_id) => transaction_id, + None => return Ok(()), + }; + match self.database.connection() { + crate::DatabaseConnection::Sqlite(pool) => { + let delete_result = sqlx::query( + r#" +DELETE FROM k_sol_dex_decoded_events +WHERE id IN ( + SELECT audit.id + FROM k_sol_dex_decoded_events audit + WHERE audit.transaction_id = ? + AND audit.protocol_name = 'raydium_launchpad' + AND audit.event_kind = 'raydium_launchpad.instruction_audit' + AND json_extract(audit.payload_json, '$.anchorSelfCpiLog') = 1 + AND json_extract(audit.payload_json, '$.anchorSelfCpiLogSelectorHex') = ? + AND EXISTS ( + SELECT 1 + FROM k_sol_dex_decoded_events direct + WHERE direct.transaction_id = audit.transaction_id + AND direct.protocol_name = 'raydium_launchpad' + AND direct.event_kind IN ( + 'raydium_launchpad.trade_event', + 'raydium_launchpad.pool_create_event', + 'raydium_launchpad.claim_vested_event', + 'raydium_launchpad.create_vesting_event' + ) + AND json_extract(direct.payload_json, '$.anchorEventDiscriminatorHex') = + json_extract(audit.payload_json, '$.anchorEventDiscriminatorHex') + ) +) + "#, + ) + .bind(transaction_id) + .bind(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX.to_string()) + .execute(pool) + .await; + match delete_result { + Ok(_) => return Ok(()), + Err(error) => { + return Err(crate::Error::Db(format!( + "cannot cleanup replaced Raydium Launchpad self-CPI instruction audits for signature '{}': {}", + transaction.signature, error + ))); + }, + } + }, + } + } + async fn delete_replaced_upstream_registry_match( &self, transaction_id: i64, @@ -1783,36 +1975,90 @@ WHERE transaction_id = ? Some(instruction_id) => instruction_id, None => continue, }; - if decoded_instruction_ids.contains(&instruction_id) { - continue; - } let accounts = parse_instruction_accounts_vec(instruction.accounts_json.as_str()); let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref()); - let discriminator_hex = discriminator_hex_from_base58(data_base58.as_deref()); - if raydium_instruction_already_decoded_by_discriminator( - &decoded_discriminator_keys, + let data_bytes = instruction_data_bytes_from_base58(data_base58.as_deref()); + let discriminator_hex = discriminator_hex_from_bytes(data_bytes.as_deref(), 0); + let anchor_event_spec = raydium_launchpad_anchor_self_cpi_event_spec( audit_spec.protocol_name, - discriminator_hex.as_deref(), - ) { + data_bytes.as_deref(), + ); + let dedupe_discriminator_hex = match anchor_event_spec { + Some(anchor_event_spec) => Some(anchor_event_spec.discriminator_hex.to_string()), + None => discriminator_hex.clone(), + }; + if decoded_instruction_ids.contains(&instruction_id) && anchor_event_spec.is_none() { + if let Some(discriminator_hex) = dedupe_discriminator_hex.as_deref() { + let cleanup_result = self + .delete_replaced_instruction_audit_by_discriminator_hex( + transaction_id, + audit_spec.protocol_name, + discriminator_hex, + ) + .await; + if let Err(error) = cleanup_result { + return Err(error); + } + } continue; } - let mapped_spec = raydium_mapped_non_trade_instruction_spec( - audit_spec.protocol_name, - discriminator_hex.as_deref(), - accounts.len(), - ); + if anchor_event_spec.is_none() + && raydium_instruction_already_decoded_by_discriminator( + &decoded_discriminator_keys, + audit_spec.protocol_name, + dedupe_discriminator_hex.as_deref(), + ) + { + if let Some(discriminator_hex) = dedupe_discriminator_hex.as_deref() { + let cleanup_result = self + .delete_replaced_instruction_audit_by_discriminator_hex( + transaction_id, + audit_spec.protocol_name, + discriminator_hex, + ) + .await; + if let Err(error) = cleanup_result { + return Err(error); + } + } + continue; + } + let mapped_spec = if anchor_event_spec.is_some() { + None + } else { + raydium_mapped_non_trade_instruction_spec( + audit_spec.protocol_name, + discriminator_hex.as_deref(), + accounts.len(), + ) + }; if let Some(mapped_spec) = mapped_spec { if raydium_mapped_event_kind_already_decoded( decoded_events.as_slice(), audit_spec.protocol_name, mapped_spec.event_kind, ) { + if let Some(discriminator_hex) = dedupe_discriminator_hex.as_deref() { + let cleanup_result = self + .delete_replaced_instruction_audit_by_discriminator_hex( + transaction_id, + audit_spec.protocol_name, + discriminator_hex, + ) + .await; + if let Err(error) = cleanup_result { + return Err(error); + } + } continue; } } - let event_kind = match mapped_spec { - Some(mapped_spec) => mapped_spec.event_kind, - None => audit_spec.event_kind, + let event_kind = match anchor_event_spec { + Some(anchor_event_spec) => anchor_event_spec.event_kind, + None => match mapped_spec { + Some(mapped_spec) => mapped_spec.event_kind, + None => audit_spec.event_kind, + }, }; let mut payload = build_raydium_instruction_audit_payload( transaction, @@ -1821,6 +2067,13 @@ WHERE transaction_id = ? event_kind, program_id.as_str(), ); + if let Some(anchor_event_spec) = anchor_event_spec { + payload = enrich_raydium_launchpad_anchor_self_cpi_payload( + payload, + anchor_event_spec, + data_bytes.as_deref(), + ); + } if let Some(mapped_spec) = mapped_spec { payload = enrich_raydium_mapped_non_trade_payload( payload, @@ -1828,12 +2081,18 @@ WHERE transaction_id = ? data_base58.as_deref(), ); } - let pool_account = candidate_raydium_mapped_pool_account( - mapped_spec, - accounts.as_slice(), - audit_spec.protocol_name, - instruction.accounts_json.as_str(), - ); + let pool_account = match anchor_event_spec { + Some(anchor_event_spec) => raydium_launchpad_anchor_self_cpi_pool_account( + anchor_event_spec, + data_bytes.as_deref(), + ), + None => candidate_raydium_mapped_pool_account( + mapped_spec, + accounts.as_slice(), + audit_spec.protocol_name, + instruction.accounts_json.as_str(), + ), + }; let token_a_mint = candidate_raydium_mapped_account( mapped_spec.and_then(|spec| return spec.token_a_mint_index), accounts.as_slice(), @@ -1866,6 +2125,32 @@ WHERE transaction_id = ? Ok(persisted_event) => persisted_event, Err(error) => return Err(error), }; + if let Some(anchor_event_spec) = anchor_event_spec { + let cleanup_result = self + .delete_replaced_raydium_launchpad_anchor_self_cpi_audit( + transaction_id, + instruction_id, + anchor_event_spec.discriminator_hex, + ) + .await; + if let Err(error) = cleanup_result { + return Err(error); + } + } + if anchor_event_spec.is_none() { + if let Some(discriminator_hex) = dedupe_discriminator_hex.as_deref() { + let cleanup_result = self + .delete_replaced_instruction_audit_by_discriminator_hex( + transaction_id, + audit_spec.protocol_name, + discriminator_hex, + ) + .await; + if let Err(error) = cleanup_result { + return Err(error); + } + } + } persisted.push(persisted_event); } return Ok(persisted); @@ -2305,12 +2590,14 @@ enum RaydiumMappedNonTradeAmountLayout { ClmmOpenLimitOrder, ClmmIncreaseLimitOrder, ClmmDecreaseLimitOrder, + AnchorIdl, CpmmAmmConfig, CpmmDeposit, CpmmFeePair, CpmmInitialize, CpmmPoolStatus, CpmmWithdraw, + LaunchpadInitialize, } fn raydium_instruction_audit_spec( @@ -2337,6 +2624,13 @@ fn raydium_instruction_audit_spec( candidate_pool_account_index: 3, }); } + if program_id == crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID { + return Some(RaydiumInstructionAuditSpec { + protocol_name: "raydium_launchpad", + event_kind: "raydium_launchpad.instruction_audit", + candidate_pool_account_index: 4, + }); + } return None; } @@ -2349,7 +2643,24 @@ fn raydium_mapped_non_trade_instruction_spec( Some(discriminator_hex) => discriminator_hex, None => return None, }; + if protocol_name == "raydium_launchpad" { + return raydium_launchpad_mapped_non_trade_instruction_spec( + discriminator_hex, + account_count, + ); + } if protocol_name == "raydium_clmm" { + if discriminator_hex == "e445a52e51cb9a1d" { + return Some(RaydiumMappedNonTradeInstructionSpec { + instruction_name: "cpi_event", + event_kind: "raydium_clmm.cpi_event", + pool_account_index: None, + token_a_mint_index: None, + token_b_mint_index: None, + lp_mint_index: None, + amount_layout: RaydiumMappedNonTradeAmountLayout::None, + }); + } if discriminator_hex == "4c7c800fd55725fa" && account_count >= 3 { return Some(RaydiumMappedNonTradeInstructionSpec { instruction_name: "close_limit_order", @@ -2614,6 +2925,17 @@ fn raydium_mapped_non_trade_instruction_spec( amount_layout: RaydiumMappedNonTradeAmountLayout::None, }); } + if discriminator_hex == "0707500802c784f0" && account_count >= 2 { + return Some(RaydiumMappedNonTradeInstructionSpec { + instruction_name: "update_dynamic_fee_config", + event_kind: "raydium_clmm.update_dynamic_fee_config", + pool_account_index: None, + token_a_mint_index: None, + token_b_mint_index: None, + lp_mint_index: None, + amount_layout: RaydiumMappedNonTradeAmountLayout::None, + }); + } if discriminator_hex == "7f467728bce33d07" { return Some(RaydiumMappedNonTradeInstructionSpec { instruction_name: "update_operation_account", @@ -2693,6 +3015,28 @@ fn raydium_mapped_non_trade_instruction_spec( } } if protocol_name == "raydium_cpmm" { + if discriminator_hex == "40f4bc78a7e9690a" { + return Some(RaydiumMappedNonTradeInstructionSpec { + instruction_name: "anchor_idl_instruction", + event_kind: "raydium_cpmm.anchor_idl_instruction", + pool_account_index: None, + token_a_mint_index: None, + token_b_mint_index: None, + lp_mint_index: None, + amount_layout: RaydiumMappedNonTradeAmountLayout::AnchorIdl, + }); + } + if discriminator_hex == "e445a52e51cb9a1d" { + return Some(RaydiumMappedNonTradeInstructionSpec { + instruction_name: "cpi_event", + event_kind: "raydium_cpmm.cpi_event", + pool_account_index: None, + token_a_mint_index: None, + token_b_mint_index: None, + lp_mint_index: None, + amount_layout: RaydiumMappedNonTradeAmountLayout::None, + }); + } if discriminator_hex == "9c5420764587467b" && account_count >= 4 { return Some(RaydiumMappedNonTradeInstructionSpec { instruction_name: "close_permission_pda", @@ -2829,6 +3173,31 @@ fn raydium_mapped_non_trade_instruction_spec( return None; } +fn raydium_launchpad_mapped_non_trade_instruction_spec( + discriminator_hex: &str, + account_count: usize, +) -> std::option::Option { + let layout = + match crate::dex::raydium_launchpad::account_layout(discriminator_hex, account_count) { + Some(layout) => layout, + None => return None, + }; + let amount_layout = if layout.creates_pool { + RaydiumMappedNonTradeAmountLayout::LaunchpadInitialize + } else { + RaydiumMappedNonTradeAmountLayout::None + }; + return Some(RaydiumMappedNonTradeInstructionSpec { + instruction_name: layout.instruction_name, + event_kind: layout.event_kind, + pool_account_index: layout.pool_account_index, + token_a_mint_index: layout.base_mint_index, + token_b_mint_index: layout.quote_mint_index, + lp_mint_index: None, + amount_layout, + }); +} + fn candidate_raydium_mapped_pool_account( mapped_spec: std::option::Option, accounts: &[std::string::String], @@ -2915,6 +3284,20 @@ fn insert_raydium_mapped_amounts( ) { match amount_layout { RaydiumMappedNonTradeAmountLayout::None => return, + RaydiumMappedNonTradeAmountLayout::AnchorIdl => { + let payload_len = data.len(); + object.insert("idlManagementInstruction".to_string(), serde_json::Value::Bool(true)); + object.insert( + "instructionDataLength".to_string(), + serde_json::Value::Number(serde_json::Number::from(payload_len as u64)), + ); + if payload_len >= 8 { + object.insert( + "idlManagementDiscriminatorHex".to_string(), + serde_json::Value::String("40f4bc78a7e9690a".to_string()), + ); + } + }, RaydiumMappedNonTradeAmountLayout::ClmmCreatePool => { if let Some(sqrt_price_x64) = read_u128_le_from_bytes(data, 8) { object.insert( @@ -3114,6 +3497,16 @@ fn insert_raydium_mapped_amounts( ); } }, + RaydiumMappedNonTradeAmountLayout::LaunchpadInitialize => { + object.insert( + "poolKindHint".to_string(), + serde_json::Value::String("bonding_curve".to_string()), + ); + object.insert( + "poolStatusHint".to_string(), + serde_json::Value::String("pending".to_string()), + ); + }, RaydiumMappedNonTradeAmountLayout::CpmmWithdraw => { if let Some(lp_amount) = read_u64_le_from_bytes(data, 8) { object.insert( @@ -3175,6 +3568,25 @@ fn read_i32_le_from_bytes(data: &[u8], offset: usize) -> std::option::Option, + offset: usize, +) -> std::option::Option { + let data = match data { + Some(data) => data, + None => return None, + }; + let end = offset.checked_add(32_usize); + let end = match end { + Some(end) => end, + None => return None, + }; + if data.len() < end { + return None; + } + return Some(bs58::encode(&data[offset..end]).into_string()); +} + fn read_u64_le_from_bytes(data: &[u8], offset: usize) -> std::option::Option { if data.len() < offset + 8 { return None; @@ -3546,6 +3958,7 @@ fn instruction_audit_event_kind_by_protocol( "raydium_amm_v4" => return Some("raydium_amm_v4.instruction_audit"), "raydium_clmm" => return Some("raydium_clmm.instruction_audit"), "raydium_cpmm" => return Some("raydium_cpmm.instruction_audit"), + "raydium_launchpad" => return Some("raydium_launchpad.instruction_audit"), "meteora_dlmm" => return Some("meteora_dlmm.instruction_audit"), "meteora_damm_v1" => return Some("meteora_damm_v1.instruction_audit"), "meteora_damm_v2" => return Some("meteora_damm_v2.instruction_audit"), @@ -3554,6 +3967,264 @@ fn instruction_audit_event_kind_by_protocol( } } +#[derive(Clone, Copy)] +struct RaydiumAnchorSelfCpiEventSpec { + entry_name: &'static str, + event_kind: &'static str, + event_family: &'static str, + discriminator_hex: &'static str, +} + +fn raydium_launchpad_anchor_self_cpi_event_spec( + protocol_name: &str, + data_bytes: std::option::Option<&[u8]>, +) -> std::option::Option { + if protocol_name != "raydium_launchpad" { + return None; + } + let selector_hex = discriminator_hex_from_bytes(data_bytes, 0); + if selector_hex.as_deref() != Some(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX) { + return None; + } + let event_discriminator_hex = discriminator_hex_from_bytes(data_bytes, 8); + let event_discriminator_hex = match event_discriminator_hex.as_deref() { + Some(event_discriminator_hex) => event_discriminator_hex, + None => return None, + }; + match event_discriminator_hex { + "bddb7fd34ee661ee" => { + return Some(RaydiumAnchorSelfCpiEventSpec { + entry_name: "trade_event", + event_kind: "raydium_launchpad.trade_event", + event_family: "swap", + discriminator_hex: "bddb7fd34ee661ee", + }); + }, + "97d7e20976a173ae" => { + return Some(RaydiumAnchorSelfCpiEventSpec { + entry_name: "pool_create_event", + event_kind: "raydium_launchpad.pool_create_event", + event_family: "pool_create", + discriminator_hex: "97d7e20976a173ae", + }); + }, + "15c2725778d3e220" => { + return Some(RaydiumAnchorSelfCpiEventSpec { + entry_name: "claim_vested_event", + event_kind: "raydium_launchpad.claim_vested_event", + event_family: "vesting", + discriminator_hex: "15c2725778d3e220", + }); + }, + "96980bb334d2bf7d" => { + return Some(RaydiumAnchorSelfCpiEventSpec { + entry_name: "create_vesting_event", + event_kind: "raydium_launchpad.create_vesting_event", + event_family: "vesting", + discriminator_hex: "96980bb334d2bf7d", + }); + }, + _ => return None, + } +} + +fn raydium_launchpad_anchor_self_cpi_pool_account( + spec: RaydiumAnchorSelfCpiEventSpec, + data_bytes: std::option::Option<&[u8]>, +) -> std::option::Option { + match spec.entry_name { + "trade_event" => return read_pubkey_base58_from_bytes(data_bytes, 16), + "pool_create_event" => return read_pubkey_base58_from_bytes(data_bytes, 16), + _ => return None, + } +} + +fn enrich_raydium_launchpad_anchor_self_cpi_payload( + payload: serde_json::Value, + spec: RaydiumAnchorSelfCpiEventSpec, + data_bytes: std::option::Option<&[u8]>, +) -> serde_json::Value { + let mut object = match payload { + serde_json::Value::Object(object) => object, + other => { + let mut object = serde_json::Map::new(); + object.insert("rawPayload".to_string(), other); + object + }, + }; + object.insert( + "entryKind".to_string(), + serde_json::Value::String(crate::ENTRY_KIND_EVENT.to_string()), + ); + object.insert("entryName".to_string(), serde_json::Value::String(spec.entry_name.to_string())); + object.insert( + "eventFamily".to_string(), + serde_json::Value::String(spec.event_family.to_string()), + ); + object.insert("eventName".to_string(), serde_json::Value::String(spec.entry_name.to_string())); + object.insert( + "upstreamEventName".to_string(), + serde_json::Value::String(spec.entry_name.to_string()), + ); + object.insert( + "upstreamEntryName".to_string(), + serde_json::Value::String(spec.entry_name.to_string()), + ); + object.insert( + "upstreamEntryKind".to_string(), + serde_json::Value::String(crate::ENTRY_KIND_EVENT.to_string()), + ); + object.insert( + "upstreamDiscriminatorHex".to_string(), + serde_json::Value::String(spec.discriminator_hex.to_string()), + ); + object.insert("localSpecializedDecoder".to_string(), serde_json::Value::Bool(true)); + object.insert("decodedFromAnchorSelfCpiLog".to_string(), serde_json::Value::Bool(true)); + object.insert("decodedFromAudit".to_string(), serde_json::Value::Bool(false)); + object.insert( + "auditReason".to_string(), + serde_json::Value::String("raydium_launchpad_anchor_self_cpi_event_decoded".to_string()), + ); + object.insert( + "proofSource".to_string(), + serde_json::Value::String( + "local_corpus_anchor_self_cpi_and_raydium_launchpad_event_discriminator".to_string(), + ), + ); + if let Some(data_bytes) = data_bytes { + object.insert( + "anchorSelfCpiDataLength".to_string(), + serde_json::Value::Number(serde_json::Number::from(data_bytes.len() as u64)), + ); + if data_bytes.len() >= 16 { + object.insert( + "anchorEventPayloadSize".to_string(), + serde_json::Value::Number(serde_json::Number::from((data_bytes.len() - 16) as u64)), + ); + } + } + if let Some(pool_state) = raydium_launchpad_anchor_self_cpi_pool_account(spec, data_bytes) { + object.insert("poolState".to_string(), serde_json::Value::String(pool_state.clone())); + object.insert("poolAccount".to_string(), serde_json::Value::String(pool_state)); + } + if spec.entry_name == "trade_event" { + insert_raydium_launchpad_trade_event_amounts(&mut object, data_bytes); + object.insert("tradeCandidate".to_string(), serde_json::Value::Bool(true)); + object.insert("candleCandidate".to_string(), serde_json::Value::Bool(true)); + object.insert("nonTradeUseful".to_string(), serde_json::Value::Bool(false)); + object.remove("skipTradeReason"); + object.remove("skipCandleReason"); + } else if spec.entry_name == "pool_create_event" { + object.insert("tradeCandidate".to_string(), serde_json::Value::Bool(false)); + object.insert("candleCandidate".to_string(), serde_json::Value::Bool(false)); + object.insert("nonTradeUseful".to_string(), serde_json::Value::Bool(true)); + object.insert( + "skipTradeReason".to_string(), + serde_json::Value::String("pool_lifecycle_event".to_string()), + ); + object.insert( + "skipCandleReason".to_string(), + serde_json::Value::String("pool_lifecycle_event".to_string()), + ); + } else { + object.insert("tradeCandidate".to_string(), serde_json::Value::Bool(false)); + object.insert("candleCandidate".to_string(), serde_json::Value::Bool(false)); + object.insert("nonTradeUseful".to_string(), serde_json::Value::Bool(false)); + object.insert( + "skipTradeReason".to_string(), + serde_json::Value::String("launchpad_event_audit_only".to_string()), + ); + object.insert( + "skipCandleReason".to_string(), + serde_json::Value::String("launchpad_event_audit_only".to_string()), + ); + } + return serde_json::Value::Object(object); +} + +fn insert_raydium_launchpad_trade_event_amounts( + object: &mut serde_json::Map, + data_bytes: std::option::Option<&[u8]>, +) { + let data = match data_bytes { + Some(data) => data, + None => return, + }; + let fields = [ + ("totalBaseSellRaw", 48_usize), + ("virtualBaseRaw", 56_usize), + ("virtualQuoteRaw", 64_usize), + ("realBaseBeforeRaw", 72_usize), + ("realQuoteBeforeRaw", 80_usize), + ("realBaseAfterRaw", 88_usize), + ("realQuoteAfterRaw", 96_usize), + ("amountInRaw", 104_usize), + ("amountOutRaw", 112_usize), + ("protocolFeeRaw", 120_usize), + ("platformFeeRaw", 128_usize), + ("creatorFeeRaw", 136_usize), + ("shareFeeRaw", 144_usize), + ]; + for field in fields { + if let Some(value) = read_u64_le_from_bytes(data, field.1) { + object.insert(field.0.to_string(), serde_json::Value::String(value.to_string())); + } + } + if let Some(direction) = read_u8_from_bytes(data, 152) { + object.insert( + "tradeDirectionRaw".to_string(), + serde_json::Value::Number(serde_json::Number::from(direction as u64)), + ); + } + if let Some(pool_status) = read_u8_from_bytes(data, 153) { + object.insert( + "poolStatusRaw".to_string(), + serde_json::Value::Number(serde_json::Number::from(pool_status as u64)), + ); + } + if let Some(exact_in) = read_u8_from_bytes(data, 154) { + object.insert("exactIn".to_string(), serde_json::Value::Bool(exact_in != 0)); + } + insert_raydium_launchpad_normalized_trade_amounts(object); +} + +fn insert_raydium_launchpad_normalized_trade_amounts( + object: &mut serde_json::Map, +) { + let direction = object.get("tradeDirectionRaw").and_then(serde_json::Value::as_u64); + let amount_in = object + .get("amountInRaw") + .and_then(serde_json::Value::as_str) + .map(str::to_string); + let amount_out = object + .get("amountOutRaw") + .and_then(serde_json::Value::as_str) + .map(str::to_string); + match direction { + Some(0) => { + object + .insert("tradeSide".to_string(), serde_json::Value::String("BuyBase".to_string())); + if let Some(amount_out) = amount_out { + object.insert("baseAmountRaw".to_string(), serde_json::Value::String(amount_out)); + } + if let Some(amount_in) = amount_in { + object.insert("quoteAmountRaw".to_string(), serde_json::Value::String(amount_in)); + } + }, + Some(1) => { + object + .insert("tradeSide".to_string(), serde_json::Value::String("SellBase".to_string())); + if let Some(amount_in) = amount_in { + object.insert("baseAmountRaw".to_string(), serde_json::Value::String(amount_in)); + } + if let Some(amount_out) = amount_out { + object.insert("quoteAmountRaw".to_string(), serde_json::Value::String(amount_out)); + } + }, + _ => {}, + } +} + fn build_raydium_instruction_audit_payload( transaction: &crate::ChainTransactionDto, instruction: &crate::ChainInstructionDto, @@ -3567,7 +4238,33 @@ fn build_raydium_instruction_audit_payload( None => 0, }; let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref()); - let discriminator_hex = discriminator_hex_from_base58(data_base58.as_deref()); + let data_bytes = instruction_data_bytes_from_base58(data_base58.as_deref()); + let discriminator_hex = discriminator_hex_from_bytes(data_bytes.as_deref(), 0); + let anchor_self_cpi_log = + discriminator_hex.as_deref() == Some(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX); + let anchor_event_discriminator_hex = if anchor_self_cpi_log { + discriminator_hex_from_bytes(data_bytes.as_deref(), 8) + } else { + None + }; + let anchor_event_payload_size = if anchor_self_cpi_log { + match data_bytes.as_ref() { + Some(data_bytes) => data_bytes.len().checked_sub(8), + None => None, + } + } else { + None + }; + let audit_reason = if anchor_self_cpi_log { + "raydium_anchor_self_cpi_log_not_decoded_by_specific_event_decoder" + } else { + "raydium_instruction_not_decoded_by_specific_decoder" + }; + let proof_status = if anchor_self_cpi_log { + "observed_local_corpus_anchor_self_cpi_log" + } else { + "unclassified_local_corpus_instruction" + }; return serde_json::json!({ "decoder": protocol_name, "eventKind": event_kind, @@ -3582,7 +4279,12 @@ fn build_raydium_instruction_audit_payload( "accountCount": account_count, "data": data_base58, "discriminatorHex": discriminator_hex, - "auditReason": "raydium_instruction_not_decoded_by_specific_decoder", + "anchorSelfCpiLog": anchor_self_cpi_log, + "anchorSelfCpiLogSelectorHex": if anchor_self_cpi_log { Some(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX) } else { None }, + "anchorEventDiscriminatorHex": anchor_event_discriminator_hex, + "anchorEventPayloadSize": anchor_event_payload_size, + "auditReason": audit_reason, + "proofStatus": proof_status, "tradeCandidate": false, "candleCandidate": false, "nonTradeUseful": false, @@ -3611,6 +4313,11 @@ fn candidate_raydium_audit_pool_account( event_kind: "raydium_cpmm.instruction_audit", candidate_pool_account_index: 3, }, + "raydium_launchpad" => RaydiumInstructionAuditSpec { + protocol_name: "raydium_launchpad", + event_kind: "raydium_launchpad.instruction_audit", + candidate_pool_account_index: 4, + }, _ => return None, }; let accounts_result = serde_json::from_str::>(accounts_json); @@ -5150,6 +5857,26 @@ mod tests { assert_eq!(decrease_limit_order.pool_account_index, Some(1)); assert_eq!(decrease_limit_order.token_a_mint_index, Some(8)); assert_eq!(decrease_limit_order.token_b_mint_index, Some(9)); + let update_dynamic_fee_config = super::raydium_mapped_non_trade_instruction_spec( + "raydium_clmm", + Some("0707500802c784f0"), + 2, + ); + let update_dynamic_fee_config = match update_dynamic_fee_config { + Some(update_dynamic_fee_config) => update_dynamic_fee_config, + None => panic!("update_dynamic_fee_config discriminator must be mapped"), + }; + assert_eq!(update_dynamic_fee_config.event_kind, "raydium_clmm.update_dynamic_fee_config"); + let cpi_event = super::raydium_mapped_non_trade_instruction_spec( + "raydium_clmm", + Some("e445a52e51cb9a1d"), + 1, + ); + let cpi_event = match cpi_event { + Some(cpi_event) => cpi_event, + None => panic!("clmm cpi_event discriminator must be mapped"), + }; + assert_eq!(cpi_event.event_kind, "raydium_clmm.cpi_event"); } #[test] @@ -5167,6 +5894,9 @@ mod tests { ("313cae889a1c74c8", 2_usize, "raydium_cpmm.update_amm_config"), ("82576c062ee0757b", 2_usize, "raydium_cpmm.update_pool_status"), ("b712469c946da122", 14_usize, "raydium_cpmm.withdraw"), + ("e445a52e51cb9a1d", 1_usize, "raydium_cpmm.cpi_event"), + ("40f4bc78a7e9690a", 3_usize, "raydium_cpmm.anchor_idl_instruction"), + ("40f4bc78a7e9690a", 6_usize, "raydium_cpmm.anchor_idl_instruction"), ]; for (discriminator, account_count, event_kind) in expected { let mapped = super::raydium_mapped_non_trade_instruction_spec( @@ -5232,12 +5962,47 @@ mod tests { )); } + #[test] + fn maps_raydium_launchpad_non_trade_discriminators() { + let buy = super::raydium_mapped_non_trade_instruction_spec( + "raydium_launchpad", + Some("faea0d7bd59c13ec"), + 11, + ); + let buy = match buy { + Some(buy) => buy, + None => panic!("buy_exact_in discriminator must map"), + }; + assert_eq!(buy.instruction_name, "buy_exact_in"); + assert_eq!(buy.event_kind, "raydium_launchpad.buy_exact_in"); + assert_eq!(buy.pool_account_index, Some(4)); + assert_eq!(buy.token_a_mint_index, Some(9)); + assert_eq!(buy.token_b_mint_index, Some(10)); + assert_eq!(buy.lp_mint_index, None); + + let migration = super::raydium_mapped_non_trade_instruction_spec( + "raydium_launchpad", + Some("cf52c091fecf91df"), + 1, + ); + let migration = match migration { + Some(migration) => migration, + None => panic!("migrate_to_amm discriminator must map"), + }; + assert_eq!(migration.event_kind, "raydium_launchpad.migrate_to_amm"); + assert_eq!(migration.pool_account_index, None); + } + #[test] fn maps_instruction_audit_event_kind_for_raydium_and_meteora_dlmm_protocols() { assert_eq!( super::instruction_audit_event_kind_by_protocol("raydium_clmm"), Some("raydium_clmm.instruction_audit") ); + assert_eq!( + super::instruction_audit_event_kind_by_protocol("raydium_launchpad"), + Some("raydium_launchpad.instruction_audit") + ); assert_eq!( super::instruction_audit_event_kind_by_protocol("meteora_dlmm"), Some("meteora_dlmm.instruction_audit") diff --git a/kb_lib/src/dex_detect.rs b/kb_lib/src/dex_detect.rs index 8ca3b35..2c98aab 100644 --- a/kb_lib/src/dex_detect.rs +++ b/kb_lib/src/dex_detect.rs @@ -111,6 +111,9 @@ impl DexDetectService { crate::dex_detection_route::DexDetectionRoute::RaydiumClmmTrade => { self.detect_raydium_clmm_trade(&transaction, decoded_event).await }, + crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool => { + self.detect_raydium_launchpad_pool(&transaction, decoded_event).await + }, crate::dex_detection_route::DexDetectionRoute::PumpFunCreateV2Token => { self.detect_pump_fun_create_v2_token(&transaction, decoded_event).await }, @@ -647,6 +650,24 @@ impl DexDetectService { return Ok(detection_result); } + + async fn detect_raydium_launchpad_pool( + &self, + transaction: &crate::ChainTransactionDto, + decoded_event: &crate::DexDecodedEventDto, + ) -> Result { + return self + .detect_materialized_pool_from_decoded_event( + transaction, + decoded_event, + "raydium_launchpad", + crate::PoolKind::BondingCurve, + crate::PoolStatus::Pending, + "signal.dex.raydium_launchpad", + ) + .await; + } + async fn detect_raydium_cpmm_trade( &self, transaction: &crate::ChainTransactionDto, diff --git a/kb_lib/src/dex_detection_route.rs b/kb_lib/src/dex_detection_route.rs index d732fa7..f37900b 100644 --- a/kb_lib/src/dex_detection_route.rs +++ b/kb_lib/src/dex_detection_route.rs @@ -13,6 +13,8 @@ pub(crate) enum DexDetectionRoute { RaydiumCpmmTrade, /// Raydium CLMM trade route. RaydiumClmmTrade, + /// Raydium Launchpad pool or bonding-curve creation route. + RaydiumLaunchpadPool, /// Pump.fun create token route. PumpFunCreateV2Token, /// Pump.fun trade route. @@ -62,6 +64,27 @@ pub(crate) fn dex_detection_route( ("raydium_clmm", "raydium_clmm.swap_v2") => { return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumClmmTrade); }, + ("raydium_launchpad", "raydium_launchpad.initialize") => { + return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool); + }, + ("raydium_launchpad", "raydium_launchpad.initialize_v2") => { + return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool); + }, + ("raydium_launchpad", "raydium_launchpad.initialize_with_token_2022") => { + return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool); + }, + ("raydium_launchpad", "raydium_launchpad.buy_exact_in") => { + return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool); + }, + ("raydium_launchpad", "raydium_launchpad.buy_exact_out") => { + return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool); + }, + ("raydium_launchpad", "raydium_launchpad.sell_exact_in") => { + return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool); + }, + ("raydium_launchpad", "raydium_launchpad.sell_exact_out") => { + return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool); + }, ("pump_fun", "pump_fun.create_v2_token") => { return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunCreateV2Token); }, @@ -233,6 +256,26 @@ mod tests { assert!(!crate::dex_detection_route::decoded_event_has_full_pool_context(&event)); } + #[test] + fn raydium_launchpad_initialize_route_requires_full_pool_context() { + let event = make_decoded_event( + "raydium_launchpad", + "raydium_launchpad.initialize", + Some("Pool111"), + Some("TokenA111"), + Some("TokenB111"), + ); + let route_option = crate::dex_detection_route::dex_detection_route(&event); + let route = match route_option { + Some(route) => route, + None => panic!("route must be selected"), + }; + assert_eq!(route, crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool); + assert!(crate::dex_detection_route::dex_detection_route_requires_full_pool_context( + route + )); + } + #[test] fn pump_fun_create_token_route_does_not_require_full_pool_context() { let event = make_decoded_event( diff --git a/kb_lib/src/dex_event_classification.rs b/kb_lib/src/dex_event_classification.rs index 2dc0a28..d323bff 100644 --- a/kb_lib/src/dex_event_classification.rs +++ b/kb_lib/src/dex_event_classification.rs @@ -23,6 +23,8 @@ pub enum DexEventCategory { PoolLifecycle, /// Protocol administration, configuration or permission update event. Admin, + /// Token-account creation, close, wrap or unwrap event. + TokenAccount, /// Informational or audit-only decoded event retained for corpus analysis. Informational, /// Event kind that is not classified yet. @@ -39,6 +41,7 @@ impl DexEventCategory { Self::Reward => return "reward", Self::PoolLifecycle => return "pool_lifecycle", Self::Admin => return "admin", + Self::TokenAccount => return "token_account", Self::Informational => return "informational", Self::Unknown => return "unknown", } @@ -76,6 +79,10 @@ pub enum DexEventLifecycleKind { Reward, /// Administration, configuration or permission update event. AdminConfig, + /// Token-account creation event. + TokenAccountCreate, + /// Token-account close event. + TokenAccountClose, /// Instruction-level audit event retained for corpus analysis. InstructionAudit, /// Event kind that is not classified yet. @@ -100,6 +107,8 @@ impl DexEventLifecycleKind { Self::FeeCollection => return "fee_collection", Self::Reward => return "reward", Self::AdminConfig => return "admin_config", + Self::TokenAccountCreate => return "token_account_create", + Self::TokenAccountClose => return "token_account_close", Self::InstructionAudit => return "instruction_audit", Self::Unknown => return "unknown", } @@ -157,6 +166,9 @@ pub fn classify_dex_event_category(event_kind: &str) -> DexEventCategory { if is_dex_admin_event_kind(event_kind) { return DexEventCategory::Admin; } + if is_dex_token_account_event_kind(event_kind) { + return DexEventCategory::TokenAccount; + } if is_dex_trade_event_kind(event_kind) { return DexEventCategory::Trade; } @@ -215,6 +227,12 @@ pub fn classify_dex_event_lifecycle_kind(event_kind: &str) -> DexEventLifecycleK if is_dex_admin_event_kind(event_kind) { return DexEventLifecycleKind::AdminConfig; } + if is_dex_token_account_close_event_kind(event_kind) { + return DexEventLifecycleKind::TokenAccountClose; + } + if is_dex_token_account_create_event_kind(event_kind) { + return DexEventLifecycleKind::TokenAccountCreate; + } if is_dex_trade_event_kind(event_kind) { return DexEventLifecycleKind::TradeSwap; } @@ -254,6 +272,7 @@ pub fn classify_dex_event_actionability( DexEventCategory::Reward => return DexEventActionability::NonTradeUseful, DexEventCategory::PoolLifecycle => return DexEventActionability::NonTradeUseful, DexEventCategory::Admin => return DexEventActionability::NonTradeUseful, + DexEventCategory::TokenAccount => return DexEventActionability::NonTradeUseful, DexEventCategory::Informational => return DexEventActionability::Informational, DexEventCategory::Trade => return DexEventActionability::NonActionableTrade, DexEventCategory::Unknown => return DexEventActionability::Unknown, @@ -278,6 +297,18 @@ pub fn is_dex_informational_event_kind(event_kind: &str) -> bool { if event_kind.contains(".instruction_audit") { return true; } + if event_kind.ends_with(".cpi_event") { + return true; + } + if event_kind.ends_with(".anchor_idl_instruction") { + return true; + } + if event_kind.contains(".idl_") { + return true; + } + if event_kind.contains(".liquidity_calculate_event") { + return true; + } if event_kind.ends_with("_audit") { return true; } @@ -289,6 +320,9 @@ pub fn is_dex_informational_event_kind(event_kind: &str) -> bool { /// Returns true when the event kind represents a swap-like event. pub fn is_dex_trade_event_kind(event_kind: &str) -> bool { + if event_kind == "raydium_launchpad.trade_event" { + return true; + } if event_kind.ends_with(".buy") { return true; } @@ -332,9 +366,6 @@ pub fn is_dex_liquidity_event_kind(event_kind: &str) -> bool { if event_kind.contains(".liquidity_change_event") { return true; } - if event_kind.contains(".liquidity_calculate_event") { - return true; - } if event_kind.contains(".withdraw") { return true; } @@ -362,6 +393,9 @@ pub fn is_dex_liquidity_event_kind(event_kind: &str) -> bool { if event_kind.contains(".close_position") { return true; } + if event_kind.contains(".close_protocol_position") { + return true; + } if event_kind.contains(".position_close") { return true; } @@ -404,6 +438,9 @@ pub fn is_dex_position_open_event_kind(event_kind: &str) -> bool { if event_kind.contains(".open_position") { return true; } + if event_kind.contains(".create_personal_position_event") { + return true; + } if event_kind.contains(".position_create") { return true; } @@ -415,11 +452,23 @@ pub fn is_dex_position_close_event_kind(event_kind: &str) -> bool { if event_kind.contains(".close_position") { return true; } + if event_kind.contains(".close_protocol_position") { + return true; + } return false; } /// Returns true for fee collection events. pub fn is_dex_fee_event_kind(event_kind: &str) -> bool { + if event_kind.contains("claim_creator_fee") { + return true; + } + if event_kind.contains("claim_platform_fee") { + return true; + } + if event_kind.contains("collect_migrate_fee") { + return true; + } if event_kind.contains("collect_creator_fee") { return true; } @@ -518,6 +567,21 @@ pub fn is_dex_pool_lifecycle_event_kind(event_kind: &str) -> bool { /// Returns true for launch or bonding-curve creation events. pub fn is_dex_launch_event_kind(event_kind: &str) -> bool { + if event_kind.contains(".claim_vested_event") { + return true; + } + if event_kind.contains(".claim_vested_token") { + return true; + } + if event_kind.contains(".create_platform_vesting_account") { + return true; + } + if event_kind.contains(".create_vesting_account") { + return true; + } + if event_kind.contains(".create_vesting_event") { + return true; + } if event_kind.contains("pump_fun.create") { return true; } @@ -586,9 +650,15 @@ pub fn is_dex_pool_creation_event_kind(event_kind: &str) -> bool { if event_kind.contains(".create_pool") { return true; } + if event_kind.contains(".create_customizable_pool") { + return true; + } if event_kind.contains(".pool_created_event") { return true; } + if event_kind.contains(".pool_create_event") { + return true; + } if event_kind.contains(".create_amm") { return true; } @@ -606,8 +676,53 @@ pub fn is_dex_pair_creation_event_kind(event_kind: &str) -> bool { return false; } +/// Returns true for token-account lifecycle events detected by DEX decoders. +pub fn is_dex_token_account_event_kind(event_kind: &str) -> bool { + if is_dex_token_account_create_event_kind(event_kind) { + return true; + } + if is_dex_token_account_close_event_kind(event_kind) { + return true; + } + return false; +} + +/// Returns true for token-account creation events detected by DEX decoders. +pub fn is_dex_token_account_create_event_kind(event_kind: &str) -> bool { + if event_kind.contains(".create_support_mint_associated") { + return true; + } + if event_kind.contains(".create_ata") { + return true; + } + if event_kind.contains(".init_account") { + return true; + } + return false; +} + +/// Returns true for token-account close events detected by DEX decoders. +pub fn is_dex_token_account_close_event_kind(event_kind: &str) -> bool { + if event_kind.contains(".close_account") { + return true; + } + return false; +} + /// Returns true for admin, configuration or permission changes. pub fn is_dex_admin_event_kind(event_kind: &str) -> bool { + if event_kind.contains(".close_platform_global_access") { + return true; + } + if event_kind.contains(".create_platform_global_access") { + return true; + } + if event_kind.contains(".create_operation_account") { + return true; + } + if event_kind.contains("platform_curve_param") { + return true; + } if event_kind.contains(".initialize_with_permission") { return false; } diff --git a/kb_lib/src/dex_event_coverage.rs b/kb_lib/src/dex_event_coverage.rs index 8561b24..cd55efe 100644 --- a/kb_lib/src/dex_event_coverage.rs +++ b/kb_lib/src/dex_event_coverage.rs @@ -183,7 +183,11 @@ impl DexEventCoverageService { fn build_coverage_entry_from_upstream( entry: &crate::UpstreamRegistryEntryDto, ) -> crate::DexEventCoverageEntryDto { - let event_family = infer_event_family(entry.entry_name.as_str(), entry.entry_kind.as_str()); + let event_family = infer_event_family_for_entry( + entry.decoder_code.as_str(), + entry.entry_name.as_str(), + entry.entry_kind.as_str(), + ); let expected_db_target = infer_expected_db_target_for_entry( entry.decoder_code.as_str(), entry.entry_name.as_str(), @@ -211,11 +215,79 @@ fn infer_expected_db_target_for_entry( event_family: std::option::Option<&str>, entry_kind: &str, ) -> std::option::Option { - if decoder_code == "raydium_cpmm" && entry_name == "swap_event" { + if decoder_code == "raydium_cpmm" + && (entry_name == "swap_event" || entry_name == "anchor_idl_instruction") + { return Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string()); } - if decoder_code == "raydium_clmm" && entry_name == "initialize_reward" { - return Some(crate::DexEventCoverageEntryDto::DB_TARGET_REWARD_EVENTS.to_string()); + if decoder_code == "raydium_clmm" { + if entry_name == "initialize_reward" { + return Some(crate::DexEventCoverageEntryDto::DB_TARGET_REWARD_EVENTS.to_string()); + } + if entry_name == "swap_event" || entry_name == "swap_router_base_in" { + return Some( + crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string(), + ); + } + if entry_name == "open_position" + || entry_name == "close_position" + || entry_name == "close_protocol_position" + { + return Some( + crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string(), + ); + } + } + if decoder_code == "raydium_launchpad" { + if entry_name == "trade_event" { + return Some(crate::DexEventCoverageEntryDto::DB_TARGET_TRADE_EVENTS.to_string()); + } + if entry_name == "buy_exact_in" + || entry_name == "buy_exact_out" + || entry_name == "sell_exact_in" + || entry_name == "sell_exact_out" + { + return Some(crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS.to_string()); + } + if entry_name == "initialize" + || entry_name == "initialize_v2" + || entry_name == "initialize_with_token_2022" + || entry_name == "pool_create_event" + { + return Some( + crate::DexEventCoverageEntryDto::DB_TARGET_POOL_LIFECYCLE_EVENTS.to_string(), + ); + } + if entry_name == "claim_creator_fee" + || entry_name == "claim_platform_fee" + || entry_name == "claim_platform_fee_from_vault" + || entry_name == "collect_fee" + || entry_name == "collect_migrate_fee" + { + return Some(crate::DexEventCoverageEntryDto::DB_TARGET_FEE_EVENTS.to_string()); + } + if entry_name == "close_platform_global_access" + || entry_name == "create_config" + || entry_name == "create_platform_config" + || entry_name == "create_platform_global_access" + || entry_name == "remove_platform_curve_param" + || entry_name == "update_config" + || entry_name == "update_platform_config" + || entry_name == "update_platform_curve_param" + { + return Some(crate::DexEventCoverageEntryDto::DB_TARGET_POOL_ADMIN_EVENTS.to_string()); + } + if entry_name == "claim_vested_event" + || entry_name == "claim_vested_token" + || entry_name == "create_platform_vesting_account" + || entry_name == "create_vesting_account" + || entry_name == "create_vesting_event" + || entry_name == "migrate_to_amm" + || entry_name == "migrate_to_cpswap" + { + return Some(crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS.to_string()); + } + return Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string()); } return infer_expected_db_target(event_family, entry_kind); } @@ -241,6 +313,7 @@ fn infer_expected_db_target( "liquidity" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS, "liquidity_add" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS, "liquidity_remove" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS, + "liquidity_change" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS, "position_open" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS, "position_close" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS, "fee" => crate::DexEventCoverageEntryDto::DB_TARGET_FEE_EVENTS, @@ -264,6 +337,10 @@ fn infer_expected_db_target( "unlock" => crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_LOCK_EVENTS, "launch" => crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS, "migration" => crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS, + "vesting" => crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS, + "liquidity_calculation" => crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY, + "cpi_transport" => crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY, + "idl_management" => crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY, "stake" => crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY, "unstake" => crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY, _ => crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY, @@ -271,6 +348,99 @@ fn infer_expected_db_target( return Some(target.to_string()); } +fn infer_event_family_for_entry( + decoder_code: &str, + entry_name: &str, + entry_kind: &str, +) -> std::option::Option { + if decoder_code == "raydium_launchpad" { + return infer_raydium_launchpad_event_family(entry_name, entry_kind); + } + if decoder_code == "raydium_clmm" { + return infer_raydium_clmm_event_family(entry_name, entry_kind); + } + if decoder_code == "raydium_cpmm" { + return infer_raydium_cpmm_event_family(entry_name, entry_kind); + } + return infer_event_family(entry_name, entry_kind); +} + +fn infer_raydium_cpmm_event_family( + entry_name: &str, + entry_kind: &str, +) -> std::option::Option { + if entry_kind == crate::ENTRY_KIND_PROGRAM { + return None; + } + match entry_name { + "anchor_idl_instruction" => return Some("idl_management".to_string()), + "cpi_event" => return Some("cpi_transport".to_string()), + _ => return infer_event_family(entry_name, entry_kind), + } +} + +fn infer_raydium_clmm_event_family( + entry_name: &str, + entry_kind: &str, +) -> std::option::Option { + if entry_kind == crate::ENTRY_KIND_PROGRAM { + return None; + } + match entry_name { + "cpi_event" => return Some("cpi_transport".to_string()), + "create_personal_position_event" => return Some("position_open".to_string()), + "liquidity_calculate_event" => return Some("liquidity_calculation".to_string()), + "liquidity_change_event" => return Some("liquidity_change".to_string()), + "pool_created_event" => return Some("pool_create".to_string()), + "create_operation_account" => return Some("admin_config".to_string()), + "update_operation_account" => return Some("admin_config".to_string()), + "update_dynamic_fee_config" => return Some("admin_config".to_string()), + _ => return infer_event_family(entry_name, entry_kind), + } +} + +fn infer_raydium_launchpad_event_family( + entry_name: &str, + entry_kind: &str, +) -> std::option::Option { + if entry_kind == crate::ENTRY_KIND_PROGRAM { + return None; + } + match entry_name { + "buy_exact_in" => return Some("swap".to_string()), + "buy_exact_out" => return Some("swap".to_string()), + "sell_exact_in" => return Some("swap".to_string()), + "sell_exact_out" => return Some("swap".to_string()), + "trade_event" => return Some("swap".to_string()), + "pool_create_event" => return Some("pool_create".to_string()), + "initialize" => return Some("pool_create".to_string()), + "initialize_v2" => return Some("pool_create".to_string()), + "initialize_with_token_2022" => return Some("pool_create".to_string()), + "claim_creator_fee" => return Some("fee".to_string()), + "claim_platform_fee" => return Some("fee".to_string()), + "claim_platform_fee_from_vault" => return Some("fee".to_string()), + "collect_fee" => return Some("fee".to_string()), + "collect_migrate_fee" => return Some("fee".to_string()), + "claim_vested_event" => return Some("vesting".to_string()), + "claim_vested_token" => return Some("vesting".to_string()), + "create_platform_vesting_account" => return Some("vesting".to_string()), + "create_vesting_account" => return Some("vesting".to_string()), + "create_vesting_event" => return Some("vesting".to_string()), + "migrate_to_amm" => return Some("migration".to_string()), + "migrate_to_cpswap" => return Some("migration".to_string()), + "close_platform_global_access" => return Some("account_close".to_string()), + "create_config" => return Some("admin_config".to_string()), + "create_platform_config" => return Some("admin_config".to_string()), + "create_platform_global_access" => return Some("admin_config".to_string()), + "remove_platform_curve_param" => return Some("admin_config".to_string()), + "update_config" => return Some("admin_config".to_string()), + "update_platform_config" => return Some("admin_config".to_string()), + "update_platform_curve_param" => return Some("admin_config".to_string()), + "cpi_event" => return Some("cpi_transport".to_string()), + _ => return infer_event_family(entry_name, entry_kind), + } +} + fn infer_event_family( entry_name: &str, entry_kind: &str, @@ -282,6 +452,12 @@ fn infer_event_family( if normalized == "lp_change_event" { return Some("liquidity".to_string()); } + if normalized == "cpi_event" { + return Some("cpi_transport".to_string()); + } + if contains_any(normalized.as_str(), &["migrate", "migration", "graduate"]) { + return Some("migration".to_string()); + } if contains_any(normalized.as_str(), &["swap", "buy", "sell", "trade"]) { return Some("swap".to_string()); } @@ -416,10 +592,49 @@ fn contains_any(value: &str, needles: &[&str]) -> bool { return false; } +fn raydium_launchpad_local_entry_is_known(entry_name: &str) -> bool { + match entry_name { + "buy_exact_in" => return true, + "buy_exact_out" => return true, + "close_platform_global_access" => return true, + "claim_creator_fee" => return true, + "claim_platform_fee" => return true, + "claim_platform_fee_from_vault" => return true, + "claim_vested_event" => return true, + "claim_vested_token" => return true, + "collect_fee" => return true, + "collect_migrate_fee" => return true, + "create_config" => return true, + "create_platform_config" => return true, + "cpi_event" => return true, + "create_platform_vesting_account" => return true, + "create_platform_global_access" => return true, + "create_vesting_account" => return true, + "create_vesting_event" => return true, + "initialize" => return true, + "initialize_v2" => return true, + "initialize_with_token_2022" => return true, + "migrate_to_amm" => return true, + "migrate_to_cpswap" => return true, + "pool_create_event" => return true, + "remove_platform_curve_param" => return true, + "sell_exact_in" => return true, + "sell_exact_out" => return true, + "trade_event" => return true, + "update_config" => return true, + "update_platform_config" => return true, + "update_platform_curve_param" => return true, + _ => return false, + } +} + pub(crate) fn known_local_event_kind( decoder_code: &str, entry_name: &str, ) -> std::option::Option { + if decoder_code == "raydium_launchpad" && raydium_launchpad_local_entry_is_known(entry_name) { + return Some(format!("raydium_launchpad.{}", entry_name)); + } match (decoder_code, entry_name) { ("raydium_cpmm", "swap_base_input") => { return Some("raydium_cpmm.swap_base_input".to_string()); @@ -453,6 +668,10 @@ pub(crate) fn known_local_event_kind( ("raydium_cpmm", "lp_change_event") => { return Some("raydium_cpmm.lp_change_event".to_string()); }, + ("raydium_cpmm", "cpi_event") => return Some("raydium_cpmm.cpi_event".to_string()), + ("raydium_cpmm", "anchor_idl_instruction") => { + return Some("raydium_cpmm.anchor_idl_instruction".to_string()); + }, ("raydium_cpmm", "swap_event") => return Some("raydium_cpmm.swap_event".to_string()), ("raydium_cpmm", "update_amm_config") => { return Some("raydium_cpmm.update_amm_config".to_string()); @@ -461,6 +680,38 @@ pub(crate) fn known_local_event_kind( return Some("raydium_cpmm.update_pool_status".to_string()); }, ("raydium_cpmm", "withdraw") => return Some("raydium_cpmm.withdraw".to_string()), + ("raydium_clmm", "cpi_event") => return Some("raydium_clmm.cpi_event".to_string()), + ("raydium_clmm", "collect_personal_fee_event") => { + return Some("raydium_clmm.collect_personal_fee_event".to_string()); + }, + ("raydium_clmm", "collect_protocol_fee_event") => { + return Some("raydium_clmm.collect_protocol_fee_event".to_string()); + }, + ("raydium_clmm", "config_change_event") => { + return Some("raydium_clmm.config_change_event".to_string()); + }, + ("raydium_clmm", "create_personal_position_event") => { + return Some("raydium_clmm.create_personal_position_event".to_string()); + }, + ("raydium_clmm", "decrease_liquidity_event") => { + return Some("raydium_clmm.decrease_liquidity_event".to_string()); + }, + ("raydium_clmm", "increase_liquidity_event") => { + return Some("raydium_clmm.increase_liquidity_event".to_string()); + }, + ("raydium_clmm", "liquidity_calculate_event") => { + return Some("raydium_clmm.liquidity_calculate_event".to_string()); + }, + ("raydium_clmm", "liquidity_change_event") => { + return Some("raydium_clmm.liquidity_change_event".to_string()); + }, + ("raydium_clmm", "pool_created_event") => { + return Some("raydium_clmm.pool_created_event".to_string()); + }, + ("raydium_clmm", "swap_event") => return Some("raydium_clmm.swap_event".to_string()), + ("raydium_clmm", "update_reward_infos_event") => { + return Some("raydium_clmm.update_reward_infos_event".to_string()); + }, ("raydium_clmm", "close_limit_order") => { return Some("raydium_clmm.close_limit_order".to_string()); }, @@ -545,6 +796,9 @@ pub(crate) fn known_local_event_kind( ("raydium_clmm", "update_amm_config") => { return Some("raydium_clmm.update_amm_config".to_string()); }, + ("raydium_clmm", "update_dynamic_fee_config") => { + return Some("raydium_clmm.update_dynamic_fee_config".to_string()); + }, ("raydium_clmm", "update_operation_account") => { return Some("raydium_clmm.update_operation_account".to_string()); }, @@ -605,6 +859,23 @@ mod tests { super::infer_event_family("collect_creator_fee", crate::ENTRY_KIND_INSTRUCTION), Some("fee".to_string()) ); + assert_eq!( + super::infer_event_family_for_entry( + "raydium_cpmm", + "anchor_idl_instruction", + crate::ENTRY_KIND_INSTRUCTION, + ), + Some("idl_management".to_string()) + ); + assert_eq!( + super::infer_expected_db_target_for_entry( + "raydium_cpmm", + "anchor_idl_instruction", + Some("idl_management"), + crate::ENTRY_KIND_INSTRUCTION, + ), + Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string()) + ); } #[test] @@ -617,6 +888,38 @@ mod tests { super::infer_event_family("create_dynamic_fee_config", crate::ENTRY_KIND_INSTRUCTION), Some("admin_config".to_string()) ); + assert_eq!( + super::infer_event_family_for_entry( + "raydium_clmm", + "update_dynamic_fee_config", + crate::ENTRY_KIND_INSTRUCTION, + ), + Some("admin_config".to_string()) + ); + assert_eq!( + super::infer_event_family_for_entry( + "raydium_clmm", + "create_personal_position_event", + crate::ENTRY_KIND_EVENT, + ), + Some("position_open".to_string()) + ); + assert_eq!( + super::infer_event_family_for_entry( + "raydium_clmm", + "liquidity_calculate_event", + crate::ENTRY_KIND_EVENT, + ), + Some("liquidity_calculation".to_string()) + ); + assert_eq!( + super::infer_event_family_for_entry( + "raydium_clmm", + "liquidity_change_event", + crate::ENTRY_KIND_EVENT, + ), + Some("liquidity_change".to_string()) + ); assert_eq!( super::infer_event_family( "create_support_mint_associated", @@ -652,6 +955,15 @@ mod tests { super::infer_expected_db_target(Some("position_open"), crate::ENTRY_KIND_INSTRUCTION), Some(crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS.to_string()) ); + assert_eq!( + super::infer_expected_db_target_for_entry( + "raydium_clmm", + "open_position", + Some("position_open"), + crate::ENTRY_KIND_INSTRUCTION, + ), + Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string()) + ); assert_eq!( super::infer_expected_db_target(Some("position_close"), crate::ENTRY_KIND_INSTRUCTION), Some(crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS.to_string()) @@ -676,6 +988,79 @@ mod tests { super::known_local_event_kind("raydium_clmm", "decrease_limit_order"), Some("raydium_clmm.decrease_limit_order".to_string()) ); + assert_eq!( + super::known_local_event_kind("raydium_clmm", "update_dynamic_fee_config"), + Some("raydium_clmm.update_dynamic_fee_config".to_string()) + ); + assert_eq!( + super::known_local_event_kind("raydium_clmm", "cpi_event"), + Some("raydium_clmm.cpi_event".to_string()) + ); + assert_eq!( + super::known_local_event_kind("raydium_clmm", "pool_created_event"), + Some("raydium_clmm.pool_created_event".to_string()) + ); + } + #[test] + fn launchpad_swap_instructions_materialize_as_launch_events_without_duplicate_trades() { + assert_eq!( + super::known_local_event_kind("raydium_launchpad", "buy_exact_in"), + Some("raydium_launchpad.buy_exact_in".to_string()) + ); + assert_eq!( + super::known_local_event_kind("raydium_launchpad", "trade_event"), + Some("raydium_launchpad.trade_event".to_string()) + ); + assert_eq!(super::known_local_event_kind("raydium_launchpad", "program"), None); + assert_eq!( + super::infer_event_family_for_entry( + "raydium_launchpad", + "pool_create_event", + crate::ENTRY_KIND_EVENT, + ), + Some("pool_create".to_string()) + ); + assert_eq!( + super::infer_event_family_for_entry( + "raydium_launchpad", + "claim_vested_event", + crate::ENTRY_KIND_EVENT, + ), + Some("vesting".to_string()) + ); + assert_eq!( + super::infer_event_family_for_entry( + "raydium_launchpad", + "create_platform_vesting_account", + crate::ENTRY_KIND_INSTRUCTION, + ), + Some("vesting".to_string()) + ); + assert_eq!( + super::infer_event_family_for_entry( + "raydium_launchpad", + "remove_platform_curve_param", + crate::ENTRY_KIND_INSTRUCTION, + ), + Some("admin_config".to_string()) + ); + assert_eq!( + super::infer_event_family_for_entry( + "raydium_launchpad", + "cpi_event", + crate::ENTRY_KIND_INSTRUCTION, + ), + Some("cpi_transport".to_string()) + ); + assert_eq!( + super::infer_expected_db_target_for_entry( + "raydium_launchpad", + "buy_exact_in", + Some("swap"), + crate::ENTRY_KIND_INSTRUCTION, + ), + Some(crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS.to_string()) + ); } #[tokio::test] @@ -716,6 +1101,24 @@ mod tests { == Some(crate::DexEventCoverageEntryDto::DB_TARGET_LIQUIDITY_EVENTS.to_string()) && row.local_event_kind == Some("raydium_cpmm.lp_change_event".to_string()) })); + assert!(rows.iter().any(|row| return { + row.entry_name == "cpi_event" + && row.event_family == Some("cpi_transport".to_string()) + && row.expected_db_target + == Some( + crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string(), + ) + && row.local_event_kind == Some("raydium_cpmm.cpi_event".to_string()) + })); + assert!(rows.iter().any(|row| return { + row.entry_name == "anchor_idl_instruction" + && row.event_family == Some("idl_management".to_string()) + && row.expected_db_target + == Some( + crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string(), + ) + && row.local_event_kind == Some("raydium_cpmm.anchor_idl_instruction".to_string()) + })); assert!(rows.iter().any(|row| return { row.entry_name == "swap_event" && row.event_family == Some("swap".to_string()) diff --git a/kb_lib/src/dex_support_matrix.rs b/kb_lib/src/dex_support_matrix.rs index c5df0bc..f7fc4a4 100644 --- a/kb_lib/src/dex_support_matrix.rs +++ b/kb_lib/src/dex_support_matrix.rs @@ -248,25 +248,25 @@ const DEX_SUPPORT_MATRIX_ENTRIES: &[DexSupportMatrixEntry] = &[ catalog_enabled: true, }, DexSupportMatrixEntry { - code: "raydium_launchlab", + code: "raydium_launchpad", display_name: "Raydium LaunchLab / Launchpad", family: "raydium", - version: "launchlab", + version: "launchpad", surface_type: "launch", surface_role: "launch_surface", - program_id: Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + program_id: Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), router_program_id: None, program_id_status: "known", observed: false, - decoded: false, + decoded: true, materialized: false, trade_candidate: false, candle_candidate: false, pair_candidate: true, pool_candidate: true, - status: "planned", + status: "bootstrap", confidence: "medium", - skip_reason: Some("decoder_and_materialization_not_enabled"), + skip_reason: Some("decoded_events_only_until_local_corpus"), catalog_enabled: false, }, DexSupportMatrixEntry { @@ -2736,7 +2736,7 @@ mod tests { "raydium_cpmm", "raydium_clmm", "raydium_amm_v4", - "raydium_launchlab", + "raydium_launchpad", "raydium_liquidity_locking", "meteora_dlmm", "meteora_dlc", @@ -2832,11 +2832,25 @@ mod tests { assert_eq!(entry.skip_reason, Some("meteora_damm_v1_swap_without_amount_payload")); } + #[test] + fn matrix_marks_raydium_launchpad_bootstrap_conservatively() { + let entry = match crate::dex_support_matrix_entry_by_code("raydium_launchpad") { + Some(entry) => entry, + None => panic!("expected raydium_launchpad matrix entry"), + }; + assert_eq!(entry.status, "bootstrap"); + assert!(entry.decoded); + assert!(!entry.materialized); + assert!(!entry.trade_candidate); + assert!(!entry.candle_candidate); + assert_eq!(entry.skip_reason, Some("decoded_events_only_until_local_corpus")); + } + #[test] fn matrix_marks_launch_surfaces_as_launch_or_bonding_curve() { let codes = [ "pump_fun", - "raydium_launchlab", + "raydium_launchpad", "letsbonk", "bonk_fun", "bags", @@ -2863,15 +2877,7 @@ mod tests { #[test] fn matrix_keeps_0_7_39_deferred_surfaces_non_trade_materialized() { let codes = [ - "raydium_launchlab", - "letsbonk", - "bonk_fun", - "bags", - "moonshot", - "moonit", - "boop_fun", - "believe", - "heaven", + "letsbonk", "bonk_fun", "bags", "moonshot", "moonit", "boop_fun", "believe", "heaven", ]; for code in codes { let entry = match crate::dex_support_matrix_entry_by_code(code) { diff --git a/kb_lib/src/instruction_observation_index.rs b/kb_lib/src/instruction_observation_index.rs index 4af28d8..c398a2d 100644 --- a/kb_lib/src/instruction_observation_index.rs +++ b/kb_lib/src/instruction_observation_index.rs @@ -399,6 +399,20 @@ fn resolve_instruction_name( }; return Some(name.to_string()); } + + if program_id == crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID + || decoder_code == Some("raydium_launchpad") + { + if discriminator_hex == "e445a52e51cb9a1d" { + return Some("raydium_launchpad.anchor_self_cpi_log".to_string()); + } + let layout = crate::dex::raydium_launchpad::account_layout(discriminator_hex, usize::MAX); + let layout = match layout { + Some(layout) => layout, + None => return None, + }; + return Some(format!("raydium_launchpad.{}", layout.instruction_name)); + } return None; } diff --git a/kb_lib/src/launch_origin.rs b/kb_lib/src/launch_origin.rs index 7639b18..727509d 100644 --- a/kb_lib/src/launch_origin.rs +++ b/kb_lib/src/launch_origin.rs @@ -12,7 +12,7 @@ struct BuiltinLaunchSurfaceSpec { const BUILTIN_LAUNCH_SURFACE_SPECS: &[BuiltinLaunchSurfaceSpec] = &[ BuiltinLaunchSurfaceSpec { - code: "raydium_launchlab", + code: "raydium_launchpad", name: "Raydium LaunchLab / Launchpad", protocol_family: "raydium", enabled: true, @@ -984,7 +984,7 @@ mod tests { }; assert_eq!(surface_ids.len(), 9); let required_codes = [ - "raydium_launchlab", + "raydium_launchpad", "letsbonk", "bonk_fun", "bags", @@ -1018,7 +1018,7 @@ mod tests { let service = crate::LaunchOriginService::new(database.clone()); let register_result = service .register_launch_surface_mapping( - "raydium_launchlab", + "raydium_launchpad", Some("DbcDetectTokenA111"), Some("DbcDetectConfig111"), None, @@ -1026,23 +1026,23 @@ mod tests { ) .await; if let Err(error) = register_result { - panic!("launchlab mapping registration must succeed: {}", error); + panic!("launchpad mapping registration must succeed: {}", error); } - seed_decoded_meteora_dbc_event(database.clone(), "sig-launch-origin-launchlab-1").await; + seed_decoded_meteora_dbc_event(database.clone(), "sig-launch-origin-launchpad-1").await; let result = service - .attribute_transaction_by_signature("sig-launch-origin-launchlab-1") + .attribute_transaction_by_signature("sig-launch-origin-launchpad-1") .await; let results = match result { Ok(results) => results, - Err(error) => panic!("launchlab attribution must succeed: {}", error), + Err(error) => panic!("launchpad attribution must succeed: {}", error), }; assert_eq!(results.len(), 1); assert!(results[0].created_attribution); let surface_result = - crate::query_launch_surfaces_get_by_code(database.as_ref(), "raydium_launchlab").await; + crate::query_launch_surfaces_get_by_code(database.as_ref(), "raydium_launchpad").await; let surface = match surface_result { Ok(Some(surface)) => surface, - Ok(None) => panic!("raydium_launchlab surface must exist"), + Ok(None) => panic!("raydium_launchpad surface must exist"), Err(error) => panic!("surface fetch must succeed: {}", error), }; assert_eq!(surface.id, Some(results[0].launch_surface_id)); diff --git a/kb_lib/src/lib.rs b/kb_lib/src/lib.rs index c4f129f..8a27f8a 100644 --- a/kb_lib/src/lib.rs +++ b/kb_lib/src/lib.rs @@ -208,7 +208,7 @@ pub use constants::ED25519_PROGRAM_ID; /// Feature program identifier. ("Feature111111111111111111111111111111111111"). /// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::feature::ID pub use constants::FEATURE_PROGRAM_ID; -/// FluxBeam program id. ("FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X"). +/// Moonshot token authority account observed on Solscan program id. ("FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X"). pub use constants::FLUXBEAM_PROGRAM_ID; /// FusionAMM program id from Vybe supported DEX/AMM documentation. pub use constants::FUSIONAMM_PROGRAM_ID; @@ -278,10 +278,12 @@ pub use constants::METEORA_DAMM_V2_PROGRAM_ID; pub use constants::METEORA_DBC_PROGRAM_ID; /// Meteora DLMM program id. ("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"). pub use constants::METEORA_DLMM_PROGRAM_ID; -/// Meteora Vault program id extracted from upstream Git decoder source. +/// Meteora Vault program id. ("MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG"). pub use constants::METEORA_VAULT_PROGRAM_ID; /// Moonshot program id extracted from upstream Git decoder source. pub use constants::MOONSHOT_PROGRAM_ID; +/// FluxBeam program id. ("7rtiKSUDLBm59b1SBmD9oajcP8xE64vAGSMbAN5CXy1q"). +pub use constants::MOONSHOT_TOKEN_AUTHORITY_ID; /// MPL Core program id extracted from upstream Git decoder source. pub use constants::MPL_CORE_PROGRAM_ID; /// MPL Token Metadata program id extracted from upstream Git decoder source. @@ -325,8 +327,10 @@ pub use constants::RAYDIUM_AMM_V4_PROGRAM_ID; pub use constants::RAYDIUM_CLMM_PROGRAM_ID; /// Raydium CPMM mainnet program id. ("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"). pub use constants::RAYDIUM_CPMM_PROGRAM_ID; +/// Raydium LaunchLab platform config account observed on Solscan. ("4Bu96XjU84XjPDSpveTVf6LYGCkfW5FK7SNkREWcEfV4"). +pub use constants::RAYDIUM_LAUNCHPAD_PLATFORM_CONFIG_ACCOUNT_ID; /// Raydium LaunchLab / Launchpad program id. ("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"). -pub use constants::RAYDIUM_LAUNCHLAB_PROGRAM_ID; +pub use constants::RAYDIUM_LAUNCHPAD_PROGRAM_ID; /// Raydium Liquidity Locking program id extracted from upstream Git decoder source. pub use constants::RAYDIUM_LIQUIDITY_LOCKING_PROGRAM_ID; /// Raydium Stable Swap AMM program id, deprecated. ("5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h"). @@ -345,6 +349,8 @@ pub use constants::SHARKY_PROGRAM_ID; pub use constants::SOLAYER_RESTAKING_PROGRAM_ID; /// SolFi v2 program id from Vybe supported DEX/AMM documentation. pub use constants::SOLFI_V2_PROGRAM_ID; +/// Manual Solscan account-source inventory collected for post-`0.7.50` DEX discovery. +pub use constants::SOLSCAN_ACCOUNT_SOURCES; /// SPL Token-2022 program identifier. ("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"). /// @see solana_sdk::pubkey::Pubkey = spl_token_2022_interface::ID pub use constants::SPL_TOKEN_2022_PROGRAM_ID; @@ -405,6 +411,8 @@ pub use constants::SYSVAR_SLOT_HISTORY_PROGRAM_ID; /// Sysvar Stake History program identifier. ("SysvarStakeHistory1111111111111111111111111"). /// @see solana_sdk::pubkey::Pubkey = solana_sdk_ids::sysvar::stake_history::ID pub use constants::SYSVAR_STAKE_HISTORY_PROGRAM_ID; +/// One Solscan account source collected during manual DEX/program inventory. +pub use constants::SolscanAccountSource; /// Canonical Solana USDC mint identifier. pub use constants::USDC_MINT_ID; /// Canonical Solana USDT mint identifier. @@ -664,6 +672,10 @@ pub use db::SwapDto; pub use db::SwapEntity; /// Swap side relative to the normalized base token of the pair. pub use db::SwapTradeSide; +/// Application-facing normalized token-account event DTO. +pub use db::TokenAccountEventDto; +/// Persisted normalized token-account event row. +pub use db::TokenAccountEventEntity; /// Application-facing normalized token burn event DTO. pub use db::TokenBurnEventDto; /// Persisted normalized token burn event row. @@ -746,7 +758,10 @@ pub use db::query_dex_decoded_events_delete_locally_covered_upstream_instruction 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 Raydium CLMM instruction-audit rows for locally mapped CLMM instructions. 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. +pub use db::query_dex_decoded_events_delete_replaced_raydium_cpmm_instruction_audits; /// Reads one decoded DEX event by its natural key. pub use db::query_dex_decoded_events_get_by_key; /// Returns the latest Pump.fun create payload associated with a token mint. @@ -960,6 +975,8 @@ pub use db::query_reward_events_upsert; pub use db::query_swaps_list_recent; /// Inserts or updates one normalized swap row. pub use db::query_swaps_upsert; +/// Inserts or updates one normalized token-account event row. +pub use db::query_token_account_events_upsert; /// Lists recent token burn events ordered from newest to oldest. pub use db::query_token_burn_events_list_recent; /// Inserts or updates one normalized token burn event row. @@ -1247,6 +1264,12 @@ pub use dex_event_classification::is_dex_position_close_event_kind; pub use dex_event_classification::is_dex_position_open_event_kind; /// Returns true for reward or emission DEX events. pub use dex_event_classification::is_dex_reward_event_kind; +/// Returns true for token-account close events detected by DEX decoders. +pub use dex_event_classification::is_dex_token_account_close_event_kind; +/// Returns true for token-account creation events detected by DEX decoders. +pub use dex_event_classification::is_dex_token_account_create_event_kind; +/// Returns true for token-account lifecycle events detected by DEX decoders. +pub use dex_event_classification::is_dex_token_account_event_kind; /// Returns true for token burn DEX events. pub use dex_event_classification::is_dex_token_burn_event_kind; /// Returns true for token mint DEX events. diff --git a/kb_lib/src/local_pipeline_replay.rs b/kb_lib/src/local_pipeline_replay.rs index 9835faf..2a50c87 100644 --- a/kb_lib/src/local_pipeline_replay.rs +++ b/kb_lib/src/local_pipeline_replay.rs @@ -100,6 +100,8 @@ pub struct LocalPipelineReplayResult { pub pool_admin_event_count: usize, /// Total orderbook event materialization results returned by replayed non-trade calls. pub orderbook_event_count: usize, + /// Total token-account event materialization results returned by replayed non-trade calls. + pub token_account_event_count: usize, /// Total candle upsert results returned by replayed candle calls. /// /// This is a replay write/result counter, not the number of distinct rows @@ -367,6 +369,7 @@ impl LocalPipelineReplayService { result.reward_event_count += non_trade_result.reward_event_count; result.pool_admin_event_count += non_trade_result.pool_admin_event_count; result.orderbook_event_count += non_trade_result.orderbook_event_count; + result.token_account_event_count += non_trade_result.token_account_event_count; }, Err(error) => { result.non_trade_materialization_error_count += 1; @@ -521,6 +524,29 @@ impl LocalPipelineReplayService { } async fn refresh_event_coverage_best_effort(&self) { + let cpmm_cleanup_result = + crate::query_dex_decoded_events_delete_replaced_raydium_cpmm_instruction_audits( + self.database.as_ref(), + None, + ) + .await; + match cpmm_cleanup_result { + Ok(deleted_count) => { + if deleted_count > 0 { + tracing::info!( + deleted_count = deleted_count, + "replaced Raydium CPMM instruction audits cleaned before dex event coverage refresh" + ); + } + }, + Err(error) => { + tracing::warn!( + error = %error, + "Raydium CPMM replaced instruction-audit cleanup failed before dex event coverage refresh" + ); + }, + } + let cleanup_result = crate::query_dex_decoded_events_delete_replaced_raydium_clmm_instruction_audits( self.database.as_ref(), @@ -586,6 +612,46 @@ impl LocalPipelineReplayService { }, } + let post_refresh_cpmm_cleanup_result = + crate::query_dex_decoded_events_delete_replaced_raydium_cpmm_instruction_audits( + self.database.as_ref(), + None, + ) + .await; + match post_refresh_cpmm_cleanup_result { + Ok(deleted_count) => { + if deleted_count > 0 { + tracing::info!( + deleted_count = deleted_count, + "replaced Raydium CPMM instruction audits cleaned after dex event coverage refresh" + ); + let second_refresh_result = coverage_service.refresh_local_counts(None).await; + match second_refresh_result { + Ok(second_refresh_result) => { + tracing::debug!( + upserted_entry_count = second_refresh_result.upserted_entry_count, + refreshed_entry_count = second_refresh_result.refreshed_entry_count, + summary_count = second_refresh_result.summaries.len(), + "dex event coverage refreshed after Raydium CPMM instruction-audit cleanup" + ); + }, + Err(error) => { + tracing::warn!( + error = %error, + "dex event coverage refresh failed after Raydium CPMM instruction-audit cleanup" + ); + }, + } + } + }, + Err(error) => { + tracing::warn!( + error = %error, + "Raydium CPMM replaced instruction-audit cleanup failed after dex event coverage refresh" + ); + }, + } + let post_refresh_upstream_cleanup_result = crate::query_dex_decoded_events_delete_locally_covered_upstream_instruction_matches( self.database.as_ref(), diff --git a/kb_lib/src/non_trade_event_materialization.rs b/kb_lib/src/non_trade_event_materialization.rs index 03e9f06..c531c9c 100644 --- a/kb_lib/src/non_trade_event_materialization.rs +++ b/kb_lib/src/non_trade_event_materialization.rs @@ -21,6 +21,10 @@ pub struct NonTradeEventMaterializationResult { pub pool_admin_event_count: usize, /// Number of orderbook or limit-order events inserted or refreshed. pub orderbook_event_count: usize, + /// Number of launch-surface specific events inserted or refreshed. + pub launch_event_count: usize, + /// Number of token-account events inserted or refreshed. + pub token_account_event_count: usize, } /// Materializes useful non-trade decoded DEX events. @@ -29,6 +33,7 @@ pub struct NonTradeEventMaterializationService { database: std::sync::Arc, } +#[derive(Debug, Clone)] struct NonTradeDecodedEventContext { dex_id: std::option::Option, pool_id: std::option::Option, @@ -209,6 +214,37 @@ impl NonTradeEventMaterializationService { Err(error) => return Err(error), } } + if is_token_account_event_materializable(decoded_event.event_kind.as_str()) { + let materialized = self + .materialize_token_account_event( + &transaction, + transaction_id, + decoded_event, + &payload, + ) + .await; + match materialized { + Ok(was_materialized) => { + if was_materialized { + result.token_account_event_count += 1; + } + }, + Err(error) => return Err(error), + } + } + if is_launchpad_launch_event_materializable(decoded_event.event_kind.as_str()) { + let materialized = self + .materialize_launch_event(&transaction, transaction_id, decoded_event, &payload) + .await; + match materialized { + Ok(was_materialized) => { + if was_materialized { + result.launch_event_count += 1; + } + }, + Err(error) => return Err(error), + } + } } for decoded_event in &decoded_events { if !decoded_event.event_kind.ends_with(".lp_change_event") { @@ -531,7 +567,7 @@ WHERE decoded_event_id = ? } } - async fn materialize_liquidity_event( + async fn materialize_token_account_event( &self, transaction: &crate::ChainTransactionDto, transaction_id: i64, @@ -547,15 +583,281 @@ WHERE decoded_event_id = ? Ok(context) => context, Err(error) => return Err(error), }; - let context = if context.pool_id.is_some() && context.pair.is_some() { - context - } else { - let ensured_context = - self.ensure_liquidity_context_from_decoded_event(decoded_event, context).await; - match ensured_context { - Ok(ensured_context) => ensured_context, - Err(error) => return Err(error), - } + let token_account = match extract_first_string( + payload, + &[ + "supportMintAssociated", + "support_mint_associated", + "tokenAccount", + "token_account", + ], + ) { + Some(token_account) => Some(token_account), + None => extract_account_string(payload, 2), + }; + let token_mint = match extract_first_string(payload, &["tokenMint", "token_mint", "mint"]) { + Some(token_mint) => Some(token_mint), + None => extract_account_string(payload, 1), + }; + let owner_wallet = match extract_first_string(payload, &["owner", "authority", "payer"]) { + Some(owner_wallet) => Some(owner_wallet), + None => extract_account_string(payload, 0), + }; + let instruction_index = match extract_first_i64( + payload, + &["instructionIndex", "instruction_index", "outerInstructionIndex"], + ) { + Some(instruction_index) => instruction_index, + None => decoded_event_id, + }; + let dto = crate::TokenAccountEventDto::new( + Some(transaction_id), + Some(decoded_event_id), + context.dex_id, + context.pool_id, + context.pair_id, + transaction.signature.clone(), + instruction_index, + transaction.slot, + decoded_event.protocol_name.clone(), + Some(decoded_event.program_id.clone()), + decoded_event.event_kind.clone(), + token_account, + token_mint, + owner_wallet, + Some("create_support_mint_associated".to_string()), + Some(decoded_event.payload_json.clone()), + ); + let upsert_result = + crate::query_token_account_events_upsert(self.database.as_ref(), &dto).await; + match upsert_result { + Ok(_) => return Ok(true), + Err(error) => return Err(error), + } + } + + async fn materialize_launch_event( + &self, + transaction: &crate::ChainTransactionDto, + transaction_id: i64, + decoded_event: &crate::DexDecodedEventDto, + payload: &serde_json::Value, + ) -> Result { + let decoded_event_id = match decoded_event.id { + Some(decoded_event_id) => decoded_event_id, + None => return Ok(false), + }; + let context = self.resolve_decoded_event_context(decoded_event).await; + let context = match context { + Ok(context) => context, + Err(error) => return Err(error), + }; + let actor_wallet = extract_first_string( + payload, + &[ + "actorWallet", + "actor_wallet", + "beneficiary", + "owner", + "payer", + "authority", + "user", + "creator", + "platformVestingWallet", + "platform_vesting_wallet", + ], + ); + let event_role = launchpad_launch_event_role(decoded_event.event_kind.as_str()); + let related_account = extract_first_string( + payload, + &[ + "platformVestingRecord", + "platform_vesting_record", + "platformGlobalAccess", + "platform_global_access", + "vestingRecord", + "vesting_record", + "config", + "platformConfig", + "platform_config", + "poolState", + "pool_state", + "poolAccount", + ], + ); + let related_mint = extract_first_string( + payload, + &[ + "baseMint", + "base_mint", + "baseTokenMint", + "base_token_mint", + "tokenMint", + "token_mint", + "mint", + ], + ); + let slot_i64 = match transaction.slot { + Some(slot) => { + let converted = i64::try_from(slot); + match converted { + Ok(converted) => Some(converted), + Err(error) => { + return Err(crate::Error::Db(format!( + "cannot convert launch event slot '{}' to i64: {}", + slot, error + ))); + }, + } + }, + None => None, + }; + match self.database.connection() { + crate::DatabaseConnection::Sqlite(pool) => { + let existing_result = sqlx::query_scalar::( + r#" +SELECT id +FROM k_sol_launch_events +WHERE decoded_event_id = ? +LIMIT 1 + "#, + ) + .bind(decoded_event_id) + .fetch_optional(pool) + .await; + let existing_id = match existing_result { + Ok(existing_id) => existing_id, + Err(error) => { + return Err(crate::Error::Db(format!( + "cannot fetch k_sol_launch_events id for decoded_event_id '{}' on sqlite: {}", + decoded_event_id, error + ))); + }, + }; + if let Some(existing_id) = existing_id { + let update_result = sqlx::query( + r#" +UPDATE k_sol_launch_events +SET + transaction_id = ?, + dex_id = ?, + pool_id = ?, + pair_id = ?, + signature = ?, + slot = ?, + protocol_name = ?, + program_id = ?, + event_kind = ?, + pool_account = ?, + actor_wallet = ?, + event_role = ?, + related_account = ?, + related_mint = ?, + payload_json = ?, + executed_at = ? +WHERE id = ? + "#, + ) + .bind(transaction_id) + .bind(context.dex_id) + .bind(context.pool_id) + .bind(context.pair_id) + .bind(transaction.signature.clone()) + .bind(slot_i64) + .bind(decoded_event.protocol_name.clone()) + .bind(decoded_event.program_id.clone()) + .bind(decoded_event.event_kind.clone()) + .bind(decoded_event.pool_account.clone()) + .bind(actor_wallet.clone()) + .bind(event_role.clone()) + .bind(related_account.clone()) + .bind(related_mint.clone()) + .bind(decoded_event.payload_json.clone()) + .bind(chrono::Utc::now().to_rfc3339()) + .bind(existing_id) + .execute(pool) + .await; + if let Err(error) = update_result { + return Err(crate::Error::Db(format!( + "cannot update k_sol_launch_events id '{}' on sqlite: {}", + existing_id, error + ))); + } + return Ok(true); + } + let insert_result = sqlx::query( + r#" +INSERT INTO k_sol_launch_events ( + transaction_id, + decoded_event_id, + dex_id, + pool_id, + pair_id, + signature, + slot, + protocol_name, + program_id, + event_kind, + pool_account, + actor_wallet, + event_role, + related_account, + related_mint, + payload_json, + executed_at, + created_at +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + "#, + ) + .bind(transaction_id) + .bind(decoded_event_id) + .bind(context.dex_id) + .bind(context.pool_id) + .bind(context.pair_id) + .bind(transaction.signature.clone()) + .bind(slot_i64) + .bind(decoded_event.protocol_name.clone()) + .bind(decoded_event.program_id.clone()) + .bind(decoded_event.event_kind.clone()) + .bind(decoded_event.pool_account.clone()) + .bind(actor_wallet) + .bind(event_role) + .bind(related_account) + .bind(related_mint) + .bind(decoded_event.payload_json.clone()) + .bind(chrono::Utc::now().to_rfc3339()) + .bind(chrono::Utc::now().to_rfc3339()) + .execute(pool) + .await; + if let Err(error) = insert_result { + return Err(crate::Error::Db(format!( + "cannot insert k_sol_launch_events on sqlite: {}", + error + ))); + } + return Ok(true); + }, + } + } + + async fn materialize_liquidity_event( + &self, + transaction: &crate::ChainTransactionDto, + transaction_id: i64, + decoded_event: &crate::DexDecodedEventDto, + payload: &serde_json::Value, + ) -> Result { + let decoded_event_id = match decoded_event.id { + Some(decoded_event_id) => decoded_event_id, + None => return Ok(false), + }; + let context = self + .resolve_liquidity_context(transaction, transaction_id, decoded_event) + .await; + let context = match context { + Ok(context) => context, + Err(error) => return Err(error), }; let dex_id = match context.dex_id { Some(dex_id) => dex_id, @@ -615,6 +917,11 @@ WHERE decoded_event_id = ? "token_a_amount", "token0AmountRaw", "token_0_amount_raw", + "token0Amount", + "token_0_amount", + "amount0Raw", + "amount0_raw", + "amount0", "amount0RequestedRaw", "amount_0_requested_raw", "amountA", @@ -634,6 +941,11 @@ WHERE decoded_event_id = ? "token_b_amount", "token1AmountRaw", "token_1_amount_raw", + "token1Amount", + "token_1_amount", + "amount1Raw", + "amount1_raw", + "amount1", "amount1RequestedRaw", "amount_1_requested_raw", "amountB", @@ -648,6 +960,8 @@ WHERE decoded_event_id = ? "lpAmount", "lp_amount", "liquidity", + "liquidity_raw", + "liquidityRaw", "liquidityAmount", "liquidity_amount", ], @@ -773,6 +1087,247 @@ WHERE decoded_event_id = ? } } + async fn resolve_liquidity_context( + &self, + transaction: &crate::ChainTransactionDto, + transaction_id: i64, + decoded_event: &crate::DexDecodedEventDto, + ) -> Result { + let context = self.resolve_decoded_event_context(decoded_event).await; + let context = match context { + Ok(context) => context, + Err(error) => return Err(error), + }; + let context = if context.pool_id.is_some() && context.pair.is_some() { + context + } else { + let ensured_context = + self.ensure_liquidity_context_from_decoded_event(decoded_event, context).await; + match ensured_context { + Ok(ensured_context) => ensured_context, + Err(error) => return Err(error), + } + }; + if context.pool_id.is_some() && context.pair.is_some() { + return Ok(context); + } + let sibling_context = self + .resolve_liquidity_context_from_transaction_siblings(transaction_id, decoded_event) + .await; + let context = match sibling_context { + Ok(Some(sibling_context)) => return Ok(sibling_context), + Ok(None) => context, + Err(error) => return Err(error), + }; + let inferred_context = self + .resolve_liquidity_context_from_transaction_token_balances( + transaction, + transaction_id, + decoded_event, + context.clone(), + ) + .await; + match inferred_context { + Ok(Some(inferred_context)) => return Ok(inferred_context), + Ok(None) => return Ok(context), + Err(error) => return Err(error), + } + } + + async fn resolve_liquidity_context_from_transaction_siblings( + &self, + transaction_id: i64, + decoded_event: &crate::DexDecodedEventDto, + ) -> Result, crate::Error> { + let decoded_events_result = crate::query_dex_decoded_events_list_by_transaction_id( + self.database.as_ref(), + transaction_id, + ) + .await; + let decoded_events = match decoded_events_result { + Ok(decoded_events) => decoded_events, + Err(error) => return Err(error), + }; + let decoded_event_id = decoded_event.id; + for sibling in &decoded_events { + if sibling.id == decoded_event_id { + continue; + } + if sibling.protocol_name != decoded_event.protocol_name { + continue; + } + if sibling.pool_account.is_none() { + continue; + } + let sibling_context = self.resolve_decoded_event_context(sibling).await; + let sibling_context = match sibling_context { + Ok(sibling_context) => sibling_context, + Err(error) => return Err(error), + }; + let sibling_context = + if sibling_context.pool_id.is_some() && sibling_context.pair.is_some() { + sibling_context + } else { + let ensured_context = self + .ensure_liquidity_context_from_decoded_event(sibling, sibling_context) + .await; + match ensured_context { + Ok(ensured_context) => ensured_context, + Err(error) => return Err(error), + } + }; + if sibling_context.pool_id.is_some() && sibling_context.pair.is_some() { + return Ok(Some(sibling_context)); + } + } + return Ok(None); + } + + async fn resolve_liquidity_context_from_transaction_token_balances( + &self, + transaction: &crate::ChainTransactionDto, + transaction_id: i64, + decoded_event: &crate::DexDecodedEventDto, + context: NonTradeDecodedEventContext, + ) -> Result, crate::Error> { + let dex_id = match context.dex_id { + Some(dex_id) => dex_id, + None => return Ok(None), + }; + let token_mints_by_account = token_mints_by_account_from_transaction(transaction); + if token_mints_by_account.is_empty() { + return Ok(None); + } + let decoded_events_result = crate::query_dex_decoded_events_list_by_transaction_id( + self.database.as_ref(), + transaction_id, + ) + .await; + let decoded_events = match decoded_events_result { + Ok(decoded_events) => decoded_events, + Err(error) => return Err(error), + }; + let target_payload_result = serde_json::from_str::( + decoded_event.payload_json.as_str(), + ); + let target_payload = match target_payload_result { + Ok(target_payload) => target_payload, + Err(_) => serde_json::Value::Object(serde_json::Map::new()), + }; + for candidate in &decoded_events { + if candidate.protocol_name != decoded_event.protocol_name { + continue; + } + if !candidate.event_kind.starts_with("raydium_clmm.") { + continue; + } + let candidate_payload_result = serde_json::from_str::( + candidate.payload_json.as_str(), + ); + let candidate_payload = match candidate_payload_result { + Ok(candidate_payload) => candidate_payload, + Err(_) => serde_json::Value::Object(serde_json::Map::new()), + }; + let pool_account = match candidate.pool_account.clone() { + Some(pool_account) => Some(pool_account), + None => extract_first_string( + &candidate_payload, + &["poolState", "pool_state", "poolAccount", "pool_account"], + ), + }; + let pool_account = match pool_account { + Some(pool_account) => pool_account, + None => continue, + }; + let direct_token_a = match candidate.token_a_mint.clone() { + Some(token_a_mint) => Some(token_a_mint), + None => extract_first_string( + &candidate_payload, + &[ + "tokenMint0", + "token_mint0", + "tokenMintA", + "token_mint_a", + "baseMint", + "base_mint", + ], + ), + }; + let direct_token_b = match candidate.token_b_mint.clone() { + Some(token_b_mint) => Some(token_b_mint), + None => extract_first_string( + &candidate_payload, + &[ + "tokenMint1", + "token_mint1", + "tokenMintB", + "token_mint_b", + "quoteMint", + "quote_mint", + ], + ), + }; + let inferred_pair = infer_raydium_clmm_pair_mints_from_payload_accounts( + &candidate_payload, + &token_mints_by_account, + ); + let token_a_mint = match direct_token_a { + Some(token_a_mint) => Some(token_a_mint), + None => match inferred_pair.as_ref() { + Some(pair) => Some(pair.0.clone()), + None => None, + }, + }; + let token_b_mint = match direct_token_b { + Some(token_b_mint) => Some(token_b_mint), + None => match inferred_pair.as_ref() { + Some(pair) => Some(pair.1.clone()), + None => None, + }, + }; + let token_a_mint = match token_a_mint { + Some(token_a_mint) => token_a_mint, + None => continue, + }; + let token_b_mint = match token_b_mint { + Some(token_b_mint) => token_b_mint, + None => continue, + }; + if token_a_mint == token_b_mint { + continue; + } + let synthetic_payload = merge_payload_with_inferred_pool_context( + &target_payload, + pool_account.as_str(), + token_a_mint.as_str(), + token_b_mint.as_str(), + candidate.event_kind.as_str(), + ); + let mut synthetic_event = decoded_event.clone(); + synthetic_event.pool_account = Some(pool_account); + synthetic_event.token_a_mint = Some(token_a_mint); + synthetic_event.token_b_mint = Some(token_b_mint); + synthetic_event.payload_json = synthetic_payload.to_string(); + let synthetic_context = NonTradeDecodedEventContext { + dex_id: Some(dex_id), + pool_id: None, + pair_id: None, + pair: None, + }; + let ensured = self + .ensure_liquidity_context_from_decoded_event(&synthetic_event, synthetic_context) + .await; + let ensured = match ensured { + Ok(ensured) => ensured, + Err(error) => return Err(error), + }; + if ensured.pool_id.is_some() && ensured.pair.is_some() { + return Ok(Some(ensured)); + } + } + return Ok(None); + } + async fn ensure_liquidity_context_from_decoded_event( &self, decoded_event: &crate::DexDecodedEventDto, @@ -889,6 +1444,386 @@ WHERE decoded_event_id = ? } } +#[derive(Debug, Clone)] +struct MaterializationAccountKeyInfo { + index: i64, + address: std::string::String, +} + +fn token_mints_by_account_from_transaction( + transaction: &crate::ChainTransactionDto, +) -> std::collections::HashMap { + let transaction_json = serde_json::from_str::( + transaction.transaction_json.as_str(), + ); + let transaction_json = match transaction_json { + Ok(transaction_json) => transaction_json, + Err(_) => return std::collections::HashMap::new(), + }; + let meta_value = match transaction.meta_json.as_deref() { + Some(meta_json) => match serde_json::from_str::(meta_json) { + Ok(meta_value) => Some(meta_value), + Err(_) => None, + }, + None => transaction_json.get("meta").cloned(), + }; + let account_keys = materialization_account_keys(&transaction_json, meta_value.as_ref()); + let mut mints = std::collections::HashMap::new(); + collect_token_mints_by_account_side( + meta_value.as_ref(), + account_keys.as_slice(), + "preTokenBalances", + &mut mints, + ); + collect_token_mints_by_account_side( + meta_value.as_ref(), + account_keys.as_slice(), + "postTokenBalances", + &mut mints, + ); + return mints; +} + +fn materialization_account_keys( + transaction_json: &serde_json::Value, + meta_value: std::option::Option<&serde_json::Value>, +) -> std::vec::Vec { + let mut account_keys = std::vec::Vec::new(); + let values = transaction_json + .get("transaction") + .and_then(|value| return value.get("message")) + .and_then(|value| return value.get("accountKeys")) + .and_then(serde_json::Value::as_array); + if let Some(values) = values { + let mut index = 0usize; + for value in values { + let address = if let Some(address) = value.as_str() { + Some(address.to_string()) + } else { + value.get("pubkey").and_then(serde_json::Value::as_str).map(str::to_string) + }; + if let Some(address) = address { + account_keys.push(MaterializationAccountKeyInfo { + index: index as i64, + address, + }); + } + index += 1; + } + } + append_materialization_loaded_addresses(&mut account_keys, meta_value, "writable"); + append_materialization_loaded_addresses(&mut account_keys, meta_value, "readonly"); + return account_keys; +} + +fn append_materialization_loaded_addresses( + account_keys: &mut std::vec::Vec, + meta_value: std::option::Option<&serde_json::Value>, + key: &str, +) { + let addresses = meta_value + .and_then(|value| return value.get("loadedAddresses")) + .and_then(|value| return value.get(key)) + .and_then(serde_json::Value::as_array); + let addresses = match addresses { + Some(addresses) => addresses, + None => return, + }; + for value in addresses { + let address = match value.as_str() { + Some(address) => address, + None => continue, + }; + let index = account_keys.len() as i64; + account_keys.push(MaterializationAccountKeyInfo { + index, + address: address.to_string(), + }); + } +} + +fn collect_token_mints_by_account_side( + meta_value: std::option::Option<&serde_json::Value>, + account_keys: &[MaterializationAccountKeyInfo], + key: &str, + mints: &mut std::collections::HashMap, +) { + let values = meta_value + .and_then(|value| return value.get(key)) + .and_then(serde_json::Value::as_array); + let values = match values { + Some(values) => values, + None => return, + }; + for value in values { + let mint = match value.get("mint").and_then(serde_json::Value::as_str) { + Some(mint) => mint, + None => continue, + }; + let account_index = value.get("accountIndex").and_then(serde_json::Value::as_i64); + let account_index = match account_index { + Some(account_index) => account_index, + None => continue, + }; + let account_address = materialization_account_address_by_index(account_keys, account_index); + let account_address = match account_address { + Some(account_address) => account_address, + None => continue, + }; + mints.insert(account_address, mint.to_string()); + } +} + +fn materialization_account_address_by_index( + account_keys: &[MaterializationAccountKeyInfo], + account_index: i64, +) -> std::option::Option { + for account_key in account_keys { + if account_key.index == account_index { + return Some(account_key.address.clone()); + } + } + return None; +} + +fn infer_raydium_clmm_pair_mints_from_payload_accounts( + payload: &serde_json::Value, + token_mints_by_account: &std::collections::HashMap, +) -> std::option::Option<(std::string::String, std::string::String)> { + let accounts = payload.get("accounts").and_then(serde_json::Value::as_array); + let accounts = match accounts { + Some(accounts) => accounts, + None => return None, + }; + let instruction_name = payload + .get("instructionName") + .and_then(serde_json::Value::as_str); + let instruction_name = match instruction_name { + Some(instruction_name) => instruction_name, + None => "", + }; + let candidate_pairs = raydium_clmm_token_account_candidate_pairs(instruction_name); + for pair in candidate_pairs { + let inferred = infer_mints_from_account_pair( + accounts, + pair.0, + pair.1, + token_mints_by_account, + ); + if let Some(inferred) = inferred { + return Some(inferred); + } + } + return infer_distinct_mints_from_accounts(accounts, token_mints_by_account); +} + +fn raydium_clmm_token_account_candidate_pairs( + instruction_name: &str, +) -> std::vec::Vec<(usize, usize)> { + if instruction_name == "open_position" { + return vec![(12, 13), (10, 11), (18, 19), (20, 21), (7, 8), (8, 9)]; + } + if instruction_name == "open_position_v2" { + return vec![(12, 13), (13, 14), (18, 19), (20, 21), (10, 11), (7, 8)]; + } + if instruction_name == "increase_liquidity" { + return vec![(9, 10), (7, 8), (13, 14), (14, 15), (5, 6)]; + } + if instruction_name == "decrease_liquidity" { + return vec![(5, 6), (9, 10), (14, 15), (15, 16), (7, 8)]; + } + if instruction_name == "decrease_liquidity_v2" { + return vec![(14, 15), (5, 6), (9, 10)]; + } + if instruction_name == "increase_liquidity_v2" { + return vec![(13, 14), (9, 10), (7, 8)]; + } + return vec![(12, 13), (13, 14), (9, 10), (7, 8), (5, 6), (10, 11), (14, 15), (18, 19), (20, 21)]; +} + +fn infer_mints_from_account_pair( + accounts: &[serde_json::Value], + left_index: usize, + right_index: usize, + token_mints_by_account: &std::collections::HashMap, +) -> std::option::Option<(std::string::String, std::string::String)> { + let left_account = accounts.get(left_index).and_then(serde_json::Value::as_str); + let right_account = accounts.get(right_index).and_then(serde_json::Value::as_str); + let left_account = match left_account { + Some(left_account) => left_account, + None => return None, + }; + let right_account = match right_account { + Some(right_account) => right_account, + None => return None, + }; + let left_mint = token_mints_by_account.get(left_account); + let right_mint = token_mints_by_account.get(right_account); + let left_mint = match left_mint { + Some(left_mint) => left_mint, + None => return None, + }; + let right_mint = match right_mint { + Some(right_mint) => right_mint, + None => return None, + }; + if left_mint == right_mint { + return None; + } + return Some((left_mint.clone(), right_mint.clone())); +} + +fn infer_distinct_mints_from_accounts( + accounts: &[serde_json::Value], + token_mints_by_account: &std::collections::HashMap, +) -> std::option::Option<(std::string::String, std::string::String)> { + let mut first: std::option::Option = None; + let mut second: std::option::Option = None; + for account in accounts { + let account = match account.as_str() { + Some(account) => account, + None => continue, + }; + let mint = token_mints_by_account.get(account); + let mint = match mint { + Some(mint) => mint, + None => continue, + }; + if first.is_none() { + first = Some(mint.clone()); + continue; + } + if first.as_ref() == Some(mint) { + continue; + } + second = Some(mint.clone()); + break; + } + match (first, second) { + (Some(first), Some(second)) => return Some((first, second)), + _ => return None, + } +} + +fn merge_payload_with_inferred_pool_context( + payload: &serde_json::Value, + pool_account: &str, + token_a_mint: &str, + token_b_mint: &str, + context_source_event_kind: &str, +) -> serde_json::Value { + let mut object = match payload.clone() { + serde_json::Value::Object(object) => object, + other => { + let mut object = serde_json::Map::new(); + object.insert("rawPayload".to_string(), other); + object + }, + }; + object.insert("poolState".to_string(), serde_json::Value::String(pool_account.to_string())); + object.insert("poolAccount".to_string(), serde_json::Value::String(pool_account.to_string())); + object.insert("tokenMint0".to_string(), serde_json::Value::String(token_a_mint.to_string())); + object.insert("tokenMint1".to_string(), serde_json::Value::String(token_b_mint.to_string())); + object.insert("inferredPoolContext".to_string(), serde_json::Value::Bool(true)); + object.insert( + "inferredPoolContextSourceEventKind".to_string(), + serde_json::Value::String(context_source_event_kind.to_string()), + ); + return serde_json::Value::Object(object); +} + +fn is_token_account_event_materializable(event_kind: &str) -> bool { + return event_kind.ends_with(".create_support_mint_associated"); +} + +fn extract_account_string( + payload: &serde_json::Value, + index: usize, +) -> std::option::Option { + let accounts_option = payload.get("accounts"); + let accounts = match accounts_option { + Some(accounts) => accounts.as_array(), + None => None, + }; + let accounts = match accounts { + Some(accounts) => accounts, + None => return None, + }; + let value = accounts.get(index); + match value { + Some(value) => match value.as_str() { + Some(value) => return Some(value.to_string()), + None => return None, + }, + None => return None, + } +} + +fn is_launchpad_launch_event_materializable(event_kind: &str) -> bool { + if event_kind.contains("raydium_launchpad.buy_exact_in") { + return true; + } + if event_kind.contains("raydium_launchpad.buy_exact_out") { + return true; + } + if event_kind.contains("raydium_launchpad.sell_exact_in") { + return true; + } + if event_kind.contains("raydium_launchpad.sell_exact_out") { + return true; + } + if event_kind.contains("raydium_launchpad.claim_vested_event") { + return true; + } + if event_kind.contains("raydium_launchpad.claim_vested_token") { + return true; + } + if event_kind.contains("raydium_launchpad.create_platform_vesting_account") { + return true; + } + if event_kind.contains("raydium_launchpad.create_vesting_account") { + return true; + } + if event_kind.contains("raydium_launchpad.create_vesting_event") { + return true; + } + if event_kind.contains("raydium_launchpad.migrate_to_amm") { + return true; + } + if event_kind.contains("raydium_launchpad.migrate_to_cpswap") { + return true; + } + return false; +} + +fn launchpad_launch_event_role(event_kind: &str) -> std::string::String { + if event_kind.contains("buy_exact_in") { + return "swap_instruction_buy_exact_in".to_string(); + } + if event_kind.contains("buy_exact_out") { + return "swap_instruction_buy_exact_out".to_string(); + } + if event_kind.contains("sell_exact_in") { + return "swap_instruction_sell_exact_in".to_string(); + } + if event_kind.contains("sell_exact_out") { + return "swap_instruction_sell_exact_out".to_string(); + } + if event_kind.contains("claim_vested") { + return "vesting_claim".to_string(); + } + if event_kind.contains("vesting") { + return "vesting".to_string(); + } + if event_kind.contains("migrate_to_amm") { + return "migration_to_amm".to_string(); + } + if event_kind.contains("migrate_to_cpswap") { + return "migration_to_cpswap".to_string(); + } + return "launch".to_string(); +} + fn normalize_orderbook_action(event_kind: &str) -> std::string::String { if event_kind.contains(".open_limit_order") { return "open_limit_order".to_string(); diff --git a/kb_lib/src/trade_amount_resolution.rs b/kb_lib/src/trade_amount_resolution.rs index c0ca70a..f16e5e9 100644 --- a/kb_lib/src/trade_amount_resolution.rs +++ b/kb_lib/src/trade_amount_resolution.rs @@ -115,6 +115,44 @@ pub(crate) async fn resolve_trade_amounts( return Err(error); } } + let has_payload_trade_side = crate::trade_amount_resolution::extract_string_by_candidate_keys( + input.payload, + &["tradeSide", "trade_side"], + ) + .is_some(); + if input.decoded_event.event_kind == "raydium_launchpad.trade_event" + && (base_amount_raw.is_none() + || quote_amount_raw.is_none() + || (!has_payload_trade_side && resolved_trade_side.is_none())) + { + let resolution_result = + crate::trade_amount_resolution::apply_raydium_launchpad_sibling_instruction_amount_fallback( + input, + &mut base_amount_raw, + &mut quote_amount_raw, + &mut resolved_trade_side, + ) + .await; + if let Err(error) = resolution_result { + return Err(error); + } + } + if input.decoded_event.event_kind.starts_with("raydium_launchpad.") + && (base_amount_raw.is_none() + || quote_amount_raw.is_none() + || price_quote_per_base.is_none()) + { + let resolution_result = + crate::trade_amount_resolution::apply_raydium_launchpad_amount_fallback( + input, + &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_amm_v4.") && (base_amount_raw.is_none() || quote_amount_raw.is_none()) { @@ -504,6 +542,223 @@ async fn apply_pump_swap_amount_fallbacks( return Ok(()); } +fn apply_raydium_launchpad_amount_fallback( + input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>, + base_amount_raw: &mut std::option::Option, + quote_amount_raw: &mut std::option::Option, + price_quote_per_base: &mut std::option::Option, +) -> Result<(), crate::Error> { + if base_amount_raw.is_some() && quote_amount_raw.is_some() && price_quote_per_base.is_some() { + return Ok(()); + } + let transaction_value_result = + crate::trade_pump_swap_amounts::build_transaction_value_with_meta_json( + input.transaction.transaction_json.as_str(), + input.transaction.meta_json.as_deref(), + ); + let transaction_value = match transaction_value_result { + Ok(transaction_value) => transaction_value, + Err(error) => return Err(error), + }; + let fallback_amounts = match (input.base_token_mint, input.quote_token_mint) { + (Some(base_mint), Some(quote_mint)) => { + crate::trade_pump_swap_amounts::try_build_pump_swap_trade_amounts_from_token_balance_deltas( + &transaction_value, + base_mint, + quote_mint, + ) + }, + _ => None, + }; + let fallback_amounts = match fallback_amounts { + Some(fallback_amounts) => fallback_amounts, + None => return Ok(()), + }; + if base_amount_raw.is_none() { + *base_amount_raw = crate::trade_pump_swap_amounts::convert_ui_amount_to_raw_string( + fallback_amounts.base_amount, + input.base_token_decimals, + ); + } + if quote_amount_raw.is_none() { + *quote_amount_raw = crate::trade_pump_swap_amounts::convert_ui_amount_to_raw_string( + fallback_amounts.quote_amount, + input.quote_token_decimals, + ); + } + if price_quote_per_base.is_none() { + *price_quote_per_base = Some(fallback_amounts.price_quote_per_base); + } + tracing::debug!( + event_kind = %input.decoded_event.event_kind, + pool_account = ?input.decoded_event.pool_account, + decoded_event_id = ?input.decoded_event.id, + base_mint = ?input.base_token_mint, + quote_mint = ?input.quote_token_mint, + base_amount_raw = ?base_amount_raw, + quote_amount_raw = ?quote_amount_raw, + price_quote_per_base = ?price_quote_per_base, + "raydium_launchpad trade amounts recovered from transaction token balance deltas" + ); + return Ok(()); +} + +async fn apply_raydium_launchpad_sibling_instruction_amount_fallback( + input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>, + base_amount_raw: &mut std::option::Option, + quote_amount_raw: &mut std::option::Option, + resolved_trade_side: &mut std::option::Option, +) -> Result<(), crate::Error> { + if base_amount_raw.is_some() && quote_amount_raw.is_some() && resolved_trade_side.is_some() { + return Ok(()); + } + let amount_in_raw = crate::trade_amount_resolution::extract_amount_string( + input.payload, + &["amountInRaw", "amount_in_raw", "amountIn", "amount_in"], + ); + let amount_out_raw = crate::trade_amount_resolution::extract_amount_string( + input.payload, + &["amountOutRaw", "amount_out_raw", "amountOut", "amount_out"], + ); + let amount_in_raw = match amount_in_raw { + Some(amount_in_raw) => amount_in_raw, + None => return Ok(()), + }; + let amount_out_raw = match amount_out_raw { + Some(amount_out_raw) => amount_out_raw, + None => return Ok(()), + }; + let sibling_side_result = + crate::trade_amount_resolution::resolve_raydium_launchpad_sibling_instruction_side(input) + .await; + let sibling_side = match sibling_side_result { + Ok(sibling_side) => sibling_side, + Err(error) => return Err(error), + }; + let sibling_side = match sibling_side { + Some(sibling_side) => sibling_side, + None => return Ok(()), + }; + crate::trade_amount_resolution::apply_raydium_launchpad_side_amount_mapping( + sibling_side, + amount_in_raw.as_str(), + amount_out_raw.as_str(), + base_amount_raw, + quote_amount_raw, + resolved_trade_side, + ); + tracing::debug!( + event_kind = %input.decoded_event.event_kind, + pool_account = ?input.decoded_event.pool_account, + decoded_event_id = ?input.decoded_event.id, + transaction_signature = %input.transaction.signature, + base_amount_raw = ?base_amount_raw, + quote_amount_raw = ?quote_amount_raw, + resolved_trade_side = ?resolved_trade_side, + "raydium_launchpad trade amounts recovered from sibling swap instruction" + ); + return Ok(()); +} + +async fn resolve_raydium_launchpad_sibling_instruction_side( + input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>, +) -> Result, crate::Error> { + let decoded_events_result = crate::query_dex_decoded_events_list_by_transaction_id( + input.database, + input.decoded_event.transaction_id, + ) + .await; + let decoded_events = match decoded_events_result { + Ok(decoded_events) => decoded_events, + Err(error) => return Err(error), + }; + let mut resolved_side = None; + for decoded_event in decoded_events { + if decoded_event.id == input.decoded_event.id { + continue; + } + if decoded_event.protocol_name != "raydium_launchpad" { + continue; + } + if decoded_event.pool_account.as_deref() != Some(input.pool_address) { + continue; + } + let side = crate::trade_amount_resolution::raydium_launchpad_instruction_side( + decoded_event.event_kind.as_str(), + ); + let side = match side { + Some(side) => side, + None => continue, + }; + match resolved_side { + Some(existing_side) => { + if existing_side != side { + tracing::debug!( + transaction_signature = %input.transaction.signature, + pool_account = %input.pool_address, + decoded_event_id = ?input.decoded_event.id, + existing_side = ?existing_side, + conflicting_side = ?side, + "raydium_launchpad sibling swap instruction side is ambiguous" + ); + return Ok(None); + } + }, + None => resolved_side = Some(side), + } + } + return Ok(resolved_side); +} + +fn raydium_launchpad_instruction_side( + event_kind: &str, +) -> std::option::Option { + match event_kind { + "raydium_launchpad.buy_exact_in" | "raydium_launchpad.buy_exact_out" => { + return Some(crate::SwapTradeSide::BuyBase); + }, + "raydium_launchpad.sell_exact_in" | "raydium_launchpad.sell_exact_out" => { + return Some(crate::SwapTradeSide::SellBase); + }, + _ => return None, + } +} + +fn apply_raydium_launchpad_side_amount_mapping( + side: crate::SwapTradeSide, + amount_in_raw: &str, + amount_out_raw: &str, + base_amount_raw: &mut std::option::Option, + quote_amount_raw: &mut std::option::Option, + resolved_trade_side: &mut std::option::Option, +) { + match side { + crate::SwapTradeSide::BuyBase => { + if base_amount_raw.is_none() { + *base_amount_raw = Some(amount_out_raw.to_string()); + } + if quote_amount_raw.is_none() { + *quote_amount_raw = Some(amount_in_raw.to_string()); + } + if resolved_trade_side.is_none() { + *resolved_trade_side = Some(crate::SwapTradeSide::BuyBase); + } + }, + crate::SwapTradeSide::SellBase => { + if base_amount_raw.is_none() { + *base_amount_raw = Some(amount_in_raw.to_string()); + } + if quote_amount_raw.is_none() { + *quote_amount_raw = Some(amount_out_raw.to_string()); + } + if resolved_trade_side.is_none() { + *resolved_trade_side = Some(crate::SwapTradeSide::SellBase); + } + }, + crate::SwapTradeSide::Unknown => {}, + } +} + fn apply_pump_fun_amount_fallback( input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>, base_amount_raw: &mut std::option::Option, @@ -1676,6 +1931,66 @@ mod tests { assert_eq!(side, Some(crate::SwapTradeSide::SellBase)); } + #[test] + fn raydium_launchpad_buy_instruction_maps_amount_in_out_to_base_quote() { + let mut base_amount_raw = None; + let mut quote_amount_raw = None; + let mut resolved_trade_side = None; + super::apply_raydium_launchpad_side_amount_mapping( + crate::SwapTradeSide::BuyBase, + "15000", + "262584872", + &mut base_amount_raw, + &mut quote_amount_raw, + &mut resolved_trade_side, + ); + assert_eq!(base_amount_raw, Some("262584872".to_string())); + assert_eq!(quote_amount_raw, Some("15000".to_string())); + assert_eq!(resolved_trade_side, Some(crate::SwapTradeSide::BuyBase)); + } + + #[test] + fn raydium_launchpad_sell_instruction_maps_amount_in_out_to_base_quote() { + let mut base_amount_raw = None; + let mut quote_amount_raw = None; + let mut resolved_trade_side = None; + super::apply_raydium_launchpad_side_amount_mapping( + crate::SwapTradeSide::SellBase, + "11093407717995", + "43968050", + &mut base_amount_raw, + &mut quote_amount_raw, + &mut resolved_trade_side, + ); + assert_eq!(base_amount_raw, Some("11093407717995".to_string())); + assert_eq!(quote_amount_raw, Some("43968050".to_string())); + assert_eq!(resolved_trade_side, Some(crate::SwapTradeSide::SellBase)); + } + + #[test] + fn raydium_launchpad_instruction_side_maps_exact_in_and_exact_out_variants() { + assert_eq!( + super::raydium_launchpad_instruction_side("raydium_launchpad.buy_exact_in"), + Some(crate::SwapTradeSide::BuyBase) + ); + assert_eq!( + super::raydium_launchpad_instruction_side("raydium_launchpad.buy_exact_out"), + Some(crate::SwapTradeSide::BuyBase) + ); + assert_eq!( + super::raydium_launchpad_instruction_side("raydium_launchpad.sell_exact_in"), + Some(crate::SwapTradeSide::SellBase) + ); + assert_eq!( + super::raydium_launchpad_instruction_side("raydium_launchpad.sell_exact_out"), + Some(crate::SwapTradeSide::SellBase) + ); + assert_eq!( + super::raydium_launchpad_instruction_side("raydium_launchpad.collect_fee"), + None + ); + } + #[test] fn failed_transaction_does_not_infer_trade_side() { let transaction_json = transaction_json_with_vaults("base_vault", "quote_vault"); diff --git a/kb_lib/src/upstream_registry_generated.rs b/kb_lib/src/upstream_registry_generated.rs index eeebd46..9237558 100644 --- a/kb_lib/src/upstream_registry_generated.rs +++ b/kb_lib/src/upstream_registry_generated.rs @@ -13,8 +13,37 @@ const UPSTREAM_GIT_DISCRIMINATOR_NOTES: &str = "entry name and discriminator ext const UPSTREAM_GIT_ALIAS_PROGRAM_NOTES: &str = "upstream Git decoder name kept as a discovery alias; program id and discriminator rows are represented by the canonical decoder entry to avoid duplicate registry keys"; const RAYDIUM_IDL_SOURCE_REPO: &str = "raydium-io/raydium-idl"; +const MANUAL_SOLSCAN_SOURCE_REPO: &str = "manual-solscan"; const RAYDIUM_IDL_DISCRIMINATOR_NOTES: &str = "entry name and discriminator extracted from Raydium official IDL snapshot; not corpus-verified; no trade/candle/materialization proof"; +const MANUAL_SOLSCAN_DISCRIMINATOR_NOTES: &str = "entry name and discriminator derived from local corpus plus manual Solscan transaction-log inspection; no trade/candle/materialization proof"; + +const fn manual_solscan_discriminator_entry( + decoder_code: &'static str, + program_id: std::option::Option<&'static str>, + program_family: &'static str, + surface_kind: &'static str, + entry_kind: &'static str, + entry_name: &'static str, + discriminator_hex: &'static str, + discriminator_len: u16, + source_path: &'static str, +) -> crate::UpstreamRegistryEntry { + return crate::UpstreamRegistryEntry { + source_repo: Some(MANUAL_SOLSCAN_SOURCE_REPO), + source_path: Some(source_path), + decoder_code, + program_id, + program_family, + surface_kind, + entry_kind, + entry_name, + discriminator_hex: Some(discriminator_hex), + discriminator_len: Some(discriminator_len), + proof_status: crate::PROOF_STATUS_UPSTREAM_GIT_UNVERIFIED, + notes: MANUAL_SOLSCAN_DISCRIMINATOR_NOTES, + }; +} const fn raydium_idl_discriminator_entry( decoder_code: &'static str, @@ -453,8 +482,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-cpmm-decoder/src/lib.rs", ), upstream_git_program_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", "decoders/raydium-launchpad-decoder/src/lib.rs", @@ -619,6 +648,17 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 8, "decoders/meteora-damm-v2-decoder/src/instructions/claim_reward.rs", ), + upstream_git_discriminator_entry( + "meteora_damm_v2", + Some(crate::METEORA_DAMM_V2_PROGRAM_ID), + "meteora", + "amm", + crate::ENTRY_KIND_INSTRUCTION, + "cpi_event", + "bcd8a66c1aa68eb6", + 8, + "decoders/meteora-damm-v2-decoder/src/instructions/cpi_event.rs", + ), upstream_git_discriminator_entry( "meteora_damm_v2", Some(crate::METEORA_DAMM_V2_PROGRAM_ID), @@ -663,17 +703,6 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 8, "decoders/meteora-damm-v2-decoder/src/instructions/close_token_badge.rs", ), - upstream_git_discriminator_entry( - "meteora_damm_v2", - Some(crate::METEORA_DAMM_V2_PROGRAM_ID), - "meteora", - "amm", - crate::ENTRY_KIND_INSTRUCTION, - "cpi_event", - "bcd8a66c1aa68eb6", - 8, - "decoders/meteora-damm-v2-decoder/src/instructions/cpi_event.rs", - ), upstream_git_discriminator_entry( "meteora_damm_v2", Some(crate::METEORA_DAMM_V2_PROGRAM_ID), @@ -1961,17 +1990,6 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 8, "decoders/dflow-aggregator-v4-decoder/src/instructions/close_order.rs", ), - upstream_git_discriminator_entry( - "dflow_aggregator_v4", - Some(crate::DFLOW_AGGREGATOR_V4_PROGRAM_ID), - "dflow", - "aggregator", - crate::ENTRY_KIND_INSTRUCTION, - "cpi_event", - "e445a52e51cb9a1d", - 8, - "decoders/dflow-aggregator-v4-decoder/src/instructions/cpi_event.rs", - ), upstream_git_discriminator_entry( "dflow_aggregator_v4", Some(crate::DFLOW_AGGREGATOR_V4_PROGRAM_ID), @@ -5030,17 +5048,6 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 8, "decoders/jupiter-lend-decoder/src/instructions/collect_revenue.rs", ), - upstream_git_discriminator_entry( - "jupiter_lend", - Some(crate::JUPITER_LEND_PROGRAM_ID), - "jupiter", - "lending", - crate::ENTRY_KIND_INSTRUCTION, - "cpi_event", - "e445a52e51cb9a1d", - 8, - "decoders/jupiter-lend-decoder/src/instructions/cpi_event.rs", - ), upstream_git_discriminator_entry( "jupiter_lend", Some(crate::JUPITER_LEND_PROGRAM_ID), @@ -5503,17 +5510,6 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 8, "decoders/jupiter-swap-decoder/src/instructions/close_token.rs", ), - upstream_git_discriminator_entry( - "jupiter_swap", - Some(crate::JUPITER_SWAP_PROGRAM_ID), - "jupiter", - "aggregator", - crate::ENTRY_KIND_INSTRUCTION, - "cpi_event", - "e445a52e51cb9a1d", - 8, - "decoders/jupiter-swap-decoder/src/instructions/cpi_event.rs", - ), upstream_git_discriminator_entry( "jupiter_swap", Some(crate::JUPITER_SWAP_PROGRAM_ID), @@ -8110,17 +8106,6 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 8, "decoders/onchain-labs-dex-v2-decoder/src/instructions/claim.rs", ), - upstream_git_discriminator_entry( - "onchain_labs_dex_v2", - Some(crate::ONCHAIN_LABS_DEX_V2_PROGRAM_ID), - "onchain_labs", - "amm", - crate::ENTRY_KIND_INSTRUCTION, - "cpi_event", - "e445a52e51cb9a1d", - 8, - "decoders/onchain-labs-dex-v2-decoder/src/instructions/cpi_event.rs", - ), upstream_git_discriminator_entry( "onchain_labs_dex_v2", Some(crate::ONCHAIN_LABS_DEX_V2_PROGRAM_ID), @@ -10123,17 +10108,6 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 8, "decoders/pump-fees-decoder/src/instructions/claim_social_fee_pda.rs", ), - upstream_git_discriminator_entry( - "pump_fees", - Some(crate::PUMP_FEES_PROGRAM_ID), - "pump", - "fee_program", - crate::ENTRY_KIND_INSTRUCTION, - "cpi_event", - "e445a52e51cb9a1d", - 8, - "decoders/pump-fees-decoder/src/instructions/cpi_event.rs", - ), upstream_git_discriminator_entry( "pump_fees", Some(crate::PUMP_FEES_PROGRAM_ID), @@ -10574,17 +10548,6 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 8, "decoders/pump-swap-decoder/src/instructions/collect_coin_creator_fee.rs", ), - upstream_git_discriminator_entry( - "pump_swap", - Some(crate::PUMP_SWAP_PROGRAM_ID), - "pump", - "amm", - crate::ENTRY_KIND_INSTRUCTION, - "cpi_event", - "e445a52e51cb9a1d", - 8, - "decoders/pump-swap-decoder/src/instructions/cpi_event.rs", - ), upstream_git_discriminator_entry( "pump_swap", Some(crate::PUMP_SWAP_PROGRAM_ID), @@ -11113,17 +11076,6 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 8, "decoders/pumpfun-decoder/src/instructions/collect_creator_fee.rs", ), - upstream_git_discriminator_entry( - "pump_fun", - Some(crate::PUMP_FUN_PROGRAM_ID), - "pump", - "launch", - crate::ENTRY_KIND_INSTRUCTION, - "cpi_event", - "e445a52e51cb9a1d", - 8, - "decoders/pumpfun-decoder/src/instructions/cpi_event.rs", - ), upstream_git_discriminator_entry( "pump_fun", Some(crate::PUMP_FUN_PROGRAM_ID), @@ -11795,6 +11747,17 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 1, "decoders/raydium-amm-v4-decoder/src/instructions/withdraw_srm.rs", ), + upstream_git_discriminator_entry( + "raydium_clmm", + Some(crate::RAYDIUM_CLMM_PROGRAM_ID), + "raydium", + "clmm", + crate::ENTRY_KIND_INSTRUCTION, + "cpi_event", + "e445a52e51cb9a1d", + 8, + "decoders/raydium-clmm-decoder/src/instructions/cpi_event.rs", + ), upstream_git_discriminator_entry( "raydium_clmm", Some(crate::RAYDIUM_CLMM_PROGRAM_ID), @@ -12235,6 +12198,17 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 8, "decoders/raydium-clmm-decoder/src/instructions/update_amm_config.rs", ), + upstream_git_discriminator_entry( + "raydium_clmm", + Some(crate::RAYDIUM_CLMM_PROGRAM_ID), + "raydium", + "clmm", + crate::ENTRY_KIND_INSTRUCTION, + "update_dynamic_fee_config", + "0707500802c784f0", + 8, + "decoders/raydium-clmm-decoder/src/instructions/update_dynamic_fee_config.rs", + ), upstream_git_discriminator_entry( "raydium_clmm", Some(crate::RAYDIUM_CLMM_PROGRAM_ID), @@ -12279,6 +12253,17 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 8, "decoders/raydium-clmm-decoder/src/instructions/update_reward_infos_event.rs", ), + manual_solscan_discriminator_entry( + "raydium_cpmm", + Some(crate::RAYDIUM_CPMM_PROGRAM_ID), + "raydium", + "amm", + crate::ENTRY_KIND_INSTRUCTION, + "anchor_idl_instruction", + "40f4bc78a7e9690a", + 8, + "solscan/tx/Hi6MkRTkcgwBi1WpiiudGPHKLuaKXKamNgVsy6YqoQeMRnrkpGjNx75ymrY59tJ3NN1GCn6nrndz9thMmwALLcY", + ), upstream_git_discriminator_entry( "raydium_cpmm", Some(crate::RAYDIUM_CPMM_PROGRAM_ID), @@ -12323,6 +12308,17 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 8, "decoders/raydium-cpmm-decoder/src/instructions/collect_protocol_fee.rs", ), + upstream_git_discriminator_entry( + "raydium_cpmm", + Some(crate::RAYDIUM_CPMM_PROGRAM_ID), + "raydium", + "amm", + crate::ENTRY_KIND_INSTRUCTION, + "cpi_event", + "e445a52e51cb9a1d", + 8, + "decoders/raydium-cpmm-decoder/src/instructions/cpi_event.rs", + ), upstream_git_discriminator_entry( "raydium_cpmm", Some(crate::RAYDIUM_CPMM_PROGRAM_ID), @@ -12456,8 +12452,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-cpmm-decoder/src/instructions/withdraw.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12467,8 +12463,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/buy_exact_in.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12478,8 +12474,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/buy_exact_out.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12489,8 +12485,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/claim_creator_fee.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12500,8 +12496,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/claim_platform_fee.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12511,8 +12507,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/claim_platform_fee_from_vault.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_EVENT, @@ -12522,8 +12518,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/claim_vested_event.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12533,8 +12529,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/claim_vested_token.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12544,8 +12540,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/collect_fee.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12555,8 +12551,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/collect_migrate_fee.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12566,8 +12562,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/create_config.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12577,8 +12573,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/create_platform_config.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12588,8 +12584,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/create_vesting_account.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_EVENT, @@ -12599,8 +12595,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/create_vesting_event.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12610,8 +12606,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/initialize.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12621,8 +12617,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/initialize_v2.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12632,8 +12628,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/initialize_with_token_2022.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12643,8 +12639,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/migrate_to_amm.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12654,8 +12650,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/migrate_to_cpswap.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_EVENT, @@ -12665,8 +12661,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/pool_create_event.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12676,8 +12672,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/remove_platform_curve_param.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12687,8 +12683,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/sell_exact_in.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12698,8 +12694,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/sell_exact_out.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_EVENT, @@ -12709,8 +12705,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/trade_event.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12720,8 +12716,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/update_config.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12731,8 +12727,8 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ "decoders/raydium-launchpad-decoder/src/instructions/update_platform_config.rs", ), upstream_git_discriminator_entry( - "raydium_launchlab", - Some(crate::RAYDIUM_LAUNCHLAB_PROGRAM_ID), + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), "raydium", "launch", crate::ENTRY_KIND_INSTRUCTION, @@ -12741,6 +12737,50 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[ 8, "decoders/raydium-launchpad-decoder/src/instructions/update_platform_curve_param.rs", ), + upstream_git_discriminator_entry( + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), + "raydium", + "launch", + crate::ENTRY_KIND_INSTRUCTION, + "close_platform_global_access", + "7bb4b8816fb9bb3b", + 8, + "decoders/raydium-launchpad-decoder/src/instructions/close_platform_global_access.rs", + ), + upstream_git_discriminator_entry( + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), + "raydium", + "launch", + crate::ENTRY_KIND_INSTRUCTION, + "create_platform_global_access", + "a25b92c75d85eaed", + 8, + "decoders/raydium-launchpad-decoder/src/instructions/create_platform_global_access.rs", + ), + upstream_git_discriminator_entry( + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), + "raydium", + "launch", + crate::ENTRY_KIND_INSTRUCTION, + "create_platform_vesting_account", + "9247ad4562130f6a", + 8, + "decoders/raydium-launchpad-decoder/src/instructions/create_platform_vesting_account.rs", + ), + upstream_git_discriminator_entry( + "raydium_launchpad", + Some(crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID), + "raydium", + "launch", + crate::ENTRY_KIND_INSTRUCTION, + "cpi_event", + "e445a52e51cb9a1d", + 8, + "decoders/raydium-launchpad-decoder/src/instructions/cpi_event.rs", + ), upstream_git_discriminator_entry( "raydium_liquidity_locking", Some(crate::RAYDIUM_LIQUIDITY_LOCKING_PROGRAM_ID), diff --git a/kb_lib/src/upstream_registry_match.rs b/kb_lib/src/upstream_registry_match.rs index d9a643b..a2cbf85 100644 --- a/kb_lib/src/upstream_registry_match.rs +++ b/kb_lib/src/upstream_registry_match.rs @@ -327,7 +327,7 @@ mod tests { "raydium_amm_v4", "raydium_clmm", "raydium_cpmm", - "raydium_launchlab", + "raydium_launchpad", "raydium_liquidity_locking", "raydium_stable_swap", "orca_whirlpools", diff --git a/validation_sql/SQL_TRACE_RAYDIUM_CPMM_AUDIT_40F_0_7_50_PRE_R2.sql b/validation_sql/SQL_TRACE_RAYDIUM_CPMM_AUDIT_40F_0_7_50_PRE_R2.sql new file mode 100644 index 0000000..e12ec12 --- /dev/null +++ b/validation_sql/SQL_TRACE_RAYDIUM_CPMM_AUDIT_40F_0_7_50_PRE_R2.sql @@ -0,0 +1,48 @@ +-- file: validation_sql/SQL_TRACE_RAYDIUM_CPMM_AUDIT_40F_0_7_50_PRE_R2.sql +-- Purpose: locate the Raydium CPMM audit-only discriminator 40f4bc78a7e9690a +-- so the instruction can be inspected manually on Solscan or decoded from local meta_json. + +SELECT + tx.signature, + tx.slot, + de.id AS decoded_event_id, + de.pool_account, + de.token_a_mint, + de.token_b_mint, + json_extract(de.payload_json, '$.instructionIndex') AS instruction_index, + json_extract(de.payload_json, '$.innerInstructionIndex') AS inner_instruction_index, + json_extract(de.payload_json, '$.programId') AS program_id, + json_extract(de.payload_json, '$.discriminatorHex') AS discriminator_hex, + json_extract(de.payload_json, '$.accountCount') AS account_count, + json_extract(de.payload_json, '$.accounts') AS accounts_json, + json_extract(de.payload_json, '$.data') AS instruction_data, + de.payload_json +FROM k_sol_dex_decoded_events de +JOIN k_sol_chain_transactions tx + ON tx.id = de.transaction_id +WHERE de.protocol_name = 'raydium_cpmm' + AND de.event_kind = 'raydium_cpmm.instruction_audit' + AND json_extract(de.payload_json, '$.discriminatorHex') = '40f4bc78a7e9690a' +ORDER BY tx.slot, tx.signature, instruction_index, inner_instruction_index; + +-- Optional: include the raw projected instruction row if decoded_event.instruction_id is populated. +SELECT + tx.signature, + tx.slot, + de.id AS decoded_event_id, + ci.id AS chain_instruction_id, + ci.instruction_index, + ci.inner_instruction_index, + ci.program_id, + ci.accounts_json, + ci.data_json, + de.payload_json +FROM k_sol_dex_decoded_events de +JOIN k_sol_chain_transactions tx + ON tx.id = de.transaction_id +LEFT JOIN k_sol_chain_instructions ci + ON ci.id = de.instruction_id +WHERE de.protocol_name = 'raydium_cpmm' + AND de.event_kind = 'raydium_cpmm.instruction_audit' + AND json_extract(de.payload_json, '$.discriminatorHex') = '40f4bc78a7e9690a' +ORDER BY tx.slot, tx.signature, ci.instruction_index, ci.inner_instruction_index; diff --git a/validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_50_PRE_R2.sql b/validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_50_PRE_R2.sql new file mode 100644 index 0000000..d28186e --- /dev/null +++ b/validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_50_PRE_R2.sql @@ -0,0 +1,399 @@ +-- file: validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_50_PRE_R2.sql + +-- Raydium CLMM 0.7.50-pre-r2 re-verification pack. +-- Goal: repeat the Launchpad-style closure checks on a dedicated CLMM +-- database, after replay with skipDexDecode='no' and forceDexDecode='yes'. +-- Expected invariants: +-- - no `unknown` or empty event_family except the synthetic `program` row; +-- - no observed discriminator missing from coverage; +-- - no residual upstream fallback for locally-covered entries; +-- - no successful, actionable decoded event left without its materialized row; +-- - no failed transaction materialized into business tables; +-- - decoded-only entries such as CPMM anchor IDL management and CLMM router/program-data +-- corroboration events are excluded from materialization shortfall checks. + +-- 01. Coverage detail. +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_clmm' +ORDER BY entry_kind, entry_name, discriminator_hex; +-> +collect_personal_fee_event event fee k_sol_fee_events upstream_git_mapped_unverified raydium_clmm.collect_personal_fee_event a6ae69c051a15369 0 0 0 +collect_protocol_fee_event event fee k_sol_fee_events upstream_git_local_corpus_materialized raydium_clmm.collect_protocol_fee_event ce57114f2d29d53d 4 4 0 +config_change_event event admin_config k_sol_pool_admin_events upstream_git_local_corpus_materialized raydium_clmm.config_change_event f7bd07776a705f97 4 4 0 +create_personal_position_event event position_open k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_clmm.create_personal_position_event 641e57f9c4df9ace 26 23 0 +decrease_liquidity_event event liquidity_remove k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_clmm.decrease_liquidity_event 3ade563a44325538 16 16 0 +increase_liquidity_event event liquidity_add k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_clmm.increase_liquidity_event 314f69d420221e54 9 8 0 +liquidity_calculate_event event liquidity_calculation k_sol_dex_decoded_events_only upstream_git_local_corpus_observed raydium_clmm.liquidity_calculate_event ed7094e63954b4a2 58 0 0 +liquidity_change_event event liquidity_change k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_clmm.liquidity_change_event 7ef0afce9e58996b 50 45 0 +pool_created_event event pool_create k_sol_pool_lifecycle_events upstream_git_local_corpus_materialized raydium_clmm.pool_created_event 195e4b2f7063353f 14 13 0 +swap_event event swap k_sol_dex_decoded_events_only upstream_git_local_corpus_observed raydium_clmm.swap_event 40c6cde8260871e2 1456 0 0 +update_reward_infos_event event reward k_sol_reward_events upstream_git_local_corpus_materialized raydium_clmm.update_reward_infos_event 6d7fba4e724125ec 7 7 0 +close_limit_order instruction order_cancel k_sol_orderbook_events upstream_git_local_corpus_materialized raydium_clmm.close_limit_order 4c7c800fd55725fa 24 22 0 +close_position instruction position_close k_sol_dex_decoded_events_only upstream_git_local_corpus_observed raydium_clmm.close_position 7b86510031446262 16 0 0 +close_protocol_position instruction position_close k_sol_dex_decoded_events_only upstream_git_local_corpus_observed raydium_clmm.close_protocol_position c975989055556cb2 3 0 0 +collect_fund_fee instruction fee k_sol_fee_events upstream_git_local_corpus_materialized raydium_clmm.collect_fund_fee a78a4e95dfc2067e 5 3 0 +collect_protocol_fee instruction fee k_sol_fee_events upstream_git_local_corpus_materialized raydium_clmm.collect_protocol_fee 8888fcddc2427e59 11 11 0 +collect_remaining_rewards instruction reward k_sol_reward_events upstream_git_local_corpus_materialized raydium_clmm.collect_remaining_rewards 12eda6c52210d590 10 6 0 +cpi_event instruction cpi_transport k_sol_dex_decoded_events_only upstream_git_mapped_unverified raydium_clmm.cpi_event e445a52e51cb9a1d 0 0 0 +create_amm_config instruction admin_config k_sol_pool_admin_events upstream_git_local_corpus_materialized raydium_clmm.create_amm_config 8934edd4d7756c68 9 7 0 +create_customizable_pool instruction pool_create k_sol_pool_lifecycle_events upstream_git_local_corpus_materialized raydium_clmm.create_customizable_pool 2b44d4a7592fa401 5 3 0 +create_dynamic_fee_config instruction admin_config k_sol_pool_admin_events upstream_git_local_corpus_materialized raydium_clmm.create_dynamic_fee_config bd0eb5785576e33e 1 1 0 +create_operation_account instruction admin_config k_sol_pool_admin_events upstream_git_local_corpus_materialized raydium_clmm.create_operation_account 3f5794216d230868 4 1 0 +create_pool instruction pool_create k_sol_pool_lifecycle_events upstream_git_local_corpus_materialized raydium_clmm.create_pool e992d18ecf6840bc 8 8 0 +create_support_mint_associated instruction account_create k_sol_token_account_events upstream_git_local_corpus_observed raydium_clmm.create_support_mint_associated 11fb415c88f20ea9 5 0 0 +decrease_limit_order instruction order_cancel k_sol_orderbook_events upstream_git_local_corpus_materialized raydium_clmm.decrease_limit_order 759d3c674231a300 24 22 0 +decrease_liquidity instruction liquidity_remove k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_clmm.decrease_liquidity a026d06f685b2c01 16 1 0 +decrease_liquidity_v2 instruction liquidity_remove k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_clmm.decrease_liquidity_v2 3a7fbc3e4f52c460 25 23 0 +increase_limit_order instruction order_place k_sol_orderbook_events upstream_git_local_corpus_materialized raydium_clmm.increase_limit_order b19059ecfaba7d63 6 6 0 +increase_liquidity instruction liquidity_add k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_clmm.increase_liquidity 2e9cf3760dcdfbb2 6 2 0 +increase_liquidity_v2 instruction liquidity_add k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_clmm.increase_liquidity_v2 851d59df45eeb00a 7 6 0 +initialize_reward instruction pool_create k_sol_reward_events upstream_git_local_corpus_materialized raydium_clmm.initialize_reward 5f87c0c4f281e644 7 4 0 +open_limit_order instruction order_place k_sol_orderbook_events upstream_git_local_corpus_materialized raydium_clmm.open_limit_order 9d20dab7471d1293 10 5 0 +open_position instruction position_open k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_clmm.open_position 87802f4d0f98f031 9 1 0 +open_position_v2 instruction position_open k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_clmm.open_position_v2 4db84ad67056f1c7 11 7 0 +open_position_with_token22_nft instruction position_open k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_clmm.open_position_with_token22_nft 4dffae527d1dc92e 19 15 0 +set_reward_params instruction reward k_sol_reward_events upstream_git_local_corpus_materialized raydium_clmm.set_reward_params 7034a74b20c9d389 6 4 0 +settle_limit_order instruction settle_funds k_sol_orderbook_events upstream_git_local_corpus_materialized raydium_clmm.settle_limit_order cd4e74215c691a60 9 9 0 +swap instruction swap k_sol_trade_events upstream_git_local_corpus_materialized raydium_clmm.swap f8c69e91e17587c8 1464 910 910 +swap_router_base_in instruction swap k_sol_dex_decoded_events_only upstream_git_local_corpus_observed raydium_clmm.swap_router_base_in 457d73daf5baf2c4 90 0 0 +swap_v2 instruction swap k_sol_trade_events upstream_git_local_corpus_materialized raydium_clmm.swap_v2 2b04ed0b1ac91e62 715 276 276 +transfer_reward_owner instruction reward k_sol_reward_events upstream_git_local_corpus_materialized raydium_clmm.transfer_reward_owner 07160c53f22b3079 7 5 0 +update_amm_config instruction admin_config k_sol_pool_admin_events upstream_git_local_corpus_materialized raydium_clmm.update_amm_config 313cae889a1c74c8 11 8 0 +update_dynamic_fee_config instruction admin_config k_sol_pool_admin_events upstream_git_mapped_unverified raydium_clmm.update_dynamic_fee_config 0707500802c784f0 0 0 0 +update_operation_account instruction admin_config k_sol_pool_admin_events upstream_git_local_corpus_materialized raydium_clmm.update_operation_account 7f467728bce33d07 4 3 0 +update_pool_status instruction admin_config k_sol_pool_admin_events upstream_git_local_corpus_materialized raydium_clmm.update_pool_status 82576c062ee0757b 6 4 0 +update_reward_infos instruction reward k_sol_reward_events upstream_git_local_corpus_materialized raydium_clmm.update_reward_infos a3ace0340b9a6adf 7 7 0 +program program k_sol_dex_decoded_events_only upstream_git_unverified 0 0 0 + +SELECT + decoder_code, + COUNT(*) AS listed_entry_count, + SUM(CASE WHEN local_event_kind IS NOT NULL AND local_event_kind <> '' THEN 1 ELSE 0 END) AS decoded_entry_count, + SUM(CASE WHEN observed_count > 0 THEN 1 ELSE 0 END) AS observed_entry_count, + SUM(CASE WHEN materialized_count > 0 THEN 1 ELSE 0 END) AS materialized_entry_count, + COALESCE(SUM(observed_count), 0) AS total_observed_count, + COALESCE(SUM(materialized_count), 0) AS total_materialized_count, + COALESCE(SUM(trade_count), 0) AS trade_count +FROM k_sol_dex_event_coverage_entries +WHERE decoder_code = 'raydium_clmm' +GROUP BY decoder_code; +-> +raydium_clmm 47 46 43 37 4204 1500 1186 + +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_clmm' + AND ( + event_family IS NULL + OR event_family = '' + OR event_family = 'unknown' + ) + AND entry_kind <> 'program' +ORDER BY entry_kind, entry_name; +-> vide + +SELECT + instruction_name, + discriminator_hex, + COUNT(*) AS observed_count, + COUNT(DISTINCT signature) AS tx_count +FROM k_sol_instruction_observations +WHERE decoder_code = 'raydium_clmm' +GROUP BY instruction_name, discriminator_hex +ORDER BY observed_count DESC, instruction_name, discriminator_hex; +-> +raydium_clmm.swap f8c69e91e17587c8 1464 1327 +raydium_clmm.swap_v2 2b04ed0b1ac91e62 715 459 +raydium_clmm.swap_router_base_in 457d73daf5baf2c4 90 88 +raydium_clmm.close_limit_order 4c7c800fd55725fa 28 24 +raydium_clmm.decrease_limit_order 759d3c674231a300 28 24 +raydium_clmm.decrease_liquidity_v2 3a7fbc3e4f52c460 25 18 +raydium_clmm.create_support_mint_associated 11fb415c88f20ea9 22 5 +raydium_clmm.update_amm_config 313cae889a1c74c8 20 11 +raydium_clmm.open_position_with_token22_nft 4dffae527d1dc92e 19 19 +raydium_clmm.collect_fund_fee a78a4e95dfc2067e 18 5 +raydium_clmm.close_position 7b86510031446262 16 16 +raydium_clmm.decrease_liquidity a026d06f685b2c01 16 16 +raydium_clmm.create_amm_config 8934edd4d7756c68 12 9 +raydium_clmm.collect_protocol_fee 8888fcddc2427e59 11 4 +raydium_clmm.open_position_v2 4db84ad67056f1c7 11 11 +raydium_clmm.collect_remaining_rewards 12eda6c52210d590 10 10 +raydium_clmm.create_pool e992d18ecf6840bc 10 10 +raydium_clmm.open_limit_order 9d20dab7471d1293 10 10 +raydium_clmm.open_position 87802f4d0f98f031 9 9 +raydium_clmm.set_reward_params 7034a74b20c9d389 9 6 +raydium_clmm.settle_limit_order cd4e74215c691a60 9 9 +raydium_clmm.transfer_reward_owner 07160c53f22b3079 9 7 +raydium_clmm.increase_liquidity_v2 851d59df45eeb00a 7 7 +raydium_clmm.initialize_reward 5f87c0c4f281e644 7 7 +raydium_clmm.update_reward_infos a3ace0340b9a6adf 7 7 +raydium_clmm.increase_limit_order b19059ecfaba7d63 6 6 +raydium_clmm.increase_liquidity 2e9cf3760dcdfbb2 6 6 +raydium_clmm.update_pool_status 82576c062ee0757b 6 6 +raydium_clmm.close_protocol_position c975989055556cb2 5 3 +raydium_clmm.create_customizable_pool 2b44d4a7592fa401 5 5 +raydium_clmm.update_operation_account 7f467728bce33d07 5 4 +raydium_clmm.create_dynamic_fee_config bd0eb5785576e33e 4 1 +raydium_clmm.create_operation_account 3f5794216d230868 4 4 + +SELECT + io.discriminator_hex, + MIN(io.instruction_name) AS sample_instruction_name, + MIN(io.decoded_event_kind) AS sample_decoded_event_kind, + COUNT(*) AS observed_count, + COUNT(DISTINCT io.signature) AS tx_count +FROM k_sol_instruction_observations io +LEFT JOIN k_sol_dex_event_coverage_entries ce + ON ce.decoder_code = 'raydium_clmm' + AND ce.discriminator_hex = io.discriminator_hex +WHERE io.decoder_code = 'raydium_clmm' + AND io.discriminator_hex IS NOT NULL + AND io.discriminator_hex <> '' + AND ce.id IS NULL +GROUP BY io.discriminator_hex +ORDER BY observed_count DESC, io.discriminator_hex; +-> vide + +SELECT + de.event_kind, + COUNT(*) AS decoded_count, + COUNT(le.id) AS liquidity_count, + COUNT(fe.id) AS fee_count, + COUNT(re.id) AS reward_count, + COUNT(ae.id) AS admin_count, + COUNT(la.id) AS launch_count, + COUNT(pe.id) AS lifecycle_count, + COUNT(oe.id) AS orderbook_count, + COUNT(ta.id) AS token_account_count, + COUNT(te.id) AS trade_count +FROM k_sol_dex_decoded_events de +LEFT JOIN k_sol_liquidity_events le ON le.decoded_event_id = de.id +LEFT JOIN k_sol_fee_events fe ON fe.decoded_event_id = de.id +LEFT JOIN k_sol_reward_events re ON re.decoded_event_id = de.id +LEFT JOIN k_sol_pool_admin_events ae ON ae.decoded_event_id = de.id +LEFT JOIN k_sol_launch_events la ON la.decoded_event_id = de.id +LEFT JOIN k_sol_pool_lifecycle_events pe ON pe.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 ta ON ta.decoded_event_id = de.id +LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_clmm' +GROUP BY de.event_kind +ORDER BY decoded_count DESC, de.event_kind; +-> +raydium_clmm.swap 1464 0 0 0 0 0 0 0 0 910 +raydium_clmm.swap_event 1456 0 0 0 0 0 0 0 0 0 +raydium_clmm.swap_v2 715 0 0 0 0 0 0 0 0 276 +raydium_clmm.swap_router_base_in 90 0 0 0 0 0 0 0 0 0 +raydium_clmm.liquidity_calculate_event 58 0 0 0 0 0 0 0 0 0 +raydium_clmm.liquidity_change_event 50 45 0 0 0 0 0 0 0 0 +raydium_clmm.create_personal_position_event 26 23 0 0 0 0 0 0 0 0 +raydium_clmm.decrease_liquidity_v2 25 23 0 0 0 0 0 0 0 0 +raydium_clmm.close_limit_order 24 0 0 0 0 0 0 22 0 0 +raydium_clmm.decrease_limit_order 24 0 0 0 0 0 0 22 0 0 +raydium_clmm.open_position_with_token22_nft 19 15 0 0 0 0 0 0 0 0 +raydium_clmm.close_position 16 7 0 0 0 0 0 0 0 0 +raydium_clmm.decrease_liquidity 16 1 0 0 0 0 0 0 0 0 +raydium_clmm.decrease_liquidity_event 16 16 0 0 0 0 0 0 0 0 +raydium_clmm.pool_created_event 14 0 0 0 0 0 13 0 0 0 +raydium_clmm.collect_protocol_fee 11 0 11 0 0 0 0 0 0 0 +raydium_clmm.open_position_v2 11 7 0 0 0 0 0 0 0 0 +raydium_clmm.update_amm_config 11 0 0 0 8 0 0 0 0 0 +raydium_clmm.collect_remaining_rewards 10 0 0 6 0 0 0 0 0 0 +raydium_clmm.open_limit_order 10 0 0 0 0 0 0 5 0 0 +raydium_clmm.create_amm_config 9 0 0 0 7 0 0 0 0 0 +raydium_clmm.increase_liquidity_event 9 8 0 0 0 0 0 0 0 0 +raydium_clmm.open_position 9 1 0 0 0 0 0 0 0 0 +raydium_clmm.settle_limit_order 9 0 0 0 0 0 0 9 0 0 +raydium_clmm.create_pool 8 0 0 0 0 0 8 0 0 0 +raydium_clmm.increase_liquidity_v2 7 6 0 0 0 0 0 0 0 0 +raydium_clmm.initialize_reward 7 0 0 4 0 0 4 0 0 0 +raydium_clmm.transfer_reward_owner 7 0 0 5 0 0 0 0 0 0 +raydium_clmm.update_reward_infos 7 0 0 7 7 0 0 0 0 0 +raydium_clmm.update_reward_infos_event 7 0 0 7 7 0 0 0 0 0 +raydium_clmm.increase_limit_order 6 0 0 0 0 0 0 6 0 0 +raydium_clmm.increase_liquidity 6 2 0 0 0 0 0 0 0 0 +raydium_clmm.set_reward_params 6 0 0 4 4 0 0 0 0 0 +raydium_clmm.update_pool_status 6 0 0 0 4 0 0 0 0 0 +raydium_clmm.collect_fund_fee 5 0 3 0 0 0 0 0 0 0 +raydium_clmm.create_customizable_pool 5 0 0 0 0 0 3 0 0 0 +raydium_clmm.create_support_mint_associated 5 0 0 0 0 0 0 0 5 0 +raydium_clmm.collect_protocol_fee_event 4 0 4 0 0 0 0 0 0 0 +raydium_clmm.config_change_event 4 0 0 0 4 0 0 0 0 0 +raydium_clmm.create_operation_account 4 0 0 0 1 0 0 0 0 0 +raydium_clmm.update_operation_account 4 0 0 0 3 0 0 0 0 0 +raydium_clmm.close_protocol_position 3 0 0 0 0 0 0 0 0 0 +raydium_clmm.create_dynamic_fee_config 1 0 0 0 1 0 0 0 0 0 + +SELECT + de.event_kind, + ce.entry_name, + ce.entry_kind, + ce.event_family, + ce.expected_db_target, + COUNT(*) AS decoded_count, + COUNT(CASE WHEN tx.err_json IS NOT NULL AND tx.err_json <> '' AND tx.err_json <> 'null' THEN de.id END) AS failed_tx_count, + COUNT(le.id) AS liquidity_count, + COUNT(fe.id) AS fee_count, + COUNT(re.id) AS reward_count, + COUNT(ae.id) AS admin_count, + COUNT(la.id) AS launch_count, + COUNT(pe.id) AS lifecycle_count, + COUNT(oe.id) AS orderbook_count, + COUNT(ta.id) AS token_account_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_dex_event_coverage_entries ce + ON ce.decoder_code = 'raydium_clmm' + AND ce.local_event_kind = de.event_kind +LEFT JOIN k_sol_liquidity_events le ON le.decoded_event_id = de.id +LEFT JOIN k_sol_fee_events fe ON fe.decoded_event_id = de.id +LEFT JOIN k_sol_reward_events re ON re.decoded_event_id = de.id +LEFT JOIN k_sol_pool_admin_events ae ON ae.decoded_event_id = de.id +LEFT JOIN k_sol_launch_events la ON la.decoded_event_id = de.id +LEFT JOIN k_sol_pool_lifecycle_events pe ON pe.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 ta ON ta.decoded_event_id = de.id +LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_clmm' +GROUP BY de.event_kind, ce.entry_name, ce.entry_kind, ce.event_family, ce.expected_db_target +HAVING + COALESCE(ce.expected_db_target, '') <> 'k_sol_dex_decoded_events_only' + AND (liquidity_count + fee_count + reward_count + admin_count + launch_count + lifecycle_count + orderbook_count + token_account_count + trade_count + failed_tx_count) < decoded_count +ORDER BY decoded_count DESC, de.event_kind; +-> +raydium_clmm.liquidity_change_event liquidity_change_event event liquidity_change k_sol_liquidity_events 50 1 45 0 0 0 0 0 0 0 0 +raydium_clmm.create_personal_position_event create_personal_position_event event position_open k_sol_liquidity_events 26 0 23 0 0 0 0 0 0 0 0 +raydium_clmm.decrease_liquidity decrease_liquidity instruction liquidity_remove k_sol_liquidity_events 16 5 1 0 0 0 0 0 0 0 0 +raydium_clmm.open_position_v2 open_position_v2 instruction position_open k_sol_liquidity_events 11 3 7 0 0 0 0 0 0 0 0 +raydium_clmm.increase_liquidity_event increase_liquidity_event event liquidity_add k_sol_liquidity_events 9 0 8 0 0 0 0 0 0 0 0 +raydium_clmm.open_position open_position instruction position_open k_sol_liquidity_events 9 5 1 0 0 0 0 0 0 0 0 +raydium_clmm.increase_liquidity increase_liquidity instruction liquidity_add k_sol_liquidity_events 6 3 2 0 0 0 0 0 0 0 0 + +SELECT + de.event_kind, + COUNT(*) AS decoded_count, + COUNT(le.id) AS liquidity_count, + COUNT(fe.id) AS fee_count, + COUNT(re.id) AS reward_count, + COUNT(ae.id) AS admin_count, + COUNT(la.id) AS launch_count, + COUNT(pe.id) AS lifecycle_count, + COUNT(oe.id) AS orderbook_count, + COUNT(ta.id) AS token_account_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_liquidity_events le ON le.decoded_event_id = de.id +LEFT JOIN k_sol_fee_events fe ON fe.decoded_event_id = de.id +LEFT JOIN k_sol_reward_events re ON re.decoded_event_id = de.id +LEFT JOIN k_sol_pool_admin_events ae ON ae.decoded_event_id = de.id +LEFT JOIN k_sol_launch_events la ON la.decoded_event_id = de.id +LEFT JOIN k_sol_pool_lifecycle_events pe ON pe.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 ta ON ta.decoded_event_id = de.id +LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_clmm' + AND tx.err_json IS NOT NULL + AND tx.err_json <> '' + AND tx.err_json <> 'null' +GROUP BY de.event_kind +HAVING + COUNT(le.id) > 0 + OR COUNT(fe.id) > 0 + OR COUNT(re.id) > 0 + OR COUNT(ae.id) > 0 + OR COUNT(la.id) > 0 + OR COUNT(pe.id) > 0 + OR COUNT(oe.id) > 0 + OR COUNT(ta.id) > 0 + OR COUNT(te.id) > 0 +ORDER BY de.event_kind; +-> vide +-- 09. Residual instruction audit rows. +-- Expected: zero rows after full direct mapping. +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_clmm' + AND event_kind = 'raydium_clmm.instruction_audit' +GROUP BY discriminator_hex +ORDER BY audit_count DESC, discriminator_hex; + +-- 10. Redundant upstream fallback matches for locally covered entries. +-- Expected: zero rows. +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_clmm' +GROUP BY upstream_decoder_code, entry_name, discriminator_hex, source_repo +ORDER BY fallback_count DESC, entry_name; + +-- 11. Decoded event kinds without a coverage entry. +-- Expected: zero rows, excluding explicit `.instruction_audit` if it remains by design. +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_clmm' + AND ce.local_event_kind = de.event_kind +WHERE de.protocol_name = 'raydium_clmm' + AND ce.id IS NULL +GROUP BY de.event_kind +ORDER BY decoded_count DESC, de.event_kind; + +-- 12. Non-swap decoded events that created trades. +-- Expected: zero rows. +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_clmm' + 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_clmm' +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; diff --git a/validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_50_RECHECK.sql b/validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_50_RECHECK.sql new file mode 100644 index 0000000..6a08f37 --- /dev/null +++ b/validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_50_RECHECK.sql @@ -0,0 +1,272 @@ +-- file: validation_sql/SQL_VALIDATION_RAYDIUM_CLMM_0_7_50_RECHECK.sql + +-- Raydium CLMM 0.7.50 re-verification pack. +-- Goal: repeat the Launchpad-style closure checks on a dedicated CLMM +-- database, after replay with skipDexDecode='no' and forceDexDecode='yes'. +-- Expected invariants: +-- - no `unknown` or empty event_family except the synthetic `program` row; +-- - no observed discriminator missing from coverage; +-- - no residual upstream fallback for locally-covered entries; +-- - no successful, actionable decoded event left without its materialized row; +-- - no failed transaction materialized into business tables; +-- - decoded-only entries such as CPMM anchor IDL management and CLMM router/program-data +-- corroboration events are excluded from materialization shortfall checks. + +-- 01. Coverage detail. +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_clmm' +ORDER BY entry_kind, entry_name, discriminator_hex; + +-- 02. Coverage summary. +SELECT + decoder_code, + COUNT(*) AS listed_entry_count, + SUM(CASE WHEN local_event_kind IS NOT NULL AND local_event_kind <> '' THEN 1 ELSE 0 END) AS decoded_entry_count, + SUM(CASE WHEN observed_count > 0 THEN 1 ELSE 0 END) AS observed_entry_count, + SUM(CASE WHEN materialized_count > 0 THEN 1 ELSE 0 END) AS materialized_entry_count, + COALESCE(SUM(observed_count), 0) AS total_observed_count, + COALESCE(SUM(materialized_count), 0) AS total_materialized_count, + COALESCE(SUM(trade_count), 0) AS trade_count +FROM k_sol_dex_event_coverage_entries +WHERE decoder_code = 'raydium_clmm' +GROUP BY decoder_code; + +-- 03. Coverage family normalization guard. +-- Expected: zero rows, or only the synthetic `program` row if it intentionally has no family. +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_clmm' + AND ( + event_family IS NULL + OR event_family = '' + OR event_family = 'unknown' + ) + AND entry_kind <> 'program' +ORDER BY entry_kind, entry_name; + +-- 04. Instruction observations by discriminator. +SELECT + instruction_name, + discriminator_hex, + COUNT(*) AS observed_count, + COUNT(DISTINCT signature) AS tx_count +FROM k_sol_instruction_observations +WHERE decoder_code = 'raydium_clmm' +GROUP BY instruction_name, discriminator_hex +ORDER BY observed_count DESC, instruction_name, discriminator_hex; + +-- 05. Observed discriminators absent from coverage. +-- Expected: zero rows. +SELECT + io.discriminator_hex, + MIN(io.instruction_name) AS sample_instruction_name, + MIN(io.decoded_event_kind) AS sample_decoded_event_kind, + COUNT(*) AS observed_count, + COUNT(DISTINCT io.signature) AS tx_count +FROM k_sol_instruction_observations io +LEFT JOIN k_sol_dex_event_coverage_entries ce + ON ce.decoder_code = 'raydium_clmm' + AND ce.discriminator_hex = io.discriminator_hex +WHERE io.decoder_code = 'raydium_clmm' + AND io.discriminator_hex IS NOT NULL + AND io.discriminator_hex <> '' + AND ce.id IS NULL +GROUP BY io.discriminator_hex +ORDER BY observed_count DESC, io.discriminator_hex; + +-- 06. Decoded event distribution and materialization links. +SELECT + de.event_kind, + COUNT(*) AS decoded_count, + COUNT(le.id) AS liquidity_count, + COUNT(fe.id) AS fee_count, + COUNT(re.id) AS reward_count, + COUNT(ae.id) AS admin_count, + COUNT(la.id) AS launch_count, + COUNT(pe.id) AS lifecycle_count, + COUNT(oe.id) AS orderbook_count, + COUNT(ta.id) AS token_account_count, + COUNT(te.id) AS trade_count +FROM k_sol_dex_decoded_events de +LEFT JOIN k_sol_liquidity_events le ON le.decoded_event_id = de.id +LEFT JOIN k_sol_fee_events fe ON fe.decoded_event_id = de.id +LEFT JOIN k_sol_reward_events re ON re.decoded_event_id = de.id +LEFT JOIN k_sol_pool_admin_events ae ON ae.decoded_event_id = de.id +LEFT JOIN k_sol_launch_events la ON la.decoded_event_id = de.id +LEFT JOIN k_sol_pool_lifecycle_events pe ON pe.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 ta ON ta.decoded_event_id = de.id +LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_clmm' +GROUP BY de.event_kind +ORDER BY decoded_count DESC, de.event_kind; + +-- 07. Successful decoded events not materialized where coverage target says they should be. +-- Expected: zero rows, except expected_db_target='k_sol_dex_decoded_events_only' and failed tx rows. +SELECT + de.event_kind, + ce.entry_name, + ce.entry_kind, + ce.event_family, + ce.expected_db_target, + COUNT(*) AS decoded_count, + COUNT(CASE WHEN tx.err_json IS NOT NULL AND tx.err_json <> '' AND tx.err_json <> 'null' THEN de.id END) AS failed_tx_count, + COUNT(le.id) AS liquidity_count, + COUNT(fe.id) AS fee_count, + COUNT(re.id) AS reward_count, + COUNT(ae.id) AS admin_count, + COUNT(la.id) AS launch_count, + COUNT(pe.id) AS lifecycle_count, + COUNT(oe.id) AS orderbook_count, + COUNT(ta.id) AS token_account_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_dex_event_coverage_entries ce + ON ce.decoder_code = 'raydium_clmm' + AND ce.local_event_kind = de.event_kind +LEFT JOIN k_sol_liquidity_events le ON le.decoded_event_id = de.id +LEFT JOIN k_sol_fee_events fe ON fe.decoded_event_id = de.id +LEFT JOIN k_sol_reward_events re ON re.decoded_event_id = de.id +LEFT JOIN k_sol_pool_admin_events ae ON ae.decoded_event_id = de.id +LEFT JOIN k_sol_launch_events la ON la.decoded_event_id = de.id +LEFT JOIN k_sol_pool_lifecycle_events pe ON pe.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 ta ON ta.decoded_event_id = de.id +LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_clmm' +GROUP BY de.event_kind, ce.entry_name, ce.entry_kind, ce.event_family, ce.expected_db_target +HAVING + COALESCE(ce.expected_db_target, '') <> 'k_sol_dex_decoded_events_only' + AND (liquidity_count + fee_count + reward_count + admin_count + launch_count + lifecycle_count + orderbook_count + token_account_count + trade_count + failed_tx_count) < decoded_count +ORDER BY decoded_count DESC, de.event_kind; + +-- 08. Failed transaction materialization guard. +-- Expected: zero rows. +SELECT + de.event_kind, + COUNT(*) AS decoded_count, + COUNT(le.id) AS liquidity_count, + COUNT(fe.id) AS fee_count, + COUNT(re.id) AS reward_count, + COUNT(ae.id) AS admin_count, + COUNT(la.id) AS launch_count, + COUNT(pe.id) AS lifecycle_count, + COUNT(oe.id) AS orderbook_count, + COUNT(ta.id) AS token_account_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_liquidity_events le ON le.decoded_event_id = de.id +LEFT JOIN k_sol_fee_events fe ON fe.decoded_event_id = de.id +LEFT JOIN k_sol_reward_events re ON re.decoded_event_id = de.id +LEFT JOIN k_sol_pool_admin_events ae ON ae.decoded_event_id = de.id +LEFT JOIN k_sol_launch_events la ON la.decoded_event_id = de.id +LEFT JOIN k_sol_pool_lifecycle_events pe ON pe.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 ta ON ta.decoded_event_id = de.id +LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_clmm' + AND tx.err_json IS NOT NULL + AND tx.err_json <> '' + AND tx.err_json <> 'null' +GROUP BY de.event_kind +HAVING + COUNT(le.id) > 0 + OR COUNT(fe.id) > 0 + OR COUNT(re.id) > 0 + OR COUNT(ae.id) > 0 + OR COUNT(la.id) > 0 + OR COUNT(pe.id) > 0 + OR COUNT(oe.id) > 0 + OR COUNT(ta.id) > 0 + OR COUNT(te.id) > 0 +ORDER BY de.event_kind; + +-- 09. Residual instruction audit rows. +-- Expected: zero rows after full direct mapping. +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_clmm' + AND event_kind = 'raydium_clmm.instruction_audit' +GROUP BY discriminator_hex +ORDER BY audit_count DESC, discriminator_hex; + +-- 10. Redundant upstream fallback matches for locally covered entries. +-- Expected: zero rows. +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_clmm' +GROUP BY upstream_decoder_code, entry_name, discriminator_hex, source_repo +ORDER BY fallback_count DESC, entry_name; + +-- 11. Decoded event kinds without a coverage entry. +-- Expected: zero rows, excluding explicit `.instruction_audit` if it remains by design. +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_clmm' + AND ce.local_event_kind = de.event_kind +WHERE de.protocol_name = 'raydium_clmm' + AND ce.id IS NULL +GROUP BY de.event_kind +ORDER BY decoded_count DESC, de.event_kind; + +-- 12. Non-swap decoded events that created trades. +-- Expected: zero rows. +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_clmm' + 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_clmm' +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; diff --git a/validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_0_7_50_PRE_R2.sql b/validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_0_7_50_PRE_R2.sql new file mode 100644 index 0000000..6561126 --- /dev/null +++ b/validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_0_7_50_PRE_R2.sql @@ -0,0 +1,315 @@ +-- file: validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_0_7_50_PRE_R2.sql + +-- Raydium CPMM 0.7.50-pre-r2 re-verification pack. +-- Goal: repeat the Launchpad-style closure checks on a dedicated CPMM +-- database, after replay with skipDexDecode='no' and forceDexDecode='yes'. +-- Expected invariants: +-- - no `unknown` or empty event_family except the synthetic `program` row; +-- - no observed discriminator missing from coverage; +-- - no residual upstream fallback for locally-covered entries; +-- - no successful, actionable decoded event left without its materialized row; +-- - no failed transaction materialized into business tables; +-- - decoded-only entries such as CPMM anchor IDL management and CLMM router/program-data +-- corroboration events are excluded from materialization shortfall checks. + +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_cpmm' +ORDER BY entry_kind, entry_name, discriminator_hex; +-> +lp_change_event event liquidity k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_cpmm.lp_change_event 79a3cdc939da753c 25 25 0 +swap_event event swap k_sol_dex_decoded_events_only upstream_git_local_corpus_observed raydium_cpmm.swap_event 40c6cde8260871e2 529 0 0 +anchor_idl_instruction instruction idl_management k_sol_dex_decoded_events_only upstream_git_local_corpus_observed raydium_cpmm.anchor_idl_instruction 40f4bc78a7e9690a 6 0 0 +close_permission_pda instruction admin_config k_sol_pool_admin_events upstream_git_mapped_unverified raydium_cpmm.close_permission_pda 9c5420764587467b 0 0 0 +collect_creator_fee instruction fee k_sol_fee_events upstream_git_local_corpus_materialized raydium_cpmm.collect_creator_fee 1416567bc61cdb84 4 4 0 +collect_fund_fee instruction fee k_sol_fee_events upstream_git_local_corpus_materialized raydium_cpmm.collect_fund_fee a78a4e95dfc2067e 7 7 0 +collect_protocol_fee instruction fee k_sol_fee_events upstream_git_local_corpus_materialized raydium_cpmm.collect_protocol_fee 8888fcddc2427e59 15 15 0 +cpi_event instruction cpi_transport k_sol_dex_decoded_events_only upstream_git_mapped_unverified raydium_cpmm.cpi_event e445a52e51cb9a1d 0 0 0 +create_amm_config instruction admin_config k_sol_pool_admin_events upstream_git_local_corpus_materialized raydium_cpmm.create_amm_config 8934edd4d7756c68 6 6 0 +create_permission_pda instruction admin_config k_sol_pool_admin_events upstream_git_local_corpus_materialized raydium_cpmm.create_permission_pda 878802d889a9b5ca 4 4 0 +deposit instruction liquidity_add k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_cpmm.deposit f223c68952e1f2b6 11 11 0 +initialize instruction pool_create k_sol_pool_lifecycle_events upstream_git_local_corpus_materialized raydium_cpmm.initialize afaf6d1f0d989bed 5 5 0 +initialize_with_permission instruction pool_create k_sol_pool_lifecycle_events upstream_git_local_corpus_materialized raydium_cpmm.initialize_with_permission 3f37fe4131b25979 4 4 0 +swap_base_input instruction swap k_sol_trade_events upstream_git_local_corpus_materialized raydium_cpmm.swap_base_input 8fbe5adac41e33de 750 482 482 +swap_base_output instruction swap k_sol_trade_events upstream_git_local_corpus_materialized raydium_cpmm.swap_base_output 37d96256a34ab4ad 25 17 17 +update_amm_config instruction admin_config k_sol_pool_admin_events upstream_git_local_corpus_materialized raydium_cpmm.update_amm_config 313cae889a1c74c8 13 13 0 +update_pool_status instruction admin_config k_sol_pool_admin_events upstream_git_mapped_unverified raydium_cpmm.update_pool_status 82576c062ee0757b 0 0 0 +withdraw instruction liquidity_remove k_sol_liquidity_events upstream_git_local_corpus_materialized raydium_cpmm.withdraw b712469c946da122 14 14 0 +program program k_sol_dex_decoded_events_only upstream_git_unverified 0 0 0 + +SELECT + decoder_code, + COUNT(*) AS listed_entry_count, + SUM(CASE WHEN local_event_kind IS NOT NULL AND local_event_kind <> '' THEN 1 ELSE 0 END) AS decoded_entry_count, + SUM(CASE WHEN observed_count > 0 THEN 1 ELSE 0 END) AS observed_entry_count, + SUM(CASE WHEN materialized_count > 0 THEN 1 ELSE 0 END) AS materialized_entry_count, + COALESCE(SUM(observed_count), 0) AS total_observed_count, + COALESCE(SUM(materialized_count), 0) AS total_materialized_count, + COALESCE(SUM(trade_count), 0) AS trade_count +FROM k_sol_dex_event_coverage_entries +WHERE decoder_code = 'raydium_cpmm' +GROUP BY decoder_code; +-> +raydium_cpmm 19 18 15 13 1418 607 499 + +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_cpmm' + AND ( + event_family IS NULL + OR event_family = '' + OR event_family = 'unknown' + ) + AND entry_kind <> 'program' +ORDER BY entry_kind, entry_name; +-> vide + +SELECT + instruction_name, + discriminator_hex, + COUNT(*) AS observed_count, + COUNT(DISTINCT signature) AS tx_count +FROM k_sol_instruction_observations +WHERE decoder_code = 'raydium_cpmm' +GROUP BY instruction_name, discriminator_hex +ORDER BY observed_count DESC, instruction_name, discriminator_hex; +-> +raydium_cpmm.swap_base_input 8fbe5adac41e33de 750 741 +raydium_cpmm.swap_base_output 37d96256a34ab4ad 25 25 +raydium_cpmm.collect_protocol_fee 8888fcddc2427e59 15 15 +raydium_cpmm.withdraw b712469c946da122 14 13 +raydium_cpmm.update_amm_config 313cae889a1c74c8 13 5 +raydium_cpmm.deposit f223c68952e1f2b6 11 11 +raydium_cpmm.collect_fund_fee a78a4e95dfc2067e 7 7 +raydium_cpmm.create_amm_config 8934edd4d7756c68 6 4 +raydium_cpmm.initialize afaf6d1f0d989bed 5 5 +raydium_cpmm.collect_creator_fee 1416567bc61cdb84 4 4 +raydium_cpmm.create_permission_pda 878802d889a9b5ca 4 4 +raydium_cpmm.initialize_with_permission 3f37fe4131b25979 4 4 + 40f4bc78a7e9690a 3 3 + +SELECT + io.discriminator_hex, + MIN(io.instruction_name) AS sample_instruction_name, + MIN(io.decoded_event_kind) AS sample_decoded_event_kind, + COUNT(*) AS observed_count, + COUNT(DISTINCT io.signature) AS tx_count +FROM k_sol_instruction_observations io +LEFT JOIN k_sol_dex_event_coverage_entries ce + ON ce.decoder_code = 'raydium_cpmm' + AND ce.discriminator_hex = io.discriminator_hex +WHERE io.decoder_code = 'raydium_cpmm' + AND io.discriminator_hex IS NOT NULL + AND io.discriminator_hex <> '' + AND ce.id IS NULL +GROUP BY io.discriminator_hex +ORDER BY observed_count DESC, io.discriminator_hex; +-> vide + +SELECT + de.event_kind, + COUNT(*) AS decoded_count, + COUNT(le.id) AS liquidity_count, + COUNT(fe.id) AS fee_count, + COUNT(re.id) AS reward_count, + COUNT(ae.id) AS admin_count, + COUNT(la.id) AS launch_count, + COUNT(pe.id) AS lifecycle_count, + COUNT(oe.id) AS orderbook_count, + COUNT(ta.id) AS token_account_count, + COUNT(te.id) AS trade_count +FROM k_sol_dex_decoded_events de +LEFT JOIN k_sol_liquidity_events le ON le.decoded_event_id = de.id +LEFT JOIN k_sol_fee_events fe ON fe.decoded_event_id = de.id +LEFT JOIN k_sol_reward_events re ON re.decoded_event_id = de.id +LEFT JOIN k_sol_pool_admin_events ae ON ae.decoded_event_id = de.id +LEFT JOIN k_sol_launch_events la ON la.decoded_event_id = de.id +LEFT JOIN k_sol_pool_lifecycle_events pe ON pe.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 ta ON ta.decoded_event_id = de.id +LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_cpmm' +GROUP BY de.event_kind +ORDER BY decoded_count DESC, de.event_kind; +-> +raydium_cpmm.swap_base_input 750 0 0 0 0 0 0 0 0 482 +raydium_cpmm.swap_event 529 0 0 0 0 0 0 0 0 0 +raydium_cpmm.lp_change_event 25 25 0 0 0 0 0 0 0 0 +raydium_cpmm.swap_base_output 25 0 0 0 0 0 0 0 0 17 +raydium_cpmm.collect_protocol_fee 15 0 15 0 0 0 0 0 0 0 +raydium_cpmm.withdraw 14 14 0 0 0 0 0 0 0 0 +raydium_cpmm.update_amm_config 13 0 0 0 13 0 0 0 0 0 +raydium_cpmm.deposit 11 11 0 0 0 0 0 0 0 0 +raydium_cpmm.collect_fund_fee 7 0 7 0 0 0 0 0 0 0 +raydium_cpmm.create_amm_config 6 0 0 0 6 0 0 0 0 0 +raydium_cpmm.initialize 5 0 0 0 0 0 5 0 0 0 +raydium_cpmm.collect_creator_fee 4 0 4 0 0 0 0 0 0 0 +raydium_cpmm.create_permission_pda 4 0 0 0 4 0 0 0 0 0 +raydium_cpmm.initialize_with_permission 4 0 0 0 0 0 4 0 0 0 +raydium_cpmm.anchor_idl_instruction 3 0 0 0 0 0 0 0 0 0 +raydium_cpmm.instruction_audit 3 0 0 0 0 0 0 0 0 0 + +SELECT + de.event_kind, + ce.entry_name, + ce.entry_kind, + ce.event_family, + ce.expected_db_target, + COUNT(*) AS decoded_count, + COUNT(CASE WHEN tx.err_json IS NOT NULL AND tx.err_json <> '' AND tx.err_json <> 'null' THEN de.id END) AS failed_tx_count, + COUNT(le.id) AS liquidity_count, + COUNT(fe.id) AS fee_count, + COUNT(re.id) AS reward_count, + COUNT(ae.id) AS admin_count, + COUNT(la.id) AS launch_count, + COUNT(pe.id) AS lifecycle_count, + COUNT(oe.id) AS orderbook_count, + COUNT(ta.id) AS token_account_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_dex_event_coverage_entries ce + ON ce.decoder_code = 'raydium_cpmm' + AND ce.local_event_kind = de.event_kind +LEFT JOIN k_sol_liquidity_events le ON le.decoded_event_id = de.id +LEFT JOIN k_sol_fee_events fe ON fe.decoded_event_id = de.id +LEFT JOIN k_sol_reward_events re ON re.decoded_event_id = de.id +LEFT JOIN k_sol_pool_admin_events ae ON ae.decoded_event_id = de.id +LEFT JOIN k_sol_launch_events la ON la.decoded_event_id = de.id +LEFT JOIN k_sol_pool_lifecycle_events pe ON pe.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 ta ON ta.decoded_event_id = de.id +LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_cpmm' +GROUP BY de.event_kind, ce.entry_name, ce.entry_kind, ce.event_family, ce.expected_db_target +HAVING + COALESCE(ce.expected_db_target, '') <> 'k_sol_dex_decoded_events_only' + AND (liquidity_count + fee_count + reward_count + admin_count + launch_count + lifecycle_count + orderbook_count + token_account_count + trade_count + failed_tx_count) < decoded_count +ORDER BY decoded_count DESC, de.event_kind; +-> vide + +SELECT + de.event_kind, + COUNT(*) AS decoded_count, + COUNT(le.id) AS liquidity_count, + COUNT(fe.id) AS fee_count, + COUNT(re.id) AS reward_count, + COUNT(ae.id) AS admin_count, + COUNT(la.id) AS launch_count, + COUNT(pe.id) AS lifecycle_count, + COUNT(oe.id) AS orderbook_count, + COUNT(ta.id) AS token_account_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_liquidity_events le ON le.decoded_event_id = de.id +LEFT JOIN k_sol_fee_events fe ON fe.decoded_event_id = de.id +LEFT JOIN k_sol_reward_events re ON re.decoded_event_id = de.id +LEFT JOIN k_sol_pool_admin_events ae ON ae.decoded_event_id = de.id +LEFT JOIN k_sol_launch_events la ON la.decoded_event_id = de.id +LEFT JOIN k_sol_pool_lifecycle_events pe ON pe.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 ta ON ta.decoded_event_id = de.id +LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_cpmm' + AND tx.err_json IS NOT NULL + AND tx.err_json <> '' + AND tx.err_json <> 'null' +GROUP BY de.event_kind +HAVING + COUNT(le.id) > 0 + OR COUNT(fe.id) > 0 + OR COUNT(re.id) > 0 + OR COUNT(ae.id) > 0 + OR COUNT(la.id) > 0 + OR COUNT(pe.id) > 0 + OR COUNT(oe.id) > 0 + OR COUNT(ta.id) > 0 + OR COUNT(te.id) > 0 +ORDER BY de.event_kind; +-> vide + +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_cpmm' + AND event_kind = 'raydium_cpmm.instruction_audit' +GROUP BY discriminator_hex +ORDER BY audit_count DESC, discriminator_hex; +-> +40f4bc78a7e9690a 3 3 + +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_cpmm' +GROUP BY upstream_decoder_code, entry_name, discriminator_hex, source_repo +ORDER BY fallback_count DESC, entry_name; +-> vide + +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_cpmm' + AND ce.local_event_kind = de.event_kind +WHERE de.protocol_name = 'raydium_cpmm' + AND ce.id IS NULL +GROUP BY de.event_kind +ORDER BY decoded_count DESC, de.event_kind; +-> +raydium_cpmm.instruction_audit 3 + +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_cpmm' + 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_cpmm' +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; +-> vide \ No newline at end of file diff --git a/validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_0_7_50_RECHECK.sql b/validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_0_7_50_RECHECK.sql new file mode 100644 index 0000000..65749c3 --- /dev/null +++ b/validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_0_7_50_RECHECK.sql @@ -0,0 +1,273 @@ +-- file: validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_0_7_50_RECHECK.sql + +-- Raydium CPMM 0.7.50 re-verification pack. +-- Goal: repeat the Launchpad-style closure checks on a dedicated CPMM +-- database, after replay with skipDexDecode='no' and forceDexDecode='yes'. +-- Expected invariants: +-- - no `unknown` or empty event_family except the synthetic `program` row; +-- - no observed discriminator missing from coverage; +-- - no residual upstream fallback for locally-covered entries; +-- - no successful, actionable decoded event left without its materialized row; +-- - no failed transaction materialized into business tables; +-- - decoded-only entries such as CPMM anchor IDL management and CLMM router/program-data +-- corroboration events are excluded from materialization shortfall checks. + +-- 01. Coverage detail. +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_cpmm' +ORDER BY entry_kind, entry_name, discriminator_hex; + +-- 02. Coverage summary. +SELECT + decoder_code, + COUNT(*) AS listed_entry_count, + SUM(CASE WHEN local_event_kind IS NOT NULL AND local_event_kind <> '' THEN 1 ELSE 0 END) AS decoded_entry_count, + SUM(CASE WHEN observed_count > 0 THEN 1 ELSE 0 END) AS observed_entry_count, + SUM(CASE WHEN materialized_count > 0 THEN 1 ELSE 0 END) AS materialized_entry_count, + COALESCE(SUM(observed_count), 0) AS total_observed_count, + COALESCE(SUM(materialized_count), 0) AS total_materialized_count, + COALESCE(SUM(trade_count), 0) AS trade_count +FROM k_sol_dex_event_coverage_entries +WHERE decoder_code = 'raydium_cpmm' +GROUP BY decoder_code; + +-- 03. Coverage family normalization guard. +-- Expected: zero rows, or only the synthetic `program` row if it intentionally has no family. +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_cpmm' + AND ( + event_family IS NULL + OR event_family = '' + OR event_family = 'unknown' + ) + AND entry_kind <> 'program' +ORDER BY entry_kind, entry_name; + +-- 04. Instruction observations by discriminator. +SELECT + instruction_name, + discriminator_hex, + COUNT(*) AS observed_count, + COUNT(DISTINCT signature) AS tx_count +FROM k_sol_instruction_observations +WHERE decoder_code = 'raydium_cpmm' +GROUP BY instruction_name, discriminator_hex +ORDER BY observed_count DESC, instruction_name, discriminator_hex; + +-- 05. Observed discriminators absent from coverage. +-- Expected: zero rows. +SELECT + io.discriminator_hex, + MIN(io.instruction_name) AS sample_instruction_name, + MIN(io.decoded_event_kind) AS sample_decoded_event_kind, + COUNT(*) AS observed_count, + COUNT(DISTINCT io.signature) AS tx_count +FROM k_sol_instruction_observations io +LEFT JOIN k_sol_dex_event_coverage_entries ce + ON ce.decoder_code = 'raydium_cpmm' + AND ce.discriminator_hex = io.discriminator_hex +WHERE io.decoder_code = 'raydium_cpmm' + AND io.discriminator_hex IS NOT NULL + AND io.discriminator_hex <> '' + AND ce.id IS NULL +GROUP BY io.discriminator_hex +ORDER BY observed_count DESC, io.discriminator_hex; + +-- 06. Decoded event distribution and materialization links. +SELECT + de.event_kind, + COUNT(*) AS decoded_count, + COUNT(le.id) AS liquidity_count, + COUNT(fe.id) AS fee_count, + COUNT(re.id) AS reward_count, + COUNT(ae.id) AS admin_count, + COUNT(la.id) AS launch_count, + COUNT(pe.id) AS lifecycle_count, + COUNT(oe.id) AS orderbook_count, + COUNT(ta.id) AS token_account_count, + COUNT(te.id) AS trade_count +FROM k_sol_dex_decoded_events de +LEFT JOIN k_sol_liquidity_events le ON le.decoded_event_id = de.id +LEFT JOIN k_sol_fee_events fe ON fe.decoded_event_id = de.id +LEFT JOIN k_sol_reward_events re ON re.decoded_event_id = de.id +LEFT JOIN k_sol_pool_admin_events ae ON ae.decoded_event_id = de.id +LEFT JOIN k_sol_launch_events la ON la.decoded_event_id = de.id +LEFT JOIN k_sol_pool_lifecycle_events pe ON pe.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 ta ON ta.decoded_event_id = de.id +LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_cpmm' +GROUP BY de.event_kind +ORDER BY decoded_count DESC, de.event_kind; + +-- 07. Successful decoded events not materialized where coverage target says they should be. +-- Expected: zero rows, except expected_db_target='k_sol_dex_decoded_events_only' and failed tx rows. +SELECT + de.event_kind, + ce.entry_name, + ce.entry_kind, + ce.event_family, + ce.expected_db_target, + COUNT(*) AS decoded_count, + COUNT(CASE WHEN tx.err_json IS NOT NULL AND tx.err_json <> '' AND tx.err_json <> 'null' THEN de.id END) AS failed_tx_count, + COUNT(le.id) AS liquidity_count, + COUNT(fe.id) AS fee_count, + COUNT(re.id) AS reward_count, + COUNT(ae.id) AS admin_count, + COUNT(la.id) AS launch_count, + COUNT(pe.id) AS lifecycle_count, + COUNT(oe.id) AS orderbook_count, + COUNT(ta.id) AS token_account_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_dex_event_coverage_entries ce + ON ce.decoder_code = 'raydium_cpmm' + AND ce.local_event_kind = de.event_kind +LEFT JOIN k_sol_liquidity_events le ON le.decoded_event_id = de.id +LEFT JOIN k_sol_fee_events fe ON fe.decoded_event_id = de.id +LEFT JOIN k_sol_reward_events re ON re.decoded_event_id = de.id +LEFT JOIN k_sol_pool_admin_events ae ON ae.decoded_event_id = de.id +LEFT JOIN k_sol_launch_events la ON la.decoded_event_id = de.id +LEFT JOIN k_sol_pool_lifecycle_events pe ON pe.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 ta ON ta.decoded_event_id = de.id +LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_cpmm' +GROUP BY de.event_kind, ce.entry_name, ce.entry_kind, ce.event_family, ce.expected_db_target +HAVING + COALESCE(ce.expected_db_target, '') <> 'k_sol_dex_decoded_events_only' + AND (liquidity_count + fee_count + reward_count + admin_count + launch_count + lifecycle_count + orderbook_count + token_account_count + trade_count + failed_tx_count) < decoded_count +ORDER BY decoded_count DESC, de.event_kind; + +-- 08. Failed transaction materialization guard. +-- Expected: zero rows. +SELECT + de.event_kind, + COUNT(*) AS decoded_count, + COUNT(le.id) AS liquidity_count, + COUNT(fe.id) AS fee_count, + COUNT(re.id) AS reward_count, + COUNT(ae.id) AS admin_count, + COUNT(la.id) AS launch_count, + COUNT(pe.id) AS lifecycle_count, + COUNT(oe.id) AS orderbook_count, + COUNT(ta.id) AS token_account_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_liquidity_events le ON le.decoded_event_id = de.id +LEFT JOIN k_sol_fee_events fe ON fe.decoded_event_id = de.id +LEFT JOIN k_sol_reward_events re ON re.decoded_event_id = de.id +LEFT JOIN k_sol_pool_admin_events ae ON ae.decoded_event_id = de.id +LEFT JOIN k_sol_launch_events la ON la.decoded_event_id = de.id +LEFT JOIN k_sol_pool_lifecycle_events pe ON pe.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 ta ON ta.decoded_event_id = de.id +LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_cpmm' + AND tx.err_json IS NOT NULL + AND tx.err_json <> '' + AND tx.err_json <> 'null' +GROUP BY de.event_kind +HAVING + COUNT(le.id) > 0 + OR COUNT(fe.id) > 0 + OR COUNT(re.id) > 0 + OR COUNT(ae.id) > 0 + OR COUNT(la.id) > 0 + OR COUNT(pe.id) > 0 + OR COUNT(oe.id) > 0 + OR COUNT(ta.id) > 0 + OR COUNT(te.id) > 0 +ORDER BY de.event_kind; + +-- 09. Residual instruction audit rows. +-- Expected: zero rows after full direct mapping. `40f4bc78a7e9690a` should now +-- appear as `raydium_cpmm.anchor_idl_instruction`, not 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_cpmm' + AND event_kind = 'raydium_cpmm.instruction_audit' +GROUP BY discriminator_hex +ORDER BY audit_count DESC, discriminator_hex; + +-- 10. Redundant upstream fallback matches for locally covered entries. +-- Expected: zero rows. +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_cpmm' +GROUP BY upstream_decoder_code, entry_name, discriminator_hex, source_repo +ORDER BY fallback_count DESC, entry_name; + +-- 11. Decoded event kinds without a coverage entry. +-- Expected: zero rows, excluding explicit `.instruction_audit` if it remains by design. +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_cpmm' + AND ce.local_event_kind = de.event_kind +WHERE de.protocol_name = 'raydium_cpmm' + AND ce.id IS NULL +GROUP BY de.event_kind +ORDER BY decoded_count DESC, de.event_kind; + +-- 12. Non-swap decoded events that created trades. +-- Expected: zero rows. +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_cpmm' + 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_cpmm' +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; diff --git a/validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_AUDIT_CLEANUP_0_7_50_FINAL.sql b/validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_AUDIT_CLEANUP_0_7_50_FINAL.sql new file mode 100644 index 0000000..f5cc64b --- /dev/null +++ b/validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_AUDIT_CLEANUP_0_7_50_FINAL.sql @@ -0,0 +1,46 @@ +-- file: validation_sql/SQL_VALIDATION_RAYDIUM_CPMM_AUDIT_CLEANUP_0_7_50_FINAL.sql + +-- Raydium CPMM residual audit cleanup validation for 0.7.50 final. +-- These queries must be empty after applying the final cleanup patch and replaying with +-- skipDexDecode=no, forceDexDecode=yes, deferInstructionObservations=yes. + +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_cpmm' + AND event_kind = 'raydium_cpmm.instruction_audit' +GROUP BY discriminator_hex +ORDER BY audit_count DESC, discriminator_hex; + +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_cpmm' + AND ce.local_event_kind = de.event_kind +WHERE de.protocol_name = 'raydium_cpmm' + AND ce.id IS NULL +GROUP BY de.event_kind +ORDER BY decoded_count DESC, de.event_kind; + +-- Manual emergency cleanup only if the application replay still leaves the legacy audit rows. +-- It intentionally unlinks k_sol_instruction_observations first, because existing DBs created +-- before ON DELETE SET NULL may otherwise retain decoded_event_id references. + +UPDATE k_sol_instruction_observations +SET decoded_event_id = NULL +WHERE decoded_event_id IN ( + SELECT id + FROM k_sol_dex_decoded_events + WHERE protocol_name = 'raydium_cpmm' + AND event_kind = 'raydium_cpmm.instruction_audit' + AND instr(lower(COALESCE(payload_json, '')), '40f4bc78a7e9690a') > 0 +); + +DELETE FROM k_sol_dex_decoded_events +WHERE protocol_name = 'raydium_cpmm' + AND event_kind = 'raydium_cpmm.instruction_audit' + AND instr(lower(COALESCE(payload_json, '')), '40f4bc78a7e9690a') > 0; diff --git a/validation_sql/SQL_VALIDATION_RAYDIUM_LAUNCHPAD_0_7_50.sql b/validation_sql/SQL_VALIDATION_RAYDIUM_LAUNCHPAD_0_7_50.sql new file mode 100644 index 0000000..dd8b016 --- /dev/null +++ b/validation_sql/SQL_VALIDATION_RAYDIUM_LAUNCHPAD_0_7_50.sql @@ -0,0 +1,347 @@ +-- file: validation_sql/SQL_VALIDATION_RAYDIUM_LAUNCHPAD_0_7_50.sql + +-- Raydium Launchpad 0.7.50 validation pack. +-- Notes: +-- - k_sol_pools uses column `address`, not `pool_address`. +-- - raydium_pool_v4 is deliberately excluded from runtime validation until its +-- program id and role are confirmed from source/corpus audit. + +-- 01. Coverage detail. +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_launchpad' +ORDER BY entry_kind, entry_name, discriminator_hex; + +-- 02. Coverage summary. +SELECT + decoder_code, + COUNT(*) AS listed_entry_count, + SUM(CASE WHEN local_event_kind IS NOT NULL AND local_event_kind <> '' THEN 1 ELSE 0 END) AS decoded_entry_count, + SUM(CASE WHEN observed_count > 0 THEN 1 ELSE 0 END) AS observed_entry_count, + SUM(CASE WHEN materialized_count > 0 THEN 1 ELSE 0 END) AS materialized_entry_count, + COALESCE(SUM(observed_count), 0) AS total_observed_count, + COALESCE(SUM(materialized_count), 0) AS total_materialized_count, + COALESCE(SUM(trade_count), 0) AS trade_count +FROM k_sol_dex_event_coverage_entries +WHERE decoder_code = 'raydium_launchpad' +GROUP BY decoder_code; + +-- 03. 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_launchpad' +GROUP BY instruction_name, discriminator_hex +ORDER BY observed_count DESC, instruction_name; + +-- 04. Decoded event distribution. +SELECT + de.event_kind, + COUNT(*) AS decoded_count, + COUNT(te.id) AS trade_count +FROM k_sol_dex_decoded_events de +LEFT JOIN k_sol_trade_events te + ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_launchpad' +GROUP BY de.event_kind +ORDER BY decoded_count DESC, de.event_kind; + +-- 05. Upstream fallback residual. Expected: empty. +SELECT + json_extract(payload_json, '$.upstreamDecoderCode') AS upstream_decoder_code, + json_extract(payload_json, '$.upstreamEntryName') AS entry_name, + json_extract(payload_json, '$.upstreamDiscriminatorHex') AS discriminator_hex, + COUNT(*) AS fallback_count +FROM k_sol_dex_decoded_events +WHERE protocol_name = 'upstream_git' + AND event_kind = 'upstream_git.instruction_match' + AND json_extract(payload_json, '$.upstreamDecoderCode') = 'raydium_launchpad' +GROUP BY upstream_decoder_code, entry_name, discriminator_hex +ORDER BY fallback_count DESC, entry_name; + +-- 06. Failed transaction materialization guard. Expected: trade_count = 0. +SELECT + de.event_kind, + 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_launchpad' + AND tx.err_json IS NOT NULL + AND tx.err_json <> '' + AND tx.err_json <> 'null' +GROUP BY de.event_kind +ORDER BY trade_count DESC, decoded_count DESC; + +-- 07. Residual audit discriminators. +-- Expected after pre5: e445a52e51cb9a1d should no longer remain in decoded audit rows. +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_launchpad' + AND event_kind = 'raydium_launchpad.instruction_audit' +GROUP BY discriminator_hex +ORDER BY audit_count DESC, discriminator_hex; + +-- 08. Self-CPI transport still present inside residual audit rows. +-- Expected after pre5: empty for known embedded event discriminators. +SELECT + json_extract(payload_json, '$.anchorSelfCpiLog') AS anchor_self_cpi_log, + json_extract(payload_json, '$.anchorSelfCpiLogSelectorHex') AS cpi_selector_hex, + json_extract(payload_json, '$.anchorEventDiscriminatorHex') AS anchor_event_discriminator_hex, + COUNT(*) AS decoded_count, + COUNT(DISTINCT transaction_id) AS tx_count +FROM k_sol_dex_decoded_events +WHERE protocol_name = 'raydium_launchpad' + AND event_kind = 'raydium_launchpad.instruction_audit' + AND json_extract(payload_json, '$.anchorSelfCpiLog') = 1 +GROUP BY + anchor_self_cpi_log, + cpi_selector_hex, + anchor_event_discriminator_hex +ORDER BY decoded_count DESC, anchor_event_discriminator_hex; + +-- 09. Direct Launchpad self-CPI events. +SELECT + event_kind, + json_extract(payload_json, '$.anchorEventDiscriminatorHex') AS anchor_event_discriminator_hex, + COUNT(*) AS decoded_count, + COUNT(DISTINCT transaction_id) AS tx_count +FROM k_sol_dex_decoded_events +WHERE protocol_name = 'raydium_launchpad' + AND event_kind IN ( + 'raydium_launchpad.trade_event', + 'raydium_launchpad.pool_create_event', + 'raydium_launchpad.claim_vested_event', + 'raydium_launchpad.create_vesting_event' + ) +GROUP BY event_kind, anchor_event_discriminator_hex +ORDER BY decoded_count DESC, event_kind; + +-- 10. Launchpad initialize* pool hints. +SELECT + de.event_kind, + de.pool_account, + de.token_a_mint, + de.token_b_mint, + json_extract(de.payload_json, '$.instructionName') AS instruction_name, + COUNT(*) AS decoded_count, + COUNT(DISTINCT de.transaction_id) AS tx_count +FROM k_sol_dex_decoded_events de +WHERE de.protocol_name = 'raydium_launchpad' + AND de.event_kind IN ( + 'raydium_launchpad.initialize', + 'raydium_launchpad.initialize_v2', + 'raydium_launchpad.initialize_with_token_2022' + ) +GROUP BY + de.event_kind, + de.pool_account, + de.token_a_mint, + de.token_b_mint, + instruction_name +ORDER BY decoded_count DESC, de.event_kind; + +-- 11. Launchpad pool/pair catalog materialized from initialize*. +SELECT + p.address AS pool_address, + pa.id AS pair_id, + d.code AS dex_code, + bt.mint AS base_mint, + bt.symbol AS base_symbol, + qt.mint AS quote_mint, + qt.symbol AS quote_symbol, + p.pool_kind, + p.status +FROM k_sol_pools p +JOIN k_sol_dexes d + ON d.id = p.dex_id +LEFT JOIN k_sol_pairs pa + ON pa.pool_id = p.id +LEFT JOIN k_sol_tokens bt + ON bt.id = pa.base_token_id +LEFT JOIN k_sol_tokens qt + ON qt.id = pa.quote_token_id +WHERE d.code = 'raydium_launchpad' +ORDER BY p.id DESC; + +-- 12. Specific pool/pair probe. +SELECT + p.address AS pool_address, + pa.id AS pair_id, + d.code AS dex_code +FROM k_sol_pools p +JOIN k_sol_dexes d + ON d.id = p.dex_id +LEFT JOIN k_sol_pairs pa + ON pa.pool_id = p.id +WHERE p.address = '6HLQPoLrzX6LqePRiXQ1GGs2Dd9K3dp9VhTSHBugYzzZ'; + +-- 13. Pool-create events that do not yet have normalized local catalog rows. +SELECT + de.pool_account, + COUNT(*) AS decoded_count, + COUNT(DISTINCT de.transaction_id) AS tx_count +FROM k_sol_dex_decoded_events de +LEFT JOIN k_sol_pools p + ON p.address = de.pool_account +WHERE de.protocol_name = 'raydium_launchpad' + AND de.event_kind = 'raydium_launchpad.pool_create_event' + AND p.id IS NULL +GROUP BY de.pool_account +ORDER BY decoded_count DESC, de.pool_account; + +-- 14. Launchpad buy/sell instruction pool hints used to backfill catalog rows. +SELECT + de.event_kind, + de.pool_account, + de.token_a_mint, + de.token_b_mint, + json_extract(de.payload_json, '$.instructionName') AS instruction_name, + COUNT(*) AS decoded_count, + COUNT(DISTINCT de.transaction_id) AS tx_count +FROM k_sol_dex_decoded_events de +WHERE de.protocol_name = 'raydium_launchpad' + AND de.event_kind IN ( + 'raydium_launchpad.buy_exact_in', + 'raydium_launchpad.buy_exact_out', + 'raydium_launchpad.sell_exact_in', + 'raydium_launchpad.sell_exact_out' + ) +GROUP BY + de.event_kind, + de.pool_account, + de.token_a_mint, + de.token_b_mint, + instruction_name +ORDER BY decoded_count DESC, de.event_kind; + +-- 15. Direct Launchpad trade-event materialization readiness. +SELECT + de.pool_account, + json_extract(de.payload_json, '$.tradeSide') AS trade_side, + json_extract(de.payload_json, '$.baseAmountRaw') AS base_amount_raw, + json_extract(de.payload_json, '$.quoteAmountRaw') AS quote_amount_raw, + json_extract(de.payload_json, '$.tradeCandidate') AS trade_candidate, + json_extract(de.payload_json, '$.candleCandidate') AS candle_candidate, + COUNT(*) AS decoded_count, + COUNT(te.id) AS trade_count, + COUNT(DISTINCT de.transaction_id) AS tx_count +FROM k_sol_dex_decoded_events de +LEFT JOIN k_sol_trade_events te + ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_launchpad' + AND de.event_kind = 'raydium_launchpad.trade_event' +GROUP BY + de.pool_account, + trade_side, + base_amount_raw, + quote_amount_raw, + trade_candidate, + candle_candidate +ORDER BY decoded_count DESC, de.pool_account; + +-- 16. Launchpad pairs with decoded trade events but no materialized trade rows. +SELECT + p.address AS pool_address, + pa.id AS pair_id, + COUNT(de.id) AS decoded_trade_event_count, + COUNT(te.id) AS materialized_trade_count +FROM k_sol_pools p +JOIN k_sol_dexes d + ON d.id = p.dex_id +JOIN k_sol_pairs pa + ON pa.pool_id = p.id +LEFT JOIN k_sol_dex_decoded_events de + ON de.pool_account = p.address + AND de.protocol_name = 'raydium_launchpad' + AND de.event_kind = 'raydium_launchpad.trade_event' +LEFT JOIN k_sol_trade_events te + ON te.decoded_event_id = de.id +WHERE d.code = 'raydium_launchpad' +GROUP BY p.address, pa.id +HAVING decoded_trade_event_count > 0 +ORDER BY decoded_trade_event_count DESC, p.address; + +-- 17. Launchpad candles by pair after trade-event promotion. +SELECT + pair_id, + timeframe_seconds, + COUNT(*) AS candle_count, + SUM(trade_count) AS trade_count, + MIN(bucket_start_unix) AS first_bucket_start_unix, + MAX(bucket_start_unix) AS last_bucket_start_unix +FROM k_sol_pair_candles +WHERE pair_id IN ( + SELECT pa.id + FROM k_sol_pairs pa + JOIN k_sol_pools p ON p.id = pa.pool_id + JOIN k_sol_dexes d ON d.id = p.dex_id + WHERE d.code = 'raydium_launchpad' +) +GROUP BY pair_id, timeframe_seconds +ORDER BY pair_id, timeframe_seconds; + +-- 18. Launchpad trade events still not materializable after pre7. +-- Expected after pre7: rows should be limited to failed transactions or missing token balance metadata. +SELECT + de.pool_account, + COUNT(*) AS decoded_count, + COUNT(CASE WHEN tx.err_json IS NOT NULL AND tx.err_json <> '' AND tx.err_json <> 'null' THEN de.id END) AS failed_tx_count, + COUNT(CASE WHEN json_extract(de.payload_json, '$.tradeCandidate') = 1 THEN de.id END) AS trade_candidate_count, + COUNT(CASE WHEN json_extract(de.payload_json, '$.baseAmountRaw') IS NULL THEN de.id END) AS missing_base_amount_count, + COUNT(CASE WHEN json_extract(de.payload_json, '$.quoteAmountRaw') IS NULL THEN de.id END) AS missing_quote_amount_count, + COUNT(te.id) AS materialized_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_launchpad' + AND de.event_kind = 'raydium_launchpad.trade_event' +GROUP BY de.pool_account +HAVING materialized_trade_count = 0 +ORDER BY decoded_count DESC, de.pool_account; + +-- 19. Launchpad event payload parser health. +-- Expected after pre7: direct trade_event rows should expose raw amount fields for successful self-CPI events. +SELECT + json_extract(payload_json, '$.anchorSelfCpiDataLength') AS anchor_self_cpi_data_length, + json_extract(payload_json, '$.anchorEventPayloadSize') AS anchor_event_payload_size, + json_extract(payload_json, '$.tradeDirectionRaw') AS trade_direction_raw, + json_extract(payload_json, '$.amountInRaw') AS amount_in_raw, + json_extract(payload_json, '$.amountOutRaw') AS amount_out_raw, + json_extract(payload_json, '$.baseAmountRaw') AS base_amount_raw, + json_extract(payload_json, '$.quoteAmountRaw') AS quote_amount_raw, + COUNT(*) AS decoded_count, + COUNT(DISTINCT transaction_id) AS tx_count +FROM k_sol_dex_decoded_events +WHERE protocol_name = 'raydium_launchpad' + AND event_kind = 'raydium_launchpad.trade_event' +GROUP BY + anchor_self_cpi_data_length, + anchor_event_payload_size, + trade_direction_raw, + amount_in_raw, + amount_out_raw, + base_amount_raw, + quote_amount_raw +ORDER BY decoded_count DESC; diff --git a/validation_sql/SQL_VALIDATION_RAYDIUM_LAUNCHPAD_0_7_50_PRE9.sql b/validation_sql/SQL_VALIDATION_RAYDIUM_LAUNCHPAD_0_7_50_PRE9.sql new file mode 100644 index 0000000..25835a6 --- /dev/null +++ b/validation_sql/SQL_VALIDATION_RAYDIUM_LAUNCHPAD_0_7_50_PRE9.sql @@ -0,0 +1,112 @@ +-- file: validation_sql/SQL_VALIDATION_RAYDIUM_LAUNCHPAD_0_7_50_PRE9.sql +-- Raydium Launchpad pre9 validation additions. + +-- 1. Coverage entries, including late Carbon instructions and launch-event target table. +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_launchpad' +ORDER BY entry_kind, entry_name, discriminator_hex; + +-- 2. Launchpad-specific materialized events. +SELECT + le.event_kind, + le.event_role, + le.pool_account, + COUNT(*) AS launch_event_count, + COUNT(DISTINCT le.signature) AS tx_count +FROM k_sol_launch_events le +WHERE le.protocol_name = 'raydium_launchpad' +GROUP BY le.event_kind, le.event_role, le.pool_account +ORDER BY launch_event_count DESC, le.event_kind, le.pool_account; + +-- 3. Fee materialization for Launchpad fee instructions. +SELECT + fe.event_kind, + fe.pool_account, + COUNT(*) AS fee_event_count, + COUNT(DISTINCT fe.signature) AS tx_count +FROM k_sol_fee_events fe +WHERE fe.protocol_name = 'raydium_launchpad' +GROUP BY fe.event_kind, fe.pool_account +ORDER BY fee_event_count DESC, fe.event_kind; + +-- 4. Admin/config/access materialization for Launchpad admin instructions. +SELECT + ae.event_kind, + ae.admin_action, + ae.pool_account, + COUNT(*) AS admin_event_count, + COUNT(DISTINCT ae.signature) AS tx_count +FROM k_sol_pool_admin_events ae +WHERE ae.protocol_name = 'raydium_launchpad' +GROUP BY ae.event_kind, ae.admin_action, ae.pool_account +ORDER BY admin_event_count DESC, ae.event_kind; + +-- 5. Late Carbon instruction corpus identifiers. +SELECT + instruction_name, + discriminator_hex, + COUNT(*) AS observed_count, + COUNT(DISTINCT signature) AS tx_count +FROM k_sol_instruction_observations +WHERE decoder_code = 'raydium_launchpad' + AND discriminator_hex IN ( + '7bb4b8816fb9bb3b', -- close_platform_global_access + 'a25b92c75d85eaed', -- create_platform_global_access + '9247ad4562130f6a', -- create_platform_vesting_account + 'e445a52e51cb9a1d' -- cpi_event / Anchor self-CPI transport + ) +GROUP BY instruction_name, discriminator_hex +ORDER BY observed_count DESC, discriminator_hex; + +-- 6. Residual Launchpad decoded events that are still only decoded, not materialized. +SELECT + de.event_kind, + COUNT(*) AS decoded_count, + COUNT(fe.id) AS fee_count, + COUNT(ae.id) AS admin_count, + COUNT(le.id) AS launch_count, + COUNT(pe.id) AS lifecycle_count, + COUNT(te.id) AS trade_count +FROM k_sol_dex_decoded_events de +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_launch_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_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_launchpad' +GROUP BY de.event_kind +ORDER BY decoded_count DESC, de.event_kind; + +-- 7. Residual decoded-only Launchpad events split by failed/success transaction. +SELECT + de.event_kind, + COUNT(*) AS decoded_count, + COUNT(CASE WHEN tx.err_json IS NOT NULL AND tx.err_json <> '' AND tx.err_json <> 'null' THEN de.id END) AS failed_tx_count, + COUNT(fe.id) AS fee_count, + COUNT(ae.id) AS admin_count, + COUNT(le.id) AS launch_count, + COUNT(pe.id) AS lifecycle_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_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_launch_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_trade_events te ON te.decoded_event_id = de.id +WHERE de.protocol_name = 'raydium_launchpad' +GROUP BY de.event_kind +HAVING (fee_count + admin_count + launch_count + lifecycle_count + trade_count) < decoded_count +ORDER BY decoded_count DESC, de.event_kind;