0.7.54
This commit is contained in:
@@ -85,4 +85,5 @@
|
|||||||
0.7.50-pre-r2 - Clôture CPMM/CLMM post-Launchpad : ajout des entrées Carbon `cpi_event` pour `raydium_cpmm` et `raydium_clmm`, ajout de `raydium_clmm.update_dynamic_fee_config`, normalisation des Program-data events CLMM, ajout de la table `k_sol_token_account_events` et de la matérialisation `create_support_mint_associated`, reclassement des familles ambiguës (`cpi_transport`, `liquidity_calculation`, `liquidity_change`, `position_open`, `pool_create`, `admin_config`, `account_create`, `idl_management`), codage du discriminant CPMM `40f4bc78a7e9690a` comme `raydium_cpmm.anchor_idl_instruction` decoded-only après inspection Solscan, et contexte de secours pour matérialisation liquidity CLMM via événements frères quand possible.
|
0.7.50-pre-r2 - Clôture CPMM/CLMM post-Launchpad : ajout des entrées Carbon `cpi_event` pour `raydium_cpmm` et `raydium_clmm`, ajout de `raydium_clmm.update_dynamic_fee_config`, normalisation des Program-data events CLMM, ajout de la table `k_sol_token_account_events` et de la matérialisation `create_support_mint_associated`, reclassement des familles ambiguës (`cpi_transport`, `liquidity_calculation`, `liquidity_change`, `position_open`, `pool_create`, `admin_config`, `account_create`, `idl_management`), codage du discriminant CPMM `40f4bc78a7e9690a` comme `raydium_cpmm.anchor_idl_instruction` decoded-only après inspection Solscan, et contexte de secours pour matérialisation liquidity CLMM via événements frères quand possible.
|
||||||
0.7.51 - Raydium AMM v4 event coverage clôturé : decoder maximal local pour tous les discriminants officiels AMM v4 `00..11`, spécialisation des swaps `swap_base_in/out` et `swap_base_in/out_v2`, suppression durable du `raydium_amm_v4.swap` legacy, index AMM v4 en discriminant 1 octet, matérialisation validée des swaps, liquidity, lifecycle, fees, admin/config et side effects orderbook, `pre_initialize` conservé comme lifecycle audit deprecated/partial, `simulate_info` decoded-only, reset replay renforcé par `protocol_name`, validation des invariants failed/non-swap/single-target/unexplained gaps et maintien de `raydium_pool_v4` en audit conditionnel sans decoder autonome.
|
0.7.51 - Raydium AMM v4 event coverage clôturé : decoder maximal local pour tous les discriminants officiels AMM v4 `00..11`, spécialisation des swaps `swap_base_in/out` et `swap_base_in/out_v2`, suppression durable du `raydium_amm_v4.swap` legacy, index AMM v4 en discriminant 1 octet, matérialisation validée des swaps, liquidity, lifecycle, fees, admin/config et side effects orderbook, `pre_initialize` conservé comme lifecycle audit deprecated/partial, `simulate_info` decoded-only, reset replay renforcé par `protocol_name`, validation des invariants failed/non-swap/single-target/unexplained gaps et maintien de `raydium_pool_v4` en audit conditionnel sans decoder autonome.
|
||||||
0.7.52 - Raydium Stable Swap event coverage clôturé : decoder legacy 1 octet pour la surface locale `00..0d`, matérialisation lifecycle/liquidity/admin/fee/orderbook selon contexte, swaps `swap_base_in/out` matérialisés uniquement depuis deltas de vaults exacts (`stable_swap_vault_balance_delta`), conservation des bornes d’instruction comme audit-only, failed transactions decoded-only avec skip reasons, validation locale 407 tests et clippy `-D warnings` OK.
|
0.7.52 - Raydium Stable Swap event coverage clôturé : decoder legacy 1 octet pour la surface locale `00..0d`, matérialisation lifecycle/liquidity/admin/fee/orderbook selon contexte, swaps `swap_base_in/out` matérialisés uniquement depuis deltas de vaults exacts (`stable_swap_vault_balance_delta`), conservation des bornes d’instruction comme audit-only, failed transactions decoded-only avec skip reasons, validation locale 407 tests et clippy `-D warnings` OK.
|
||||||
0.7.53 - Clôture PumpSwap : décodage transaction/log complet, matérialisation `buy/sell/buy_exact_quote_in` depuis sources exactes, events Anchor audit-only, tests synthétiques IDL, validation globale coverage SQL et non-régression Raydium.
|
0.7.53 - Clôture PumpSwap : décodage transaction/log complet, matérialisation `buy/sell/buy_exact_quote_in` depuis sources exactes, events Anchor audit-only, tests synthétiques IDL, validation globale coverage SQL et non-régression Raydium.
|
||||||
|
0.7.54 - Clôture Pump.fun : decoder maximal local depuis IDL Solscan/upstream, décodage des 40 instructions et 23 events Anchor connus, matérialisation validée des trades `buy/sell/buy_exact_sol_in` et `trade_event` v2/exact sans double-count, non-trades launch/fee/reward/admin selon contexte, validation SQL Pump.fun propre et ouverture de `0.7.55 pump_fees`.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.7.53"
|
version = "0.7.54"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
|
||||||
|
|||||||
88
README.md
88
README.md
@@ -2,6 +2,74 @@
|
|||||||
|
|
||||||
# khadhroony-bobobot
|
# khadhroony-bobobot
|
||||||
|
|
||||||
|
|
||||||
|
## État final validé `0.7.54` — `pump_fun`
|
||||||
|
|
||||||
|
La tranche `0.7.54 pump_fun` est clôturée côté coverage, décodage local maximal, matérialisation métier prudente et validation SQL. Elle ferme la surface Pump.fun principale avant l'ouverture de `0.7.55 pump_fees`.
|
||||||
|
|
||||||
|
Program id canonique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P
|
||||||
|
```
|
||||||
|
|
||||||
|
Source IDL locale prioritaire :
|
||||||
|
|
||||||
|
```text
|
||||||
|
idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Points verrouillés :
|
||||||
|
|
||||||
|
- les `40` instructions et `23` events Anchor connus par l'IDL locale sont inventoriés et couverts localement ;
|
||||||
|
- les instructions IDL-only absentes du registre upstream initial sont intégrées côté coverage, notamment `buy_v2`, `sell_v2`, `buy_exact_quote_in_v2`, `migrate_v2`, `claim_cashback_v2`, `collect_creator_fee_v2`, `distribute_creator_fees_v2` et `update_buyback_config` ;
|
||||||
|
- `pump_fun.buy` et `pump_fun.sell` restent matérialisés directement comme trades quand les montants sont fiables ;
|
||||||
|
- `pump_fun.buy_exact_sol_in` est matérialisé directement, y compris pour les logs `Program data` Anchor tronqués quand les montants exacts sont extractibles ;
|
||||||
|
- `pump_fun.buy_v2`, `pump_fun.sell_v2` et `pump_fun.buy_exact_quote_in_v2` restent des instructions audit/coverage/routing : elles ne sont pas matérialisées directement ;
|
||||||
|
- la matérialisation canonique des trades v2/exact passe par `pump_fun.trade_event` quand l'event Anchor porte les montants exécutés et se corrèle sans ambiguïté à l'instruction ;
|
||||||
|
- les `trade_event` couverts par un trade direct reçoivent un skip explicite afin d'éviter le double-count ;
|
||||||
|
- les familles non-trade alimentent uniquement les tables prévues (`launch`, `fee`, `reward`, `admin`, `lifecycle`) ou restent decoded-only/audit-only avec raison explicite ;
|
||||||
|
- les transactions failed restent décodables pour audit mais ne produisent aucun business event.
|
||||||
|
|
||||||
|
Validation locale finale rapportée après replay forcé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
1679 replayed
|
||||||
|
0 decode skipped
|
||||||
|
1679 ledger upserts
|
||||||
|
145 unsafe ledger rows
|
||||||
|
89 trades
|
||||||
|
0 liquidity
|
||||||
|
10 lifecycle
|
||||||
|
0 tokenAccount
|
||||||
|
348 candle upserts
|
||||||
|
instructionObservations = 13905
|
||||||
|
resetDeleted = 1112
|
||||||
|
catalog = 52 tokens / 50 pools / 50 pairs
|
||||||
|
```
|
||||||
|
|
||||||
|
Matérialisation Pump.fun finale observée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
pump_fun.buy 17 trades
|
||||||
|
pump_fun.sell 25 trades
|
||||||
|
pump_fun.buy_exact_sol_in 15 trades
|
||||||
|
pump_fun.trade_event 25 trades
|
||||||
|
```
|
||||||
|
|
||||||
|
Checks de fermeture :
|
||||||
|
|
||||||
|
- fallback `upstream_git` Pump.fun : vide ;
|
||||||
|
- decoded Pump.fun sans coverage : vide ;
|
||||||
|
- fallback upstream résiduel pour entrées couvertes : vide ;
|
||||||
|
- successful non-materialized sans skip reason : vide ;
|
||||||
|
- failed transaction avec business materialization : vide ;
|
||||||
|
- multi-target materialization : vide ;
|
||||||
|
- trade candidates Pump.fun sans matérialisation ni skip : vide ;
|
||||||
|
- watchlist globale : plus aucun `pump_fun`, backlog restant principalement `pump_fees`, puis `jupiter_swap` et `dflow_aggregator_v4`.
|
||||||
|
|
||||||
|
La suite immédiate est `0.7.55 pump_fees` sur nouvelle base SQLite, avec politique identique : tout ce qui peut être décodé doit l'être, et tout ce qui peut être matérialisé de manière fiable doit l'être.
|
||||||
|
|
||||||
## État final validé `0.7.53` — `pump_swap`
|
## État final validé `0.7.53` — `pump_swap`
|
||||||
|
|
||||||
La tranche `0.7.53 pump_swap` est clôturée côté décodage transaction/log et matérialisation métier. Elle ferme le program id unique `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA` sans rouvrir Raydium.
|
La tranche `0.7.53 pump_swap` est clôturée côté décodage transaction/log et matérialisation métier. Elle ferme le program id unique `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA` sans rouvrir Raydium.
|
||||||
@@ -58,7 +126,7 @@ Livrables `0.7.53` :
|
|||||||
- `validation_sql/SQL_VALIDATION_DEX_COVERAGE_GLOBAL_0_7_53.sql` ;
|
- `validation_sql/SQL_VALIDATION_DEX_COVERAGE_GLOBAL_0_7_53.sql` ;
|
||||||
- `idls/` comme corpus local d'IDL Solscan à comparer aux sources Git.
|
- `idls/` comme corpus local d'IDL Solscan à comparer aux sources Git.
|
||||||
|
|
||||||
La suite immédiate est `pump_fees` / `pump_fun` selon priorité de backlog observé. Les petits gaps Meteora sont volontairement reportés aux tranches Meteora futures.
|
La suite immédiate après `0.7.54 pump_fun` est `pump_fees` (`0.7.55`). Les petits gaps Meteora restent volontairement reportés aux tranches Meteora futures.
|
||||||
|
|
||||||
|
|
||||||
## État final validé `0.7.51` — `raydium_amm_v4`
|
## État final validé `0.7.51` — `raydium_amm_v4`
|
||||||
@@ -487,14 +555,14 @@ Si une requête DB est ajoutée ou modifiée, mettre à jour les re-exports dans
|
|||||||
|
|
||||||
## 8. Priorité immédiate
|
## 8. Priorité immédiate
|
||||||
|
|
||||||
La priorité immédiate après la clôture `0.7.53` est la suivante :
|
La priorité immédiate après la clôture `0.7.54 pump_fun` est la suivante :
|
||||||
|
|
||||||
1. `0.7.53` est clos pour `pump_swap` : ne rouvrir que pour correction de bug, pas pour ajout fonctionnel IDL déjà couvert ;
|
1. ouvrir `0.7.55 pump_fees` sur une base SQLite neuve ;
|
||||||
2. maintenir les checks globaux de surveillance dans `validation_sql/SQL_VALIDATION_DEX_COVERAGE_GLOBAL_0_7_53.sql` après chaque gros backfill ;
|
2. décoder toutes les instructions et tous les events connus de `idls/pump_fees.pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ.json` ;
|
||||||
3. traiter ensuite le backlog observé, en priorité `pump_fees`, puis `pump_fun`, puis `jupiter_swap` si l’objectif devient l’analyse des routes/agrégateurs ;
|
3. matérialiser tout ce qui est prouvable : fee accounting, fee sharing, social/donation fee PDA, buyback, config/admin, rewards éventuelles ;
|
||||||
4. reporter volontairement les corrections Meteora restantes (`meteora_dlmm.swap`, `meteora_damm_v2.swap`, `meteora_damm_v2.instruction_audit`) aux tranches Meteora dédiées ;
|
4. ne créer aucun trade/candle direct pour `pump_fees` sauf preuve transactionnelle forte d'un swap économique autonome ;
|
||||||
5. ne pas rouvrir `raydium_amm_v4`, `raydium_clmm` ou `raydium_cpmm` tant que les requêtes Raydium normalisées restent vides ;
|
5. maintenir les checks Pump.fun/PumpSwap/Raydium en non-régression ;
|
||||||
6. garder `raydium_launchpad` et `raydium_stable_swap` en surveillance : les entrées non observées restent `upstream_git_mapped_unverified`, pas des régressions.
|
6. reporter Meteora/Jupiter/dFlow aux tranches prévues sauf si une dépendance stricte apparaît pendant l'analyse `pump_fees`.
|
||||||
|
|
||||||
Garde-fous constants :
|
Garde-fous constants :
|
||||||
|
|
||||||
@@ -605,8 +673,8 @@ La suite fonctionnelle reprend par Raydium avant Meteora :
|
|||||||
4. `0.7.51` — `raydium_amm_v4` ;
|
4. `0.7.51` — `raydium_amm_v4` ;
|
||||||
5. `0.7.52` — `raydium_stable_swap` — clôturé ;
|
5. `0.7.52` — `raydium_stable_swap` — clôturé ;
|
||||||
6. `0.7.53` — `pump_swap` — clôturé ;
|
6. `0.7.53` — `pump_swap` — clôturé ;
|
||||||
7. `0.7.54` — `pump_fees` ;
|
7. `0.7.54` — `pump_fun` ;
|
||||||
8. `0.7.55` — `pump_fun` ;
|
8. `0.7.55` — `pump_fees` ;
|
||||||
9. `0.7.56+` — Meteora, routers/agrégateurs, Phoenix/OpenBook, Orca puis les autres DEX/surfaces.
|
9. `0.7.56+` — Meteora, routers/agrégateurs, Phoenix/OpenBook, Orca puis les autres DEX/surfaces.
|
||||||
|
|
||||||
`raydium_pool_v4.json` reste repoussé en audit conditionnel tardif, pas une tranche bloquante.
|
`raydium_pool_v4.json` reste repoussé en audit conditionnel tardif, pas une tranche bloquante.
|
||||||
|
|||||||
43
ROADMAP.md
43
ROADMAP.md
@@ -2,28 +2,29 @@
|
|||||||
|
|
||||||
# khadhroony-bobobot — Roadmap
|
# khadhroony-bobobot — Roadmap
|
||||||
|
|
||||||
## État courant — clôture `0.7.53 pump_swap`
|
## État courant — clôture `0.7.54 pump_fun` et ouverture `0.7.55 pump_fees`
|
||||||
|
|
||||||
`0.7.53` est clos pour `pump_swap`. La version ferme le décodage transaction/log de PumpSwap, la matérialisation `buy`, `sell` et `buy_exact_quote_in` depuis sources exactes, les events Anchor audit-only, les tests synthétiques IDL, et la surveillance SQL globale. Les futures interventions PumpSwap doivent être des corrections de bugs ou des adaptations à un changement externe prouvé, pas l’ajout d’entrées IDL déjà connues.
|
`0.7.54 pump_fun` est clos pour la surface Pump.fun principale. La tranche a transformé l'ouverture documentaire en decoder local maximal : inventaire des `40` instructions et `23` events Anchor de l'IDL locale, coverage local, décodage Anchor/self-CPI/log, tests synthétiques, routes catalogue et matérialisation prudente.
|
||||||
|
|
||||||
Décisions de clôture :
|
Décisions de clôture Pump.fun :
|
||||||
|
|
||||||
- `pump_swap.buy_exact_quote_in` est matérialisé uniquement avec `amountSource=pump_swap_anchor_buy_event`; les rows `instruction_bounds_only` restent decoded-only ;
|
- `pump_fun.buy` et `pump_fun.sell` restent matérialisés directement quand les montants sont fiables ;
|
||||||
- les events Anchor `buy_event`, `sell_event`, `deposit_event`, `withdraw_event`, `create_pool_event`, etc. restent audit-only pour éviter le double-count avec les instructions locales ;
|
- `pump_fun.buy_exact_sol_in` est matérialisé directement, y compris quand un `Program data` tronqué permet d'extraire les montants exacts ;
|
||||||
- `claim_token_incentives_event` est testé et prêt à matérialiser `reward` si un corpus réussi apparaît ; les signatures observées côté instruction étaient failed et ne doivent pas produire de reward ;
|
- `pump_fun.buy_v2`, `pump_fun.sell_v2` et `pump_fun.buy_exact_quote_in_v2` sont decoded/audit/routing, mais ne sont pas matérialisés directement ;
|
||||||
- `sync_user_volume_accumulator_event` reste `implemented_idl_unobserved` : plus de 60/70 signatures supplémentaires ont confirmé l’instruction sans faire apparaître l’event ;
|
- `pump_fun.trade_event` devient la source canonique des montants exécutés pour les v2/exact quand il est corrélé à l'instruction sans ambiguïté ;
|
||||||
- Raydium AMM v4 / CLMM / CPMM ne présentent plus de gap ciblé après normalisation des observations ;
|
- les `trade_event` déjà couverts par un trade direct reçoivent un skip explicite pour empêcher le double-count ;
|
||||||
- les gaps Meteora sont explicitement différés.
|
- les non-trades Pump.fun sont matérialisés seulement vers `launch`, `fee`, `reward`, `admin` ou `lifecycle` quand le contexte est fiable ; sinon ils restent decoded-only/audit-only avec skip reason ;
|
||||||
|
- les validations `Q00`, `Q04`, `Q05`, `Q06`, `Q07`, `Q08`, `Q11` sont propres ; la watchlist globale ne contient plus de `pump_fun`.
|
||||||
|
|
||||||
### Phasage immédiat après `0.7.53`
|
Replay final rapporté : `1679 replayed`, `89 trades`, `10 lifecycle`, `348 candle upserts`, `13905 instructionObservations`, catalogue `52 tokens / 50 pools / 50 pairs`.
|
||||||
|
|
||||||
|
### Phasage immédiat après `0.7.54`
|
||||||
|
|
||||||
| Priorité | Tranche | Surface | Raison |
|
| Priorité | Tranche | Surface | Raison |
|
||||||
|---:|---|---|---|
|
|---:|---|---|---|
|
||||||
| 1 | `0.7.54` | `pump_fees` | Backlog observé dominant (`get_fees` très fréquent) ; aucun trade/candle direct attendu. |
|
| 1 | `0.7.55` | `pump_fees` | Backlog global restant : `get_fees`, `create_fee_sharing_config`, `update_fee_shares`; programme fee associé à Pump. Tout décoder et tout matérialiser si fiable. |
|
||||||
| 2 | `0.7.55` | `pump_fun` | Launch/bonding/migration et creator fees observés en fallback upstream. |
|
| 2 | `0.7.56+` | `meteora_*` | Corriger les gaps locaux Meteora reportés volontairement. |
|
||||||
| 3 | `0.7.56+` | `meteora_*` | Corriger les gaps locaux Meteora reportés volontairement. |
|
| 3 | ultérieur | `jupiter_swap` / agrégateurs | Routes et comptes auxiliaires à traiter sans double-count des DEX effectifs. |
|
||||||
| 4 | ultérieur | `jupiter_swap` / agrégateurs | Routes et comptes auxiliaires à traiter sans double-count des DEX effectifs. |
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 0.7.47-1FE5 — Décision de planification : ne plus viser “tous les events en une session”
|
## 0.7.47-1FE5 — Décision de planification : ne plus viser “tous les events en une session”
|
||||||
@@ -62,8 +63,8 @@ Exceptions : les comptes non-programmes (`platform_config`, token authority, com
|
|||||||
| Version cible | Decoder / surface | Program id | Famille | Objectif de clôture |
|
| Version cible | Decoder / surface | Program id | Famille | Objectif de clôture |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `0.7.53` | `pump_swap` | `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA` | Pump / AMM | **Clos** : `buy/sell/buy_exact_quote_in` matérialisés seulement depuis sources exactes ; events Anchor audit-only ; tests synthétiques IDL ; SQL global. |
|
| `0.7.53` | `pump_swap` | `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA` | Pump / AMM | **Clos** : `buy/sell/buy_exact_quote_in` matérialisés seulement depuis sources exactes ; events Anchor audit-only ; tests synthétiques IDL ; SQL global. |
|
||||||
| `0.7.54` | `pump_fees` | `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` | Pump / fee | Couvrir fee accounting/claim/config observés ; aucun trade/candle direct. |
|
| `0.7.54` | `pump_fun` | `6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P` | Pump / launch-bonding | **Clos** : decoder maximal IDL/local, trades directs `buy/sell/buy_exact_sol_in`, v2/exact via `trade_event`, non-trades matérialisés selon contexte, validations Pump.fun propres. |
|
||||||
| `0.7.55` | `pump_fun` | `6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P` | Pump / launch-bonding | Couvrir create, buy/sell bonding, migration/graduate, config/update ; séparer bonding curve et DEX effectif. |
|
| `0.7.55` | `pump_fees` | `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` | Pump / fee | Couvrir fee accounting/claim/config observés ; aucun trade/candle direct. |
|
||||||
| `0.7.56` | `meteora_dbc` | `dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN` | Meteora / DBC | Compléter launch/bonding, swaps exploitables, migration, fees/admin/config. |
|
| `0.7.56` | `meteora_dbc` | `dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN` | Meteora / DBC | Compléter launch/bonding, swaps exploitables, migration, fees/admin/config. |
|
||||||
| `0.7.57` | `meteora_dlmm` | `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo` | Meteora / DLMM | Parité upstream finale : swaps, bins, positions, liquidity, fees/rewards/admin. |
|
| `0.7.57` | `meteora_dlmm` | `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo` | Meteora / DLMM | Parité upstream finale : swaps, bins, positions, liquidity, fees/rewards/admin. |
|
||||||
| `0.7.58` | `meteora_damm_v1` | `Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB` | Meteora / DAMM v1 | Parité upstream finale : pools, swaps, liquidity, lock, fees/admin. |
|
| `0.7.58` | `meteora_damm_v1` | `Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB` | Meteora / DAMM v1 | Parité upstream finale : pools, swaps, liquidity, lock, fees/admin. |
|
||||||
@@ -1423,8 +1424,8 @@ Les comptes non-programmes ne créent pas de tranche decoder autonome. `SOLSCAN_
|
|||||||
| Version | Decoder / surface | Program id | Objectif |
|
| Version | Decoder / surface | Program id | Objectif |
|
||||||
|---:|---|---|---|
|
|---:|---|---|---|
|
||||||
| `0.7.53` | `pump_swap` | `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA` | Clos : `buy/sell/buy_exact_quote_in` depuis sources exactes, non-trades spécialisés, events Anchor audit-only. |
|
| `0.7.53` | `pump_swap` | `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA` | Clos : `buy/sell/buy_exact_quote_in` depuis sources exactes, non-trades spécialisés, events Anchor audit-only. |
|
||||||
| `0.7.54` | `pump_fees` | `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` | Couvrir fee accounting/claim/config ; aucun trade/candle direct. |
|
| `0.7.54` | `pump_fun` | `6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P` | **Clos** : decoder maximal local, trades directs et `trade_event` canonique, non-trades selon contexte, validations propres. |
|
||||||
| `0.7.55` | `pump_fun` | `6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P` | Couvrir launch/bonding/migration : create, buy/sell bonding, update/config, graduate/migrate. |
|
| `0.7.55` | `pump_fees` | `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` | Couvrir fee accounting/claim/config ; aucun trade/candle direct. |
|
||||||
|
|
||||||
#### Bloc Meteora
|
#### Bloc Meteora
|
||||||
|
|
||||||
@@ -1738,8 +1739,8 @@ Ordre de travail recommandé pour la suite :
|
|||||||
9. `0.7.51` : `raydium_amm_v4` — clos ;
|
9. `0.7.51` : `raydium_amm_v4` — clos ;
|
||||||
10. `0.7.52` : `raydium_stable_swap` — clos ;
|
10. `0.7.52` : `raydium_stable_swap` — clos ;
|
||||||
11. `0.7.53` : `pump_swap` / `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA` — clos ;
|
11. `0.7.53` : `pump_swap` / `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA` — clos ;
|
||||||
12. `0.7.54` : `pump_fees` / `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` ;
|
12. `0.7.54` : `pump_fun` / `6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P` ;
|
||||||
13. `0.7.55` : `pump_fun` / `6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P` ;
|
13. `0.7.55` : `pump_fees` / `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` ;
|
||||||
14. `0.7.56+` : appliquer le phasage strict “une version = un `program_id`” défini en section `6.085`.
|
14. `0.7.56+` : appliquer le phasage strict “une version = un `program_id`” défini en section `6.085`.
|
||||||
|
|
||||||
Garde-fous constants :
|
Garde-fous constants :
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/DB_EVENT_MODEL_REVIEW.md -->
|
||||||
|
|
||||||
# Database Event Model Review — `khadhroony-bobobot` `0.7.47-1FE5`
|
# Database Event Model Review — `khadhroony-bobobot` `0.7.47-1FE5`
|
||||||
|
|
||||||
## Conclusion courte
|
## Conclusion courte
|
||||||
|
|||||||
@@ -1,4 +1,22 @@
|
|||||||
# DEX Decoder Matrix — `khadhroony-bobobot` `0.7.53 final`
|
<!-- file: docs/DEX_DECODER_MATRIX.md -->
|
||||||
|
|
||||||
|
# DEX Decoder Matrix — `khadhroony-bobobot` `0.7.54 pump_fun closed`
|
||||||
|
|
||||||
|
|
||||||
|
## Note `0.7.54 closed` — Pump.fun clos, Pump Fees ensuite
|
||||||
|
|
||||||
|
La tranche `0.7.54` ferme `pump_fun` avant `pump_fees`. La surface Pump.fun principale est couverte depuis le code local, l'IDL Solscan locale `idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json`, le registre upstream et le corpus SQLite.
|
||||||
|
|
||||||
|
Décisions structurantes :
|
||||||
|
|
||||||
|
- `pump_fun` est `supported / closed` côté decoder et validation locale ;
|
||||||
|
- toutes les instructions/events connus de l'IDL locale sont inventoriés ;
|
||||||
|
- `buy`, `sell`, `buy_exact_sol_in` peuvent être matérialisés directement avec montants fiables ;
|
||||||
|
- `buy_v2`, `sell_v2`, `buy_exact_quote_in_v2` restent decoded/audit/routing et s'appuient sur `pump_fun.trade_event` pour la matérialisation canonique ;
|
||||||
|
- `pump_fun.trade_event` matérialise les v2/exact quand les montants exécutés et la corrélation instruction sont prouvés ;
|
||||||
|
- les non-trades Pump.fun alimentent uniquement les tables business adaptées ou restent audit-only avec skip reason.
|
||||||
|
|
||||||
|
La prochaine tranche est `0.7.55 pump_fees`.
|
||||||
|
|
||||||
## Note `0.7.53 final` — PumpSwap clôturé et sources IDL locales
|
## Note `0.7.53 final` — PumpSwap clôturé et sources IDL locales
|
||||||
|
|
||||||
@@ -41,7 +59,7 @@ Cette matrice complète `kb_lib/src/dex_support_matrix.rs`. Elle documente **ce
|
|||||||
| 5 | `raydium_stable_swap` | `supported / 0.7.52 closed` | Decoder legacy 1 octet, surface `00..0d`, swaps matérialisés depuis deltas vault exacts. | Surveiller seulement de nouveaux discriminants ou `swap_event` observé. |
|
| 5 | `raydium_stable_swap` | `supported / 0.7.52 closed` | Decoder legacy 1 octet, surface `00..0d`, swaps matérialisés depuis deltas vault exacts. | Surveiller seulement de nouveaux discriminants ou `swap_event` observé. |
|
||||||
| 6 | `raydium_pool_v4` | `to_verify / late-phase conditional audit` | IDL annexe mentionnée par fnzero, non présente dans l'archive locale, pas de program id/rôle confirmé ici. | Ne pas promouvoir tant que program id distinct, rôle exact et corpus exploitable ne sont pas confirmés. |
|
| 6 | `raydium_pool_v4` | `to_verify / late-phase 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.53 closed` | `buy`, `sell` + `buy_exact_quote_in` matérialisable via `BuyEvent` exact ; instructions non-trade spécialisées : liquidity, fee/creator fee, admin/config, cashback/token incentives, volume accumulator ; events Anchor autonomes audit-only. | Trades/candles uniquement depuis montants exacts ; failed tx decoded-only ; `instruction_bounds_only` reste decoded-only ; tests synthétiques IDL et SQL global ajoutés. |
|
| 7 | `pump_swap` | `supported / 0.7.53 closed` | `buy`, `sell` + `buy_exact_quote_in` matérialisable via `BuyEvent` exact ; instructions non-trade spécialisées : liquidity, fee/creator fee, admin/config, cashback/token incentives, volume accumulator ; events Anchor autonomes audit-only. | Trades/candles uniquement depuis montants exacts ; failed tx decoded-only ; `instruction_bounds_only` reste decoded-only ; tests synthétiques IDL et SQL global ajoutés. |
|
||||||
| 8 | `pump_fun` | `partial / 0.7.55 launch_surface` | Création/token launch partiellement décodée ; intégrée au pipeline de listings. | Traiter tous les events Pump.fun disponibles : buy/sell/migrate/create/update ; séparer bonding/launch de DEX effectif ; valider migration vers PumpSwap. |
|
| 8 | `pump_fun` | `supported / 0.7.54 closed` | Surface launch/bonding/migration Pump.fun couverte localement ; trades directs et `trade_event` canonique validés. | Ne rouvrir que pour bug prouvé ou changement externe ; `pump_fees` suit en `0.7.55`. |
|
||||||
| 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é. |
|
| 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. |
|
| 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. |
|
| 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. |
|
||||||
@@ -97,7 +115,7 @@ Un event peut devenir `materialized` uniquement si :
|
|||||||
|
|
||||||
| Code | Rôle | Surface | Program id status | Observed | Decoded | Materialized | Status | Skip reason |
|
| Code | Rôle | Surface | Program id status | Observed | Decoded | Materialized | Status | Skip reason |
|
||||||
|---|---|---|---|---:|---:|---:|---|---|
|
|---|---|---|---|---:|---:|---:|---|---|
|
||||||
| `pump_fun` | `launch_surface` | `launch` | `known` | non | oui | oui | `partial` | launch_surface_requires_migration_linking_before_live_trading |
|
| `pump_fun` | `launch_surface` | `launch/bonding` | `known` | oui | oui | oui | `0.7.54_closed` | Decoder maximal IDL/local ; v2/exact matérialisés via `trade_event` canonique ; non-trades selon contexte. |
|
||||||
| `pump_swap` | `dex_effective` | `AMM` | `known` | oui | oui | oui | `supported` | |
|
| `pump_swap` | `dex_effective` | `AMM` | `known` | oui | oui | oui | `supported` | |
|
||||||
| `raydium_cpmm` | `dex_effective` | `AMM` | `known` | oui | oui | oui | `supported` | |
|
| `raydium_cpmm` | `dex_effective` | `AMM` | `known` | oui | oui | oui | `supported` | |
|
||||||
| `raydium_clmm` | `dex_effective` | `CLMM` | `known` | oui | oui | oui | `supported` | |
|
| `raydium_clmm` | `dex_effective` | `CLMM` | `known` | oui | oui | oui | `supported` | |
|
||||||
@@ -279,6 +297,13 @@ La tranche a été validée sur base SQLite dédiée : tous les discriminants `0
|
|||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `raydium_stable_swap` | `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h` | supported / closed | legacy 1 octet | Surface locale `00..0d` couverte ; swaps `swap_base_in/out` matérialisés uniquement depuis deltas vault exacts ; instruction bounds et failed tx restent decoded-only. |
|
| `raydium_stable_swap` | `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h` | supported / closed | legacy 1 octet | Surface locale `00..0d` couverte ; swaps `swap_base_in/out` matérialisés uniquement depuis deltas vault exacts ; instruction bounds et failed tx restent decoded-only. |
|
||||||
|
|
||||||
|
|
||||||
|
## 0.7.54 — Pump.fun
|
||||||
|
|
||||||
|
| Decoder | Program id | Statut | Source discriminants | Couverture locale initiale | Règles métier |
|
||||||
|
|---|---|---:|---|---|---|
|
||||||
|
| `pump_fun` | `6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P` | supported / 0.7.54 closed | upstream registry + `idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json` + corpus SQLite validé | `40` instructions et `23` events Anchor connus couverts ; `buy/sell/buy_exact_sol_in` matérialisés ; `buy_v2/sell_v2/buy_exact_quote_in_v2` audit/routing ; `trade_event` matérialise les montants exécutés v2/exact | `k_sol_trade_events` uniquement avec montants exacts ; `create/migrate` vers `k_sol_launch_events` ; creator fees vers `k_sol_fee_events` ; cashback/incentives vers `k_sol_reward_events` ; admin/config vers `k_sol_pool_admin_events` ; decoded-only/audit-only avec skip reason sinon |
|
||||||
|
|
||||||
## 0.7.53 — PumpSwap
|
## 0.7.53 — PumpSwap
|
||||||
|
|
||||||
| Decoder | Program id | Statut | Source discriminants | Couverture locale | Règles métier |
|
| Decoder | Program id | Statut | Source discriminants | Couverture locale | Règles métier |
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
# DEX Event Coverage Matrix — `khadhroony-bobobot` `0.7.53 final`
|
<!-- file: docs/DEX_EVENT_COVERAGE_MATRIX.md -->
|
||||||
|
|
||||||
|
# DEX Event Coverage Matrix — `khadhroony-bobobot` `0.7.54 pump_fun closed`
|
||||||
|
|
||||||
Cette matrice complète `docs/DEX_DECODER_MATRIX.md` avec une lecture par familles d'événements. Elle ne remplace pas la preuve locale : une entrée Git/IDL reste un indice tant qu'elle n'est pas observée dans le corpus local puis validée par replay et SQL.
|
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.
|
||||||
|
|
||||||
@@ -214,6 +216,37 @@ Status: **closed on local corpus**.
|
|||||||
|
|
||||||
Stable Swap swaps are not materialized from instruction min/max bounds. `swap_base_in/out` require `amountSource=stable_swap_vault_balance_delta`; `stable_swap_instruction_bounds_only` remains decoded-only and, in the final corpus, appears only on failed transactions.
|
Stable Swap swaps are not materialized from instruction min/max bounds. `swap_base_in/out` require `amountSource=stable_swap_vault_balance_delta`; `stable_swap_instruction_bounds_only` remains decoded-only and, in the final corpus, appears only on failed transactions.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.7.54 — `pump_fun` closed
|
||||||
|
|
||||||
|
Program id unique : `6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P`.
|
||||||
|
|
||||||
|
Source locale prioritaire : `idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json`.
|
||||||
|
|
||||||
|
Replay final rapporté : `1679 replayed`, `89 trades`, `10 lifecycle`, `348 candle upserts`, catalogue `52 tokens / 50 pools / 50 pairs`.
|
||||||
|
|
||||||
|
| Entry / groupe | Discriminator | Family | Expected DB target | Local event kind cible | Status final |
|
||||||
|
|---|---:|---|---|---|---|
|
||||||
|
| `create` / `create_v2` / `create_event` | `181ec828051c0777` / `d6904cec5f8b31b4` / `1b72a94ddeeb6376` | `launch` | `k_sol_launch_events` ou decoded-only | `pump_fun.create*` | couvert ; launch matérialisé quand mint/bonding/creator sont fiables |
|
||||||
|
| `migrate` / `migrate_v2` / migration events | `9beae792ec9ea21e` / `bbcb121fceedfe29` / voir IDL | `migration` | `k_sol_launch_events` ou decoded-only | `pump_fun.migrate*` | couvert ; migration matérialisée quand contexte fiable |
|
||||||
|
| `buy` / `sell` | `66063d1201daebea` / `33e685a4017f83ad` | `swap` | `k_sol_trade_events` | `pump_fun.buy` / `pump_fun.sell` | matérialisés directement avec montants fiables : `17` buy, `25` sell |
|
||||||
|
| `buy_exact_sol_in` | `38fc74089edfcd5f` | `swap` | `k_sol_trade_events` | `pump_fun.buy_exact_sol_in` | matérialisé directement ; `15` trades, y compris logs `Program data` tronqués exploitables |
|
||||||
|
| `buy_v2` / `sell_v2` / `buy_exact_quote_in_v2` | `b817ee6167c5d33d` / `5df6823ce7e940b2` / `c2ab1c46684d5b2f` | `swap` | audit/routing + `trade_event` canonique | `pump_fun.*_v2` | decoded/covered, non matérialisés directement ; les montants exécutés sont matérialisés via `pump_fun.trade_event` |
|
||||||
|
| `trade_event` | `bddb7fd34ee661ee` | `swap` | `k_sol_trade_events` | `pump_fun.trade_event` | `72` decoded / `25` trades ; source canonique des v2/exact quand corrélée ; skip explicite si couvert par trade direct |
|
||||||
|
| `collect_creator_fee*` / `distribute_creator_fees*` | voir IDL | `fee` | `k_sol_fee_events` ou decoded-only | `pump_fun.collect_creator_fee*`, `pump_fun.distribute_creator_fees*` | couvert ; matérialisation fee seulement avec montant/acteur fiables |
|
||||||
|
| `claim_cashback*` / `claim_token_incentives` / volume accumulators | voir IDL | `reward` | `k_sol_reward_events` ou decoded-only | `pump_fun.claim_*`, volume accumulator events | couvert ; rewards matérialisées seulement si preuve suffisante |
|
||||||
|
| creator/admin/config group | voir IDL | `admin_config` | `k_sol_pool_admin_events` ou decoded-only | `pump_fun.admin_*`, `pump_fun.set_*`, `pump_fun.toggle_*`, quote/buyback/reserve entries | couvert ; promotion seulement si action/comptes exploitables |
|
||||||
|
|
||||||
|
### Invariants de fermeture `0.7.54`
|
||||||
|
|
||||||
|
- Aucun `pump_fun` local decoded event sans coverage.
|
||||||
|
- Aucun fallback `upstream_git` résiduel pour les entrées Pump.fun couvertes localement.
|
||||||
|
- Aucun business event matérialisé depuis transaction failed.
|
||||||
|
- Aucun non-swap Pump.fun matérialisé en trade/candle.
|
||||||
|
- Aucun double-count entre instruction trade directe et `trade_event` Anchor.
|
||||||
|
- Aucun trade candidate Pump.fun réussi sans matérialisation ni skip reason.
|
||||||
|
- Les entrées IDL-only sont couvertes localement ; les non observées restent `mapped_unverified` ou audit-only, pas des gaps bloquants.
|
||||||
|
|
||||||
## 0.7.53 — `pump_swap`
|
## 0.7.53 — `pump_swap`
|
||||||
|
|
||||||
Program id unique : `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA`.
|
Program id unique : `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA`.
|
||||||
@@ -247,5 +280,5 @@ Program id unique : `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA`.
|
|||||||
- `pump_swap` ne présente plus de decoded event local sans coverage dans le corpus de clôture.
|
- `pump_swap` ne présente plus de decoded event local sans coverage dans le corpus de clôture.
|
||||||
- `buy_exact_quote_in` est matérialisé seulement quand le `BuyEvent` Anchor donne les montants exacts ; les bornes d’instruction seules restent non actionnables.
|
- `buy_exact_quote_in` est matérialisé seulement quand le `BuyEvent` Anchor donne les montants exacts ; les bornes d’instruction seules restent non actionnables.
|
||||||
- Les events Anchor `*_event` sont décodés en audit-only pour éviter les doublons, sauf exception matérialisable explicitement testée.
|
- Les events Anchor `*_event` sont décodés en audit-only pour éviter les doublons, sauf exception matérialisable explicitement testée.
|
||||||
- Les gaps globaux restants sont classés comme backlog upstream (`pump_fees`, `pump_fun`, `jupiter_swap`, agrégateurs), gaps Meteora reportés, ou observations non attribuées.
|
- Les gaps globaux restants sont classés comme backlog upstream (`pump_fun`, puis `pump_fees`, `jupiter_swap`, agrégateurs), gaps Meteora reportés, ou observations non attribuées.
|
||||||
- Les checks Raydium AMM v4 / CLMM / CPMM normalisés sont vides ; aucune correction Raydium n’est incluse dans cette clôture.
|
- Les checks Raydium AMM v4 / CLMM / CPMM normalisés sont vides ; aucune correction Raydium n’est incluse dans cette clôture.
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/RAYDIUM_LAUNCHPAD_EVENT_COVERAGE_REPORT.md -->
|
||||||
|
|
||||||
# Raydium Launchpad event coverage report — `0.7.50`
|
# Raydium Launchpad event coverage report — `0.7.50`
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/SOLSCAN_ACCOUNT_SOURCE_MATRIX.md -->
|
||||||
|
|
||||||
# Solscan account source matrix
|
# 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.
|
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.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- file: VALIDATION_STATUS_0_7_51.md -->
|
<!-- file: docs/VALIDATION_STATUS_0_7_51.md -->
|
||||||
|
|
||||||
# Validation status — `0.7.51 raydium_amm_v4`
|
# Validation status — `0.7.51 raydium_amm_v4`
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# file: VALIDATION_STATUS_0_7_51_MAX_DECODER.md
|
<!-- file: docs/VALIDATION_STATUS_0_7_51_MAX_DECODER.md -->
|
||||||
|
|
||||||
# Validation status — `0.7.51 raydium_amm_v4 max-decoder`
|
# Validation status — `0.7.51 raydium_amm_v4 max-decoder`
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/VALIDATION_STATUS_0_7_52_FINAL.md -->
|
||||||
|
|
||||||
# Validation status — 0.7.52 Raydium Stable Swap final
|
# Validation status — 0.7.52 Raydium Stable Swap final
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/prompts/NEXT_SESSION_PROMPT_0.7.47_1FE5_CONTINUATION_V2.md -->
|
||||||
|
|
||||||
# Prompt de reprise — khadhroony-bobobot `0.7.47-1FE5`
|
# Prompt de reprise — khadhroony-bobobot `0.7.47-1FE5`
|
||||||
|
|
||||||
Reprise du projet `khadhroony-bobobot`.
|
Reprise du projet `khadhroony-bobobot`.
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/prompts/NEXT_SESSION_PROMPT_0.7.47_EVENT_COVERAGE_V3.md -->
|
||||||
|
|
||||||
# Prompt de reprise — khadhroony-bobobot `0.7.47-1FE5` / Event coverage
|
# Prompt de reprise — khadhroony-bobobot `0.7.47-1FE5` / Event coverage
|
||||||
|
|
||||||
Reprise du projet `khadhroony-bobobot`.
|
Reprise du projet `khadhroony-bobobot`.
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/prompts/NEXT_SESSION_PROMPT_0.7.47_UPSTREAM_REGISTRY.md -->
|
||||||
|
|
||||||
# Prompt de reprise — khadhroony-bobobot `0.7.47`
|
# Prompt de reprise — khadhroony-bobobot `0.7.47`
|
||||||
|
|
||||||
Reprise du projet `khadhroony-bobobot`.
|
Reprise du projet `khadhroony-bobobot`.
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/prompts/NEXT_SESSION_PROMPT_0.7.49_RAYDIUM_CLMM.md -->
|
||||||
|
|
||||||
# Prompt de reprise — khadhroony-bobobot `0.7.49` / Raydium CLMM event coverage
|
# Prompt de reprise — khadhroony-bobobot `0.7.49` / Raydium CLMM event coverage
|
||||||
|
|
||||||
Reprise du projet `khadhroony-bobobot` après clôture fonctionnelle de `0.7.48 raydium_cpmm`.
|
Reprise du projet `khadhroony-bobobot` après clôture fonctionnelle de `0.7.48 raydium_cpmm`.
|
||||||
|
|||||||
@@ -269,4 +269,4 @@ Commencer par analyser l’archive fournie :
|
|||||||
- fichiers à modifier ;
|
- fichiers à modifier ;
|
||||||
- hypothèse de classification par entry ;
|
- hypothèse de classification par entry ;
|
||||||
- SQL initial de backfill/validation ;
|
- SQL initial de backfill/validation ;
|
||||||
4. proposer puis produire le premier delta minimal.
|
4. proposer puis produire le premier delta archive minimal.
|
||||||
|
|||||||
347
docs/prompts/PROMPT_0_7_55_PUMP_FEES.md
Normal file
347
docs/prompts/PROMPT_0_7_55_PUMP_FEES.md
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
<!-- file: docs/prompts/PROMPT_0_7_55_PUMP_FEES.md -->
|
||||||
|
|
||||||
|
# Prompt de reprise — khadhroony-bobobot 0.7.55 — pump_fees
|
||||||
|
|
||||||
|
Tu reprends le workspace Rust/Tauri `khadhroony-bobobot` après clôture technique de `0.7.54 pump_fun`.
|
||||||
|
|
||||||
|
## 1. Archive et fichiers à fournir
|
||||||
|
|
||||||
|
Utiliser l'archive la plus récente après clôture `0.7.54 pump_fun`.
|
||||||
|
|
||||||
|
À considérer comme sources locales de savoir :
|
||||||
|
|
||||||
|
- code Rust du workspace ;
|
||||||
|
- `README.md`, `ROADMAP.md`, `CHANGELOG.md` ;
|
||||||
|
- `docs/DEX_DECODER_MATRIX.md` ;
|
||||||
|
- `docs/DEX_EVENT_COVERAGE_MATRIX.md` ;
|
||||||
|
- `docs/reports/PUMP_FUN_EVENT_COVERAGE_REPORT.md` ;
|
||||||
|
- `validation_sql/SQL_VALIDATION_PUMP_FUN_0_7_54.sql` ;
|
||||||
|
- `validation_sql/SQL_VALIDATION_PUMP_FUN_MATERIALIZATION_0_7_54.sql` ;
|
||||||
|
- `idls/**`, en particulier `idls/pump_fees.pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ.json` ;
|
||||||
|
- les logs/requêtes SQL collés pendant la session.
|
||||||
|
|
||||||
|
Ne pas supposer que la documentation est parfaite : vérifier contre le code, l'IDL locale et le corpus SQLite.
|
||||||
|
|
||||||
|
## 2. État validé avant cette version
|
||||||
|
|
||||||
|
`0.7.54 pump_fun` est clos.
|
||||||
|
|
||||||
|
Replay final rapporté :
|
||||||
|
|
||||||
|
```text
|
||||||
|
1679 replayed
|
||||||
|
0 decode skipped
|
||||||
|
1679 ledger upserts
|
||||||
|
145 unsafe ledger rows
|
||||||
|
89 trades
|
||||||
|
0 liquidity
|
||||||
|
10 lifecycle
|
||||||
|
0 tokenAccount
|
||||||
|
348 candle upserts
|
||||||
|
instructionObservations = 13905
|
||||||
|
resetDeleted = 1112
|
||||||
|
catalog = 52 tokens / 50 pools / 50 pairs
|
||||||
|
```
|
||||||
|
|
||||||
|
Checks de fermeture Pump.fun :
|
||||||
|
|
||||||
|
- upstream fallback Pump.fun : vide ;
|
||||||
|
- decoded Pump.fun sans coverage : vide ;
|
||||||
|
- successful non-materialized sans skip reason : vide ;
|
||||||
|
- failed transaction materialization safety : vide ;
|
||||||
|
- multi-target materialization safety : vide ;
|
||||||
|
- trade candidates Pump.fun sans matérialisation ni skip : vide ;
|
||||||
|
- watchlist globale : plus aucun `pump_fun`.
|
||||||
|
|
||||||
|
Décisions Pump.fun à préserver :
|
||||||
|
|
||||||
|
- `buy`, `sell`, `buy_exact_sol_in` sont matérialisés directement quand les montants sont fiables ;
|
||||||
|
- `buy_v2`, `sell_v2`, `buy_exact_quote_in_v2` ne sont pas matérialisés directement ;
|
||||||
|
- `trade_event` est la source canonique des montants exécutés v2/exact ;
|
||||||
|
- aucun double-count entre instruction trade et event Anchor ;
|
||||||
|
- transactions failed audit-only.
|
||||||
|
|
||||||
|
Ne pas rouvrir `pump_fun`, `pump_swap` ou Raydium sauf bug prouvé par SQL/code.
|
||||||
|
|
||||||
|
## 3. Objectif de `0.7.55 pump_fees`
|
||||||
|
|
||||||
|
Ouvrir et clôturer la surface `pump_fees`.
|
||||||
|
|
||||||
|
Program id cible :
|
||||||
|
|
||||||
|
```text
|
||||||
|
pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ
|
||||||
|
```
|
||||||
|
|
||||||
|
IDL locale :
|
||||||
|
|
||||||
|
```text
|
||||||
|
idls/pump_fees.pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ.json
|
||||||
|
```
|
||||||
|
|
||||||
|
exemple de code:
|
||||||
|
https://github.com/sevenlabs-hq/carbon/tree/main/decoders/pump-fees-decoder
|
||||||
|
|
||||||
|
L'IDL locale contient au minimum :
|
||||||
|
|
||||||
|
- `29` instructions ;
|
||||||
|
- `20` events ;
|
||||||
|
- `9` accounts ;
|
||||||
|
- `34` types.
|
||||||
|
|
||||||
|
Règle forte :
|
||||||
|
|
||||||
|
> Tout ce qui peut être décodé doit être décodé. Tout ce qui peut être matérialisé de façon fiable doit être matérialisé. Ce qui ne peut pas être matérialisé doit rester decoded-only/audit-only avec `skip*Reason` explicite.
|
||||||
|
|
||||||
|
Le programme `pump_fees` est a priori un programme de fee/config/accounting. Aucun trade/candle direct n'est attendu sauf preuve transactionnelle très forte d'un swap économique autonome.
|
||||||
|
|
||||||
|
## 4. Backlog initial observé
|
||||||
|
|
||||||
|
La watchlist globale après `0.7.54 pump_fun` montre notamment :
|
||||||
|
|
||||||
|
```text
|
||||||
|
pump_fees get_fees e7257e55cf5b3f34 173 decoded / 171 tx
|
||||||
|
pump_fees create_fee_sharing_config c34e564c6f34fbd5 21 decoded / 21 tx
|
||||||
|
pump_fees update_fee_shares bd0d8863bba4ed23 14 decoded / 14 tx
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces trois entrées doivent être les premières sources de corpus/backfill.
|
||||||
|
|
||||||
|
## 5. Périmètre fonctionnel
|
||||||
|
|
||||||
|
### Inclus
|
||||||
|
|
||||||
|
- décodage de toutes les instructions `pump_fees` connues par l'IDL locale ;
|
||||||
|
- décodage de tous les events Anchor `pump_fees` connus par l'IDL locale ;
|
||||||
|
- décodage Borsh des arguments et payloads quand les layouts sont définis ;
|
||||||
|
- classification coverage par famille : fee, reward, admin/config, buyback, social fee, donation fee, fee sharing, account lifecycle ;
|
||||||
|
- matérialisation vers les tables métier existantes quand les données sont fiables :
|
||||||
|
- `k_sol_fee_events` ;
|
||||||
|
- `k_sol_reward_events` si cashback/social/donation/claim représente une récompense exploitable ;
|
||||||
|
- `k_sol_pool_admin_events` pour config/admin/authority/tier/update ;
|
||||||
|
- `k_sol_pool_lifecycle_events` si création/initialisation de compte/config est pertinente ;
|
||||||
|
- `k_sol_dex_decoded_events_only` pour les vues/calculs/audit-only ;
|
||||||
|
- SQL de validation dédié ;
|
||||||
|
- documentation finale et rapport.
|
||||||
|
|
||||||
|
### Hors périmètre sauf preuve stricte
|
||||||
|
|
||||||
|
- nouveau trade/candle direct ;
|
||||||
|
- réouverture Pump.fun/PumpSwap ;
|
||||||
|
- Raydium/Meteora/Jupiter/dFlow ;
|
||||||
|
- refactor réseau ou UI non nécessaire.
|
||||||
|
|
||||||
|
## 6. Méthode obligatoire : nouvelle base SQLite
|
||||||
|
|
||||||
|
Créer une nouvelle DB dédiée à `0.7.55 pump_fees`.
|
||||||
|
|
||||||
|
Ne pas réutiliser l'ancienne DB de validation Pump.fun sauf pour lire des signatures de départ.
|
||||||
|
|
||||||
|
Après chaque backfill ou patch decoder :
|
||||||
|
|
||||||
|
```text
|
||||||
|
skipDexDecode=no
|
||||||
|
forceDexDecode=yes
|
||||||
|
deferInstructionObservations=yes
|
||||||
|
```
|
||||||
|
|
||||||
|
Puis :
|
||||||
|
|
||||||
|
- refresh catalog ;
|
||||||
|
- replay local ;
|
||||||
|
- relancer SQL de validation ;
|
||||||
|
- noter les compteurs replay.
|
||||||
|
|
||||||
|
## 7. Corpus et backfills
|
||||||
|
|
||||||
|
Construire le corpus local à partir de :
|
||||||
|
|
||||||
|
1. signatures `sample_signature` de la watchlist globale ;
|
||||||
|
2. filtres Solscan.io par program id + instruction/discriminator quand disponibles ;
|
||||||
|
3. Demo3 discovery multi-source/multi-target ;
|
||||||
|
4. batch backfill par groupes de signatures ;
|
||||||
|
5. program/signature backfill ciblé si nécessaire ;
|
||||||
|
6. signatures issues des requêtes SQL `instruction_observations`, fallback upstream et decoded-only résiduels.
|
||||||
|
|
||||||
|
Démarrer par :
|
||||||
|
|
||||||
|
- `get_fees` / `e7257e55cf5b3f34` ;
|
||||||
|
- `create_fee_sharing_config` / `c34e564c6f34fbd5` ;
|
||||||
|
- `update_fee_shares` / `bd0d8863bba4ed23`.
|
||||||
|
|
||||||
|
Ensuite couvrir les autres instructions/events de l'IDL locale, même non observés, par tests synthétiques lorsque le layout est connu.
|
||||||
|
|
||||||
|
## 8. Instructions IDL locales à inventorier
|
||||||
|
|
||||||
|
Inventorier et classifier au minimum :
|
||||||
|
|
||||||
|
- `claim_social_fee_pda` ;
|
||||||
|
- `claim_social_fee_pda_v2` ;
|
||||||
|
- `crank_donation_fee_pda` ;
|
||||||
|
- `create_donation_fee_pda` ;
|
||||||
|
- `create_fee_sharing_config` ;
|
||||||
|
- `create_social_fee_pda` ;
|
||||||
|
- `extend_fee_config` ;
|
||||||
|
- `get_fees` ;
|
||||||
|
- `initialize_buyback` ;
|
||||||
|
- `initialize_fee_config` ;
|
||||||
|
- `initialize_fee_program_global` ;
|
||||||
|
- `reset_fee_sharing_config` ;
|
||||||
|
- `reset_fee_sharing_config_v2` ;
|
||||||
|
- `revoke_fee_sharing_authority` ;
|
||||||
|
- `set_authority` ;
|
||||||
|
- `set_claim_rate_limit` ;
|
||||||
|
- `set_disable_flags` ;
|
||||||
|
- `set_social_claim_authority` ;
|
||||||
|
- `sweep_buyback` ;
|
||||||
|
- `transfer_fee_sharing_authority` ;
|
||||||
|
- `update_admin` ;
|
||||||
|
- `update_buyback_authority` ;
|
||||||
|
- `update_buyback_claim_rate_limit` ;
|
||||||
|
- `update_fee_config` ;
|
||||||
|
- `update_fee_shares` ;
|
||||||
|
- `update_fee_shares_v2` ;
|
||||||
|
- `update_stable_fee_config` ;
|
||||||
|
- `upsert_fee_tiers` ;
|
||||||
|
- `upsert_stable_fee_tiers`.
|
||||||
|
|
||||||
|
Events Anchor à inventorier :
|
||||||
|
|
||||||
|
- `CreateFeeSharingConfigEvent` ;
|
||||||
|
- `DonationFeePdaCranked` ;
|
||||||
|
- `DonationFeePdaCreated` ;
|
||||||
|
- `ExtendFeeConfigEvent` ;
|
||||||
|
- `InitializeFeeConfigEvent` ;
|
||||||
|
- `InitializeFeeProgramGlobalEvent` ;
|
||||||
|
- `ResetFeeSharingConfigEvent` ;
|
||||||
|
- `SetAuthorityEvent` ;
|
||||||
|
- `SetClaimRateLimitEvent` ;
|
||||||
|
- `SetDisableFlagsEvent` ;
|
||||||
|
- `SetSocialClaimAuthorityEvent` ;
|
||||||
|
- `SocialFeePdaClaimed` ;
|
||||||
|
- `SocialFeePdaCreated` ;
|
||||||
|
- `SweepBuybackEvent` ;
|
||||||
|
- `UpdateAdminEvent` ;
|
||||||
|
- `UpdateFeeConfigEvent` ;
|
||||||
|
- `UpdateFeeSharesEvent` ;
|
||||||
|
- `UpdateStableFeeConfigEvent` ;
|
||||||
|
- `UpsertFeeTiersEvent` ;
|
||||||
|
- `UpsertStableFeeTiersEvent`.
|
||||||
|
|
||||||
|
## 9. Contraintes de code Rust
|
||||||
|
|
||||||
|
Respecter strictement les conventions du projet :
|
||||||
|
|
||||||
|
- Rust 2024 ;
|
||||||
|
- pas de `?` ;
|
||||||
|
- pas de `unwrap()` / `expect()` en code applicatif ;
|
||||||
|
- pas de `anyhow` / `thiserror` ;
|
||||||
|
- `match` / `if let Err` explicites ;
|
||||||
|
- async-first ;
|
||||||
|
- `tracing` obligatoire ;
|
||||||
|
- pas de `mod.rs` ;
|
||||||
|
- pas de `pub mod` ; utiliser `mod` + `pub use` ;
|
||||||
|
- imports limités, types appelés de façon qualifiée quand c'est la convention locale ;
|
||||||
|
- tests offline ;
|
||||||
|
- ne pas casser `#![deny(unreachable_pub)]` et `#![warn(missing_docs)]`.
|
||||||
|
|
||||||
|
Si des requêtes DB sont ajoutées ou déplacées, penser aux re-exports :
|
||||||
|
|
||||||
|
- `kb_lib/src/db.rs` ;
|
||||||
|
- `kb_lib/src/lib.rs`.
|
||||||
|
|
||||||
|
## 10. Matérialisation attendue
|
||||||
|
|
||||||
|
Ne pas se contenter de decoded-only si une matérialisation fiable est possible.
|
||||||
|
|
||||||
|
Classification cible :
|
||||||
|
|
||||||
|
- `get_fees` : probablement decoded-only ou fee calculation audit ; ne pas matérialiser comme fee payé sans transfert/montant réalisé ;
|
||||||
|
- fee sharing config : `k_sol_pool_admin_events` ou lifecycle/config si comptes exploitables ;
|
||||||
|
- social/donation fee PDA create/claim/crank : `k_sol_fee_events`, `k_sol_reward_events` ou admin/lifecycle selon le sens exact des flux ;
|
||||||
|
- buyback init/sweep/update : fee/admin/buyback selon comptes et montants ;
|
||||||
|
- authority/config/tier updates : `k_sol_pool_admin_events` ;
|
||||||
|
- Anchor events : matérialiser s'ils portent le montant/acteur/compte fiable ; sinon audit-only avec skip reason ;
|
||||||
|
- transactions failed : decoded-only/audit-only, jamais business matérialisé.
|
||||||
|
|
||||||
|
Aucun `pump_fees` ne doit créer de `k_sol_trade_events` ni de candle sauf preuve irréfutable d'un trade économique autonome et non doublonné.
|
||||||
|
|
||||||
|
## 11. SQL de validation attendu
|
||||||
|
|
||||||
|
Créer :
|
||||||
|
|
||||||
|
```text
|
||||||
|
validation_sql/SQL_VALIDATION_PUMP_FEES_0_7_55.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
Requêtes minimales :
|
||||||
|
|
||||||
|
1. upstream fallback samples `pump_fees` ;
|
||||||
|
2. local instruction observations `pump_fees` ;
|
||||||
|
3. coverage `pump_fees` ;
|
||||||
|
4. decoded events `pump_fees` sans coverage ;
|
||||||
|
5. residual upstream fallback pour entrées couvertes ;
|
||||||
|
6. successful non-materialized sans skip reason ;
|
||||||
|
7. failed transaction materialization safety ;
|
||||||
|
8. multi-target materialization safety ;
|
||||||
|
9. materialization summary par table ;
|
||||||
|
10. instruction observation versus coverage ;
|
||||||
|
11. contrôle anti-trade/candle direct `pump_fees` ;
|
||||||
|
12. global watchlist après replay.
|
||||||
|
|
||||||
|
## 12. Invariants de fermeture
|
||||||
|
|
||||||
|
La tranche `0.7.55` ne doit être considérée close que si :
|
||||||
|
|
||||||
|
- aucun fallback `upstream_git` `pump_fees` ne reste pour les entrées couvertes localement ;
|
||||||
|
- aucun decoded event `pump_fees` local sans coverage ;
|
||||||
|
- aucune transaction failed n'alimente une table métier ;
|
||||||
|
- aucun event multi-target incohérent ;
|
||||||
|
- aucune ligne successful non-materialized sans `skip*Reason` ;
|
||||||
|
- aucun trade/candle `pump_fees` artificiel ;
|
||||||
|
- toutes les instructions/events de l'IDL locale sont soit décodés/matérialisés, soit audit-only, soit non observés mais couverts par tests synthétiques ;
|
||||||
|
- la watchlist globale ne contient plus de `pump_fees` comme backlog dominant.
|
||||||
|
|
||||||
|
## 13. Documentation à mettre à jour en fin de tranche
|
||||||
|
|
||||||
|
Mettre à jour :
|
||||||
|
|
||||||
|
- `CHANGELOG.md` ;
|
||||||
|
- `README.md` ;
|
||||||
|
- `ROADMAP.md` ;
|
||||||
|
- `docs/DEX_DECODER_MATRIX.md` ;
|
||||||
|
- `docs/DEX_EVENT_COVERAGE_MATRIX.md` ;
|
||||||
|
- créer `docs/reports/PUMP_FEES_EVENT_COVERAGE_REPORT.md` ;
|
||||||
|
- créer ou mettre à jour le SQL de validation dédié.
|
||||||
|
|
||||||
|
## 14. Format de livraison attendu
|
||||||
|
|
||||||
|
Fournir un delta zip contenant uniquement les fichiers modifiés/ajoutés.
|
||||||
|
|
||||||
|
Nom recommandé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
khadhroony-bobobot-v0.7.55-pump_fees-delta-N-files.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Chaque livraison doit inclure :
|
||||||
|
|
||||||
|
- résumé des changements ;
|
||||||
|
- liste exacte des fichiers modifiés ;
|
||||||
|
- commandes à lancer :
|
||||||
|
- `cargo fmt` ;
|
||||||
|
- `cargo test -p kb_lib` ;
|
||||||
|
- `cargo clippy -p kb_lib --all-targets -- -D warnings` ;
|
||||||
|
- replay recommandé ;
|
||||||
|
- SQL à exécuter ;
|
||||||
|
- résultats attendus.
|
||||||
|
|
||||||
|
## 15. Première tâche demandée
|
||||||
|
|
||||||
|
1. Inspecter le code et l'IDL `pump_fees` locale.
|
||||||
|
2. Comparer `upstream_registry_generated.rs`, `idls/pump_fees...json` et le corpus SQL.
|
||||||
|
3. Créer une base SQLite neuve `0.7.55`.
|
||||||
|
4. Backfiller les signatures `get_fees`, `create_fee_sharing_config`, `update_fee_shares`.
|
||||||
|
5. Ajouter le decoder local maximal `pump_fees` : instructions + events + tests synthétiques.
|
||||||
|
6. Ajouter coverage/materialization/validation SQL.
|
||||||
|
7. Rejouer et fermer seulement si tous les invariants sont propres.
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/prompts/PROMPT_REPRISE_khadhroony-bobobot_0.7.48-raydium-cpmm.md -->
|
||||||
|
|
||||||
# Prompt de reprise — khadhroony-bobobot `0.7.48` / Raydium CPMM event coverage
|
# Prompt de reprise — khadhroony-bobobot `0.7.48` / Raydium CPMM event coverage
|
||||||
|
|
||||||
Reprise du projet `khadhroony-bobobot` après clôture de `0.7.48-pre`.
|
Reprise du projet `khadhroony-bobobot` après clôture de `0.7.48-pre`.
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/reports/DEX_COVERAGE_GLOBAL_WATCHLIST_0_7_53.md -->
|
||||||
|
|
||||||
# DEX coverage global watchlist — `0.7.53`
|
# DEX coverage global watchlist — `0.7.53`
|
||||||
|
|
||||||
## Objet
|
## Objet
|
||||||
|
|||||||
127
docs/reports/PUMP_FUN_EVENT_COVERAGE_REPORT.md
Normal file
127
docs/reports/PUMP_FUN_EVENT_COVERAGE_REPORT.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<!-- file: docs/reports/PUMP_FUN_EVENT_COVERAGE_REPORT.md -->
|
||||||
|
|
||||||
|
# Pump.fun event coverage report — clôture `0.7.54`
|
||||||
|
|
||||||
|
## Statut du rapport
|
||||||
|
|
||||||
|
Ce rapport clôture la tranche `0.7.54 pump_fun` côté coverage, décodage local, matérialisation métier prudente et validation SQL.
|
||||||
|
|
||||||
|
Program id canonique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P
|
||||||
|
```
|
||||||
|
|
||||||
|
Source IDL locale prioritaire :
|
||||||
|
|
||||||
|
```text
|
||||||
|
idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sources utilisées
|
||||||
|
|
||||||
|
- `kb_lib/src/dex/pump_fun.rs` ;
|
||||||
|
- `kb_lib/src/dex_decode.rs` ;
|
||||||
|
- `kb_lib/src/trade_aggregation.rs` ;
|
||||||
|
- `kb_lib/src/trade_amount_resolution.rs` ;
|
||||||
|
- `kb_lib/src/dex_detection_route.rs` ;
|
||||||
|
- `kb_lib/src/dex_event_coverage.rs` ;
|
||||||
|
- `kb_lib/src/upstream_registry_generated.rs` ;
|
||||||
|
- `idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json` ;
|
||||||
|
- `validation_sql/SQL_VALIDATION_PUMP_FUN_0_7_54.sql` ;
|
||||||
|
- `validation_sql/SQL_VALIDATION_PUMP_FUN_MATERIALIZATION_0_7_54.sql` ;
|
||||||
|
- corpus SQLite bâti par backfills Demo3/signatures/pools et replay forcé.
|
||||||
|
|
||||||
|
## Couverture finale
|
||||||
|
|
||||||
|
L'IDL locale Pump.fun contient `40` instructions et `23` events Anchor. La tranche a ajouté la couverture locale des instructions/events connues, y compris les instructions IDL-only absentes du registre upstream initial :
|
||||||
|
|
||||||
|
- `add_quote_mint` ;
|
||||||
|
- `buy_exact_quote_in_v2` ;
|
||||||
|
- `buy_v2` ;
|
||||||
|
- `claim_cashback_v2` ;
|
||||||
|
- `collect_creator_fee_v2` ;
|
||||||
|
- `distribute_creator_fees_v2` ;
|
||||||
|
- `migrate_v2` ;
|
||||||
|
- `remove_quote_mint` ;
|
||||||
|
- `sell_v2` ;
|
||||||
|
- `set_virtual_quote_reserves` ;
|
||||||
|
- `update_buyback_config`.
|
||||||
|
|
||||||
|
Les events Anchor sont reconnus depuis `Program data:` et depuis le transport Anchor self-CPI/log `e445a52e51cb9a1d` quand présent.
|
||||||
|
|
||||||
|
## Règles de matérialisation finales
|
||||||
|
|
||||||
|
### Trades
|
||||||
|
|
||||||
|
| Source locale | Matérialisation | Règle |
|
||||||
|
|---|---|---|
|
||||||
|
| `pump_fun.buy` | `k_sol_trade_events` | directe si montants fiables |
|
||||||
|
| `pump_fun.sell` | `k_sol_trade_events` | directe si montants fiables |
|
||||||
|
| `pump_fun.buy_exact_sol_in` | `k_sol_trade_events` | directe ; les logs `Program data` tronqués sont exploités quand les montants exacts sont extractibles |
|
||||||
|
| `pump_fun.buy_v2` | non directe | instruction audit/coverage/routing uniquement |
|
||||||
|
| `pump_fun.sell_v2` | non directe | instruction audit/coverage/routing uniquement |
|
||||||
|
| `pump_fun.buy_exact_quote_in_v2` | non directe | instruction audit/coverage/routing uniquement |
|
||||||
|
| `pump_fun.trade_event` | `k_sol_trade_events` | source canonique des montants exécutés v2/exact quand corrélée sans ambiguïté |
|
||||||
|
|
||||||
|
Les `trade_event` déjà couverts par une instruction directe reçoivent un skip explicite afin d'éviter tout double-count.
|
||||||
|
|
||||||
|
### Non-trades
|
||||||
|
|
||||||
|
Les événements non-trade sont matérialisés uniquement vers leur table métier ciblée quand les comptes, acteurs et montants sont fiables :
|
||||||
|
|
||||||
|
- `k_sol_launch_events` pour create/migrate/graduate ;
|
||||||
|
- `k_sol_fee_events` pour creator fees, fee distribution et minimum fee ;
|
||||||
|
- `k_sol_reward_events` pour cashback, incentives et volume accumulators exploitables ;
|
||||||
|
- `k_sol_pool_admin_events` pour admin/config/creator/global authority ;
|
||||||
|
- `k_sol_pool_lifecycle_events` pour initialization/lifecycle.
|
||||||
|
|
||||||
|
Sinon, ils restent decoded-only/audit-only avec `skip*Reason` explicite. Les transactions failed ne produisent aucune matérialisation métier.
|
||||||
|
|
||||||
|
## Replay final rapporté
|
||||||
|
|
||||||
|
```text
|
||||||
|
1679 replayed
|
||||||
|
0 decode skipped
|
||||||
|
1679 ledger upserts
|
||||||
|
145 unsafe ledger rows
|
||||||
|
89 trades
|
||||||
|
0 liquidity
|
||||||
|
10 lifecycle
|
||||||
|
0 tokenAccount
|
||||||
|
348 candle upserts
|
||||||
|
instructionObservations = 13905
|
||||||
|
resetDeleted = 1112
|
||||||
|
catalog = 52 tokens / 50 pools / 50 pairs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Matérialisation finale Pump.fun observée
|
||||||
|
|
||||||
|
```text
|
||||||
|
pump_fun.buy 17 trades
|
||||||
|
pump_fun.sell 25 trades
|
||||||
|
pump_fun.buy_exact_sol_in 15 trades
|
||||||
|
pump_fun.trade_event 25 trades
|
||||||
|
```
|
||||||
|
|
||||||
|
Les variantes v2/exact restent à `0` dans `k_sol_trade_events` par `decoded_event_id` d'instruction, ce qui est attendu : leur matérialisation canonique se fait via `pump_fun.trade_event`.
|
||||||
|
|
||||||
|
## Checks de fermeture SQL
|
||||||
|
|
||||||
|
Résultats finaux rapportés :
|
||||||
|
|
||||||
|
- `Q00` upstream fallback Pump.fun : vide ;
|
||||||
|
- `Q04` decoded Pump.fun sans coverage : vide ;
|
||||||
|
- `Q05` fallback upstream couvert localement : vide ;
|
||||||
|
- `Q06` successful non-materialized sans skip reason : vide ;
|
||||||
|
- `Q07` failed transaction materialization safety : vide ;
|
||||||
|
- `Q08` multi-target materialization safety : vide ;
|
||||||
|
- `Q11` trade candidates sans trade ni skip : vide ;
|
||||||
|
- `Q12` watchlist globale : plus de `pump_fun` ; restent `pump_fees`, `jupiter_swap` et `dflow_aggregator_v4`.
|
||||||
|
|
||||||
|
## Décisions de clôture
|
||||||
|
|
||||||
|
- `pump_fun` est clos côté decoder maximal local et validation corpus.
|
||||||
|
- Les prochaines interventions Pump.fun doivent être des corrections de bugs ou des adaptations à un changement externe prouvé.
|
||||||
|
- La suite logique est `0.7.55 pump_fees` sur nouvelle base SQLite.
|
||||||
|
- La politique reste : tout ce qui peut être décodé doit l'être ; tout ce qui peut être matérialisé de manière fiable doit l'être ; aucun trade/candle artificiel ne doit être créé.
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/reports/PUMP_SWAP_EVENT_COVERAGE_REPORT.md -->
|
||||||
|
|
||||||
# PumpSwap event coverage report — `0.7.53`
|
# PumpSwap event coverage report — `0.7.53`
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# file: docs/reports/RAYDIUM_CLMM_UPSTREAM_COVERAGE_REVIEW_PRE19.md
|
<!-- file: docs/reports/RAYDIUM_CLMM_UPSTREAM_COVERAGE_REVIEW_PRE19.md -->
|
||||||
|
|
||||||
# Raydium CLMM upstream coverage review — `0.7.49-pre.19`
|
# Raydium CLMM upstream coverage review — `0.7.49-pre.19`
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/reports/RAYDIUM_CPMM_CLMM_RECHECK_REPORT_0_7_50_PRE_R2.md -->
|
||||||
|
|
||||||
# Raydium CPMM/CLMM re-check report — `0.7.50-pre-r2`
|
# Raydium CPMM/CLMM re-check report — `0.7.50-pre-r2`
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/reports/RAYDIUM_CPMM_EVENT_COVERAGE_REPORT.md -->
|
||||||
|
|
||||||
# Rapport `0.7.48` — Raydium CPMM event coverage
|
# Rapport `0.7.48` — Raydium CPMM event coverage
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/reports/RAYDIUM_CPMM_UPSTREAM_COVERAGE_REVIEW_PRE22.md -->
|
||||||
|
|
||||||
# Raydium CPMM upstream coverage review — 0.7.49-pre.22
|
# Raydium CPMM upstream coverage review — 0.7.49-pre.22
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/reports/RAYDIUM_LAUNCHPAD_EVENT_COVERAGE_REPORT.md -->
|
||||||
|
|
||||||
# Raydium Launchpad event coverage report — `0.7.50`
|
# Raydium Launchpad event coverage report — `0.7.50`
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<!-- file: docs/reports/RAYDIUM_STABLE_SWAP_EVENT_COVERAGE_REPORT.md -->
|
||||||
|
|
||||||
# Raydium Stable Swap event coverage report — 0.7.52 final
|
# Raydium Stable Swap event coverage report — 0.7.52 final
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "kb-demo-app",
|
"name": "kb-demo-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.7.53",
|
"version": "0.7.54",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "kb-demo-app",
|
"productName": "kb-demo-app",
|
||||||
"version": "0.7.53",
|
"version": "0.7.54",
|
||||||
"identifier": "com.sasedev.kb-demo-app",
|
"identifier": "com.sasedev.kb-demo-app",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ pub use phoenix_v1::PhoenixV1AuditDecoded;
|
|||||||
pub use phoenix_v1::PhoenixV1DecodedEvent;
|
pub use phoenix_v1::PhoenixV1DecodedEvent;
|
||||||
pub use phoenix_v1::PhoenixV1Decoder;
|
pub use phoenix_v1::PhoenixV1Decoder;
|
||||||
pub use pump_fun::PumpFunCreateV2TokenDecoded;
|
pub use pump_fun::PumpFunCreateV2TokenDecoded;
|
||||||
|
pub use pump_fun::PumpFunInstructionAuditDecoded;
|
||||||
pub use pump_fun::PumpFunDecodedEvent;
|
pub use pump_fun::PumpFunDecodedEvent;
|
||||||
pub use pump_fun::PumpFunDecoder;
|
pub use pump_fun::PumpFunDecoder;
|
||||||
pub use pump_fun::PumpFunTradeDecoded;
|
pub use pump_fun::PumpFunTradeDecoded;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1627,9 +1627,85 @@ impl DexDecodeService {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
},
|
},
|
||||||
|
crate::PumpFunDecodedEvent::InstructionAudit(event) => {
|
||||||
|
let pool_account = Self::pump_fun_payload_string(
|
||||||
|
&event.payload_json,
|
||||||
|
&["poolAccount", "bondingCurve", "bonding_curve", "pool"],
|
||||||
|
);
|
||||||
|
let token_a_mint = Self::pump_fun_payload_string(
|
||||||
|
&event.payload_json,
|
||||||
|
&["tokenAMint", "token_a_mint", "tokenMint", "token_mint", "mint"],
|
||||||
|
);
|
||||||
|
let token_b_mint = match Self::pump_fun_payload_string(
|
||||||
|
&event.payload_json,
|
||||||
|
&["tokenBMint", "token_b_mint", "quoteMint", "quote_mint"],
|
||||||
|
) {
|
||||||
|
Some(token_b_mint) => Some(token_b_mint),
|
||||||
|
None => {
|
||||||
|
if token_a_mint.is_some() {
|
||||||
|
Some(crate::WSOL_MINT_ID.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let lp_mint = Self::pump_fun_payload_string(
|
||||||
|
&event.payload_json,
|
||||||
|
&[
|
||||||
|
"lpMint",
|
||||||
|
"lp_mint",
|
||||||
|
"associatedBondingCurve",
|
||||||
|
"associated_bonding_curve",
|
||||||
|
"poolBaseTokenAccount",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
return self
|
||||||
|
.materialize_named_dex_event(
|
||||||
|
transaction,
|
||||||
|
event.transaction_id,
|
||||||
|
event.instruction_id,
|
||||||
|
"pump_fun",
|
||||||
|
event.program_id.clone(),
|
||||||
|
event.event_kind.as_str(),
|
||||||
|
pool_account,
|
||||||
|
None,
|
||||||
|
token_a_mint,
|
||||||
|
token_b_mint,
|
||||||
|
lp_mint,
|
||||||
|
event.payload_json.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn pump_fun_payload_string(
|
||||||
|
payload: &serde_json::Value,
|
||||||
|
keys: &[&str],
|
||||||
|
) -> std::option::Option<std::string::String> {
|
||||||
|
if let Some(object) = payload.as_object() {
|
||||||
|
for key in keys {
|
||||||
|
let value = object.get(*key);
|
||||||
|
if let Some(value) = value {
|
||||||
|
if let Some(text) = value.as_str() {
|
||||||
|
if !text.trim().is_empty() {
|
||||||
|
return Some(text.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let decoded_arguments = object.get("decodedArguments");
|
||||||
|
if let Some(decoded_arguments) = decoded_arguments {
|
||||||
|
let nested = Self::pump_fun_payload_string(decoded_arguments, keys);
|
||||||
|
if nested.is_some() {
|
||||||
|
return nested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
async fn persist_pump_fun_trade_event(
|
async fn persist_pump_fun_trade_event(
|
||||||
&self,
|
&self,
|
||||||
transaction: &crate::ChainTransactionDto,
|
transaction: &crate::ChainTransactionDto,
|
||||||
@@ -2266,8 +2342,23 @@ impl DexDecodeService {
|
|||||||
Ok(decoded_events) => decoded_events,
|
Ok(decoded_events) => decoded_events,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
};
|
};
|
||||||
|
let decoded_events = pump_fun_enrich_trade_events_with_instruction_context(decoded_events);
|
||||||
let mut persisted = std::vec::Vec::new();
|
let mut persisted = std::vec::Vec::new();
|
||||||
for decoded_event in &decoded_events {
|
for decoded_event in &decoded_events {
|
||||||
|
if !pump_fun_decoded_event_is_trade_event(decoded_event) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let persist_result = self.persist_pump_fun_event(transaction, decoded_event).await;
|
||||||
|
let persisted_event = match persist_result {
|
||||||
|
Ok(persisted_event) => persisted_event,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
persisted.push(persisted_event);
|
||||||
|
}
|
||||||
|
for decoded_event in &decoded_events {
|
||||||
|
if pump_fun_decoded_event_is_trade_event(decoded_event) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let persist_result = self.persist_pump_fun_event(transaction, decoded_event).await;
|
let persist_result = self.persist_pump_fun_event(transaction, decoded_event).await;
|
||||||
let persisted_event = match persist_result {
|
let persisted_event = match persist_result {
|
||||||
Ok(persisted_event) => persisted_event,
|
Ok(persisted_event) => persisted_event,
|
||||||
@@ -4313,6 +4404,310 @@ fn dex_decode_extract_first_amount_string(
|
|||||||
return dex_decode_extract_first_number_as_string(value, candidate_keys);
|
return dex_decode_extract_first_number_as_string(value, candidate_keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn pump_fun_enrich_trade_events_with_instruction_context(
|
||||||
|
decoded_events: std::vec::Vec<crate::PumpFunDecodedEvent>,
|
||||||
|
) -> std::vec::Vec<crate::PumpFunDecodedEvent> {
|
||||||
|
let mut enriched_events = std::vec::Vec::new();
|
||||||
|
for decoded_event in &decoded_events {
|
||||||
|
let enriched_event = match decoded_event {
|
||||||
|
crate::PumpFunDecodedEvent::InstructionAudit(event) => {
|
||||||
|
if event.event_kind.as_str() == "pump_fun.trade_event" {
|
||||||
|
let mut enriched_event = event.clone();
|
||||||
|
pump_fun_merge_matching_instruction_context_into_trade_event(
|
||||||
|
&decoded_events,
|
||||||
|
&mut enriched_event,
|
||||||
|
);
|
||||||
|
pump_fun_mark_trade_event_duplicate_when_direct_instruction_exists(
|
||||||
|
&decoded_events,
|
||||||
|
&mut enriched_event,
|
||||||
|
);
|
||||||
|
crate::PumpFunDecodedEvent::InstructionAudit(enriched_event)
|
||||||
|
} else {
|
||||||
|
decoded_event.clone()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => decoded_event.clone(),
|
||||||
|
};
|
||||||
|
enriched_events.push(enriched_event);
|
||||||
|
}
|
||||||
|
return enriched_events;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pump_fun_merge_matching_instruction_context_into_trade_event(
|
||||||
|
decoded_events: &[crate::PumpFunDecodedEvent],
|
||||||
|
trade_event: &mut crate::PumpFunInstructionAuditDecoded,
|
||||||
|
) {
|
||||||
|
let trade_payload = trade_event.payload_json.clone();
|
||||||
|
let trade_instruction_id = dex_decode_extract_first_i64(
|
||||||
|
&trade_payload,
|
||||||
|
&["instructionId", "instruction_id"],
|
||||||
|
);
|
||||||
|
let trade_mint = dex_decode_extract_first_string(
|
||||||
|
&trade_payload,
|
||||||
|
&["mint", "tokenMint", "tokenAMint"],
|
||||||
|
);
|
||||||
|
let trade_actor = dex_decode_extract_first_string(
|
||||||
|
&trade_payload,
|
||||||
|
&["user", "actorWallet", "userWallet"],
|
||||||
|
);
|
||||||
|
for sibling in decoded_events {
|
||||||
|
let sibling_event = match sibling {
|
||||||
|
crate::PumpFunDecodedEvent::InstructionAudit(sibling_event) => sibling_event,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
if sibling_event.event_kind.as_str() == "pump_fun.trade_event" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !pump_fun_instruction_context_can_back_trade_event(sibling_event.event_kind.as_str()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let sibling_instruction_id = Some(sibling_event.instruction_id);
|
||||||
|
if trade_instruction_id.is_some()
|
||||||
|
&& sibling_instruction_id.is_some()
|
||||||
|
&& trade_instruction_id != sibling_instruction_id
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let sibling_mint = dex_decode_extract_first_string(
|
||||||
|
&sibling_event.payload_json,
|
||||||
|
&["mint", "tokenMint", "tokenAMint"],
|
||||||
|
);
|
||||||
|
if !dex_decode_optional_strings_match(trade_mint.as_deref(), sibling_mint.as_deref()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let sibling_actor = dex_decode_extract_first_string(
|
||||||
|
&sibling_event.payload_json,
|
||||||
|
&["user", "actorWallet", "userWallet"],
|
||||||
|
);
|
||||||
|
if !dex_decode_optional_strings_match(trade_actor.as_deref(), sibling_actor.as_deref()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pump_fun_merge_instruction_context_payload(
|
||||||
|
&sibling_event.payload_json,
|
||||||
|
&mut trade_event.payload_json,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pump_fun_instruction_context_can_back_trade_event(event_kind: &str) -> bool {
|
||||||
|
match event_kind {
|
||||||
|
"pump_fun.buy_exact_quote_in_v2" => return true,
|
||||||
|
"pump_fun.buy_v2" => return true,
|
||||||
|
"pump_fun.sell_v2" => return true,
|
||||||
|
"pump_fun.buy_exact_sol_in" => return true,
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pump_fun_mark_trade_event_duplicate_when_direct_instruction_exists(
|
||||||
|
decoded_events: &[crate::PumpFunDecodedEvent],
|
||||||
|
trade_event: &mut crate::PumpFunInstructionAuditDecoded,
|
||||||
|
) {
|
||||||
|
let trade_payload = trade_event.payload_json.clone();
|
||||||
|
let trade_instruction_id = dex_decode_extract_first_i64(
|
||||||
|
&trade_payload,
|
||||||
|
&["instructionId", "instruction_id"],
|
||||||
|
);
|
||||||
|
let trade_mint = dex_decode_extract_first_string(
|
||||||
|
&trade_payload,
|
||||||
|
&["mint", "tokenMint", "tokenAMint"],
|
||||||
|
);
|
||||||
|
let trade_actor = dex_decode_extract_first_string(
|
||||||
|
&trade_payload,
|
||||||
|
&["user", "actorWallet", "userWallet"],
|
||||||
|
);
|
||||||
|
for sibling in decoded_events {
|
||||||
|
let direct_match = match sibling {
|
||||||
|
crate::PumpFunDecodedEvent::BuyTrade(event) => {
|
||||||
|
pump_fun_direct_trade_matches_anchor_trade_event(
|
||||||
|
event.instruction_id,
|
||||||
|
event.mint.as_deref(),
|
||||||
|
event.user.as_deref(),
|
||||||
|
trade_instruction_id,
|
||||||
|
trade_mint.as_deref(),
|
||||||
|
trade_actor.as_deref(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
crate::PumpFunDecodedEvent::SellTrade(event) => {
|
||||||
|
pump_fun_direct_trade_matches_anchor_trade_event(
|
||||||
|
event.instruction_id,
|
||||||
|
event.mint.as_deref(),
|
||||||
|
event.user.as_deref(),
|
||||||
|
trade_instruction_id,
|
||||||
|
trade_mint.as_deref(),
|
||||||
|
trade_actor.as_deref(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
crate::PumpFunDecodedEvent::InstructionAudit(event) => {
|
||||||
|
if event.event_kind.as_str() != "pump_fun.buy_exact_sol_in" {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
let instruction_mint = dex_decode_extract_first_string(
|
||||||
|
&event.payload_json,
|
||||||
|
&["mint", "tokenMint", "tokenAMint"],
|
||||||
|
);
|
||||||
|
let instruction_actor = dex_decode_extract_first_string(
|
||||||
|
&event.payload_json,
|
||||||
|
&["user", "actorWallet", "userWallet"],
|
||||||
|
);
|
||||||
|
pump_fun_direct_trade_matches_anchor_trade_event(
|
||||||
|
event.instruction_id,
|
||||||
|
instruction_mint.as_deref(),
|
||||||
|
instruction_actor.as_deref(),
|
||||||
|
trade_instruction_id,
|
||||||
|
trade_mint.as_deref(),
|
||||||
|
trade_actor.as_deref(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if !direct_match {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let object = match trade_event.payload_json.as_object_mut() {
|
||||||
|
Some(object) => object,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
object.insert(
|
||||||
|
"skipTradeReason".to_string(),
|
||||||
|
serde_json::Value::String(
|
||||||
|
"pump_fun_trade_event_covered_by_direct_instruction_trade".to_string(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
object.insert(
|
||||||
|
"skipCandleReason".to_string(),
|
||||||
|
serde_json::Value::String(
|
||||||
|
"pump_fun_trade_event_covered_by_direct_instruction_trade".to_string(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
object.insert(
|
||||||
|
"anchorTradeEventCoveredByDirectInstructionTrade".to_string(),
|
||||||
|
serde_json::Value::Bool(true),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pump_fun_direct_trade_matches_anchor_trade_event(
|
||||||
|
direct_instruction_id: i64,
|
||||||
|
direct_mint: std::option::Option<&str>,
|
||||||
|
direct_actor: std::option::Option<&str>,
|
||||||
|
trade_instruction_id: std::option::Option<i64>,
|
||||||
|
trade_mint: std::option::Option<&str>,
|
||||||
|
trade_actor: std::option::Option<&str>,
|
||||||
|
) -> bool {
|
||||||
|
if let Some(trade_instruction_id) = trade_instruction_id {
|
||||||
|
if direct_instruction_id != trade_instruction_id {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !dex_decode_optional_strings_match(trade_mint, direct_mint) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if !dex_decode_optional_strings_match(trade_actor, direct_actor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn pump_fun_merge_instruction_context_payload(
|
||||||
|
instruction_payload: &serde_json::Value,
|
||||||
|
trade_payload: &mut serde_json::Value,
|
||||||
|
) {
|
||||||
|
let trade_object = match trade_payload.as_object_mut() {
|
||||||
|
Some(trade_object) => trade_object,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
let instruction_object = match instruction_payload.as_object() {
|
||||||
|
Some(instruction_object) => instruction_object,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
let copy_keys = [
|
||||||
|
"poolAccount",
|
||||||
|
"bondingCurve",
|
||||||
|
"associatedBondingCurve",
|
||||||
|
"poolBaseTokenAccount",
|
||||||
|
"poolQuoteTokenAccount",
|
||||||
|
"associatedQuoteBondingCurve",
|
||||||
|
"lpMint",
|
||||||
|
"tokenAMint",
|
||||||
|
"tokenBMint",
|
||||||
|
"quoteMint",
|
||||||
|
"feeRecipient",
|
||||||
|
"creatorVault",
|
||||||
|
"associatedCreatorVault",
|
||||||
|
];
|
||||||
|
for key in copy_keys {
|
||||||
|
if trade_object.contains_key(key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let value = match instruction_object.get(key) {
|
||||||
|
Some(value) => value.clone(),
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
trade_object.insert(key.to_string(), value);
|
||||||
|
}
|
||||||
|
trade_object.insert(
|
||||||
|
"amountSource".to_string(),
|
||||||
|
serde_json::Value::String("pump_fun_anchor_trade_event".to_string()),
|
||||||
|
);
|
||||||
|
trade_object.insert(
|
||||||
|
"anchorTradeEventInstructionContextSource".to_string(),
|
||||||
|
serde_json::Value::String("matching_instruction_audit".to_string()),
|
||||||
|
);
|
||||||
|
trade_object.remove("skipTradeReason");
|
||||||
|
trade_object.remove("skipCandleReason");
|
||||||
|
trade_object.remove("skipCatalogReason");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dex_decode_extract_first_i64(
|
||||||
|
value: &serde_json::Value,
|
||||||
|
candidate_keys: &[&str],
|
||||||
|
) -> std::option::Option<i64> {
|
||||||
|
if let Some(object) = value.as_object() {
|
||||||
|
for candidate_key in candidate_keys {
|
||||||
|
let candidate_value = match object.get(*candidate_key) {
|
||||||
|
Some(candidate_value) => candidate_value,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
if let Some(number) = candidate_value.as_i64() {
|
||||||
|
return Some(number);
|
||||||
|
}
|
||||||
|
if let Some(text) = candidate_value.as_str() {
|
||||||
|
let parsed = text.parse::<i64>();
|
||||||
|
match parsed {
|
||||||
|
Ok(parsed) => return Some(parsed),
|
||||||
|
Err(_) => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dex_decode_optional_strings_match(
|
||||||
|
left: std::option::Option<&str>,
|
||||||
|
right: std::option::Option<&str>,
|
||||||
|
) -> bool {
|
||||||
|
match (left, right) {
|
||||||
|
(Some(left), Some(right)) => return left == right,
|
||||||
|
_ => return true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pump_fun_decoded_event_is_trade_event(decoded_event: &crate::PumpFunDecodedEvent) -> bool {
|
||||||
|
match decoded_event {
|
||||||
|
crate::PumpFunDecodedEvent::InstructionAudit(event) => {
|
||||||
|
return event.event_kind.as_str() == "pump_fun.trade_event";
|
||||||
|
},
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn dex_decode_extract_first_string(
|
fn dex_decode_extract_first_string(
|
||||||
value: &serde_json::Value,
|
value: &serde_json::Value,
|
||||||
candidate_keys: &[&str],
|
candidate_keys: &[&str],
|
||||||
|
|||||||
@@ -119,6 +119,21 @@ pub(crate) fn dex_detection_route(
|
|||||||
("pump_fun", "pump_fun.sell") => {
|
("pump_fun", "pump_fun.sell") => {
|
||||||
return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
|
return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
|
||||||
},
|
},
|
||||||
|
("pump_fun", "pump_fun.buy_exact_sol_in") => {
|
||||||
|
return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
|
||||||
|
},
|
||||||
|
("pump_fun", "pump_fun.buy_exact_quote_in_v2") => {
|
||||||
|
return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
|
||||||
|
},
|
||||||
|
("pump_fun", "pump_fun.buy_v2") => {
|
||||||
|
return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
|
||||||
|
},
|
||||||
|
("pump_fun", "pump_fun.sell_v2") => {
|
||||||
|
return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
|
||||||
|
},
|
||||||
|
("pump_fun", "pump_fun.trade_event") => {
|
||||||
|
return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
|
||||||
|
},
|
||||||
("pump_swap", "pump_swap.buy") => {
|
("pump_swap", "pump_swap.buy") => {
|
||||||
if crate::dex_detection_route::is_incomplete_pump_swap_decoded_event(decoded_event) {
|
if crate::dex_detection_route::is_incomplete_pump_swap_decoded_event(decoded_event) {
|
||||||
return Some(
|
return Some(
|
||||||
|
|||||||
@@ -323,6 +323,15 @@ pub fn is_dex_trade_event_kind(event_kind: &str) -> bool {
|
|||||||
if event_kind == "raydium_launchpad.trade_event" {
|
if event_kind == "raydium_launchpad.trade_event" {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if event_kind == "pump_fun.trade_event" {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if event_kind.ends_with(".buy_v2") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if event_kind.ends_with(".sell_v2") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if event_kind.ends_with(".buy") {
|
if event_kind.ends_with(".buy") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -520,6 +529,15 @@ pub fn is_dex_fee_event_kind(event_kind: &str) -> bool {
|
|||||||
if event_kind.contains("partner_claim_fee") {
|
if event_kind.contains("partner_claim_fee") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if event_kind.contains("distribute_creator_fees") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if event_kind.contains("minimum_distributable_fee") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if event_kind.contains("reserved_fee_recipients_event") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -828,6 +846,15 @@ pub fn is_dex_admin_event_kind(event_kind: &str) -> bool {
|
|||||||
if event_kind.contains(".extend_account") {
|
if event_kind.contains(".extend_account") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if event_kind.contains(".add_quote_mint") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if event_kind.contains(".remove_quote_mint") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if event_kind.contains("reserved_fee_recipients") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if event_kind.contains("update_") {
|
if event_kind.contains("update_") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1165,6 +1192,9 @@ mod tests {
|
|||||||
assert_eq!(super::classify_dex_event_category_code("raydium_clmm.swap_v2"), "trade");
|
assert_eq!(super::classify_dex_event_category_code("raydium_clmm.swap_v2"), "trade");
|
||||||
assert_eq!(super::classify_dex_event_category_code("raydium_clmm.exact_output"), "trade");
|
assert_eq!(super::classify_dex_event_category_code("raydium_clmm.exact_output"), "trade");
|
||||||
assert_eq!(super::classify_dex_event_category_code("pump_fun.buy"), "trade");
|
assert_eq!(super::classify_dex_event_category_code("pump_fun.buy"), "trade");
|
||||||
|
assert_eq!(super::classify_dex_event_category_code("pump_fun.buy_v2"), "trade");
|
||||||
|
assert_eq!(super::classify_dex_event_category_code("pump_fun.sell_v2"), "trade");
|
||||||
|
assert_eq!(super::classify_dex_event_category_code("pump_fun.trade_event"), "trade");
|
||||||
assert!(super::is_dex_trade_event_kind("raydium_cpmm.swap_base_input"));
|
assert!(super::is_dex_trade_event_kind("raydium_cpmm.swap_base_input"));
|
||||||
assert!(super::is_dex_candle_candidate_event_kind("raydium_cpmm.swap_base_input"));
|
assert!(super::is_dex_candle_candidate_event_kind("raydium_cpmm.swap_base_input"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -318,6 +318,9 @@ fn infer_expected_db_target_for_entry(
|
|||||||
if decoder_code == "pump_swap" {
|
if decoder_code == "pump_swap" {
|
||||||
return infer_pump_swap_expected_db_target(entry_name, entry_kind);
|
return infer_pump_swap_expected_db_target(entry_name, entry_kind);
|
||||||
}
|
}
|
||||||
|
if decoder_code == "pump_fun" {
|
||||||
|
return infer_pump_fun_expected_db_target(entry_name, entry_kind);
|
||||||
|
}
|
||||||
if decoder_code == "raydium_cpmm"
|
if decoder_code == "raydium_cpmm"
|
||||||
&& (entry_name == "swap_event" || entry_name == "anchor_idl_instruction")
|
&& (entry_name == "swap_event" || entry_name == "anchor_idl_instruction")
|
||||||
{
|
{
|
||||||
@@ -524,6 +527,104 @@ fn infer_expected_db_target(
|
|||||||
return Some(target.to_string());
|
return Some(target.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn infer_pump_fun_expected_db_target(
|
||||||
|
entry_name: &str,
|
||||||
|
entry_kind: &str,
|
||||||
|
) -> std::option::Option<std::string::String> {
|
||||||
|
if entry_kind == crate::ENTRY_KIND_PROGRAM {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if entry_name == "buy"
|
||||||
|
|| entry_name == "sell"
|
||||||
|
|| entry_name == "buy_v2"
|
||||||
|
|| entry_name == "sell_v2"
|
||||||
|
|| entry_name == "buy_exact_sol_in"
|
||||||
|
|| entry_name == "buy_exact_quote_in_v2"
|
||||||
|
{
|
||||||
|
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_TRADE_EVENTS.to_string());
|
||||||
|
}
|
||||||
|
if entry_name == "trade_event" {
|
||||||
|
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_TRADE_EVENTS.to_string());
|
||||||
|
}
|
||||||
|
if entry_name == "create"
|
||||||
|
|| entry_name == "create_event"
|
||||||
|
|| entry_name == "create_v2"
|
||||||
|
|| entry_name == "create_v2_token"
|
||||||
|
|| entry_name == "complete_event"
|
||||||
|
{
|
||||||
|
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS.to_string());
|
||||||
|
}
|
||||||
|
if entry_name == "initialize" {
|
||||||
|
return Some(
|
||||||
|
crate::DexEventCoverageEntryDto::DB_TARGET_POOL_LIFECYCLE_EVENTS.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if entry_name == "migrate"
|
||||||
|
|| entry_name == "migrate_v2"
|
||||||
|
|| entry_name == "migrate_bonding_curve_creator"
|
||||||
|
|| entry_name == "migrate_bonding_curve_creator_event"
|
||||||
|
|| entry_name == "complete_pump_amm_migration_event"
|
||||||
|
{
|
||||||
|
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS.to_string());
|
||||||
|
}
|
||||||
|
if entry_name == "collect_creator_fee"
|
||||||
|
|| entry_name == "collect_creator_fee_v2"
|
||||||
|
|| entry_name == "collect_creator_fee_event"
|
||||||
|
|| entry_name == "distribute_creator_fees"
|
||||||
|
|| entry_name == "distribute_creator_fees_v2"
|
||||||
|
|| entry_name == "distribute_creator_fees_event"
|
||||||
|
|| entry_name == "get_minimum_distributable_fee"
|
||||||
|
|| entry_name == "minimum_distributable_fee_event"
|
||||||
|
{
|
||||||
|
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_FEE_EVENTS.to_string());
|
||||||
|
}
|
||||||
|
if entry_name == "claim_cashback"
|
||||||
|
|| entry_name == "claim_cashback_v2"
|
||||||
|
|| entry_name == "claim_cashback_event"
|
||||||
|
|| entry_name == "claim_token_incentives"
|
||||||
|
|| entry_name == "claim_token_incentives_event"
|
||||||
|
|| entry_name == "admin_update_token_incentives"
|
||||||
|
|| entry_name == "admin_update_token_incentives_event"
|
||||||
|
|| entry_name == "init_user_volume_accumulator"
|
||||||
|
|| entry_name == "init_user_volume_accumulator_event"
|
||||||
|
|| entry_name == "sync_user_volume_accumulator"
|
||||||
|
|| entry_name == "sync_user_volume_accumulator_event"
|
||||||
|
|| entry_name == "close_user_volume_accumulator"
|
||||||
|
|| entry_name == "close_user_volume_accumulator_event"
|
||||||
|
{
|
||||||
|
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_REWARD_EVENTS.to_string());
|
||||||
|
}
|
||||||
|
if entry_name == "admin_set_creator"
|
||||||
|
|| entry_name == "admin_set_creator_event"
|
||||||
|
|| entry_name == "admin_set_idl_authority"
|
||||||
|
|| entry_name == "admin_set_idl_authority_event"
|
||||||
|
|| entry_name == "add_quote_mint"
|
||||||
|
|| entry_name == "remove_quote_mint"
|
||||||
|
|| entry_name == "extend_account"
|
||||||
|
|| entry_name == "extend_account_event"
|
||||||
|
|| entry_name == "set_creator"
|
||||||
|
|| entry_name == "set_creator_event"
|
||||||
|
|| entry_name == "set_mayhem_virtual_params"
|
||||||
|
|| entry_name == "update_mayhem_virtual_params_event"
|
||||||
|
|| entry_name == "set_metaplex_creator"
|
||||||
|
|| entry_name == "set_metaplex_creator_event"
|
||||||
|
|| entry_name == "set_params"
|
||||||
|
|| entry_name == "set_params_event"
|
||||||
|
|| entry_name == "set_reserved_fee_recipients"
|
||||||
|
|| entry_name == "reserved_fee_recipients_event"
|
||||||
|
|| entry_name == "set_virtual_quote_reserves"
|
||||||
|
|| entry_name == "toggle_cashback_enabled"
|
||||||
|
|| entry_name == "toggle_create_v2"
|
||||||
|
|| entry_name == "toggle_mayhem_mode"
|
||||||
|
|| entry_name == "update_buyback_config"
|
||||||
|
|| entry_name == "update_global_authority"
|
||||||
|
|| entry_name == "update_global_authority_event"
|
||||||
|
{
|
||||||
|
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_POOL_ADMIN_EVENTS.to_string());
|
||||||
|
}
|
||||||
|
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
fn infer_pump_swap_expected_db_target(
|
fn infer_pump_swap_expected_db_target(
|
||||||
entry_name: &str,
|
entry_name: &str,
|
||||||
entry_kind: &str,
|
entry_kind: &str,
|
||||||
@@ -654,6 +755,161 @@ fn infer_pump_swap_event_family(
|
|||||||
return infer_event_family(entry_name, entry_kind);
|
return infer_event_family(entry_name, entry_kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn infer_pump_fun_event_family(
|
||||||
|
entry_name: &str,
|
||||||
|
entry_kind: &str,
|
||||||
|
) -> std::option::Option<std::string::String> {
|
||||||
|
if entry_kind == crate::ENTRY_KIND_PROGRAM {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match entry_name {
|
||||||
|
"buy"
|
||||||
|
| "sell"
|
||||||
|
| "buy_v2"
|
||||||
|
| "sell_v2"
|
||||||
|
| "buy_exact_quote_in_v2"
|
||||||
|
| "buy_exact_sol_in"
|
||||||
|
| "trade_event" => return Some("swap".to_string()),
|
||||||
|
"create" | "create_event" | "create_v2" | "create_v2_token" | "complete_event" => {
|
||||||
|
return Some("launch".to_string());
|
||||||
|
},
|
||||||
|
"migrate"
|
||||||
|
| "migrate_v2"
|
||||||
|
| "migrate_bonding_curve_creator"
|
||||||
|
| "migrate_bonding_curve_creator_event"
|
||||||
|
| "complete_pump_amm_migration_event" => return Some("migration".to_string()),
|
||||||
|
"claim_cashback"
|
||||||
|
| "claim_cashback_v2"
|
||||||
|
| "claim_cashback_event"
|
||||||
|
| "claim_token_incentives"
|
||||||
|
| "claim_token_incentives_event"
|
||||||
|
| "admin_update_token_incentives"
|
||||||
|
| "admin_update_token_incentives_event"
|
||||||
|
| "init_user_volume_accumulator"
|
||||||
|
| "init_user_volume_accumulator_event"
|
||||||
|
| "sync_user_volume_accumulator"
|
||||||
|
| "sync_user_volume_accumulator_event"
|
||||||
|
| "close_user_volume_accumulator"
|
||||||
|
| "close_user_volume_accumulator_event" => return Some("reward".to_string()),
|
||||||
|
"collect_creator_fee"
|
||||||
|
| "collect_creator_fee_v2"
|
||||||
|
| "collect_creator_fee_event"
|
||||||
|
| "distribute_creator_fees"
|
||||||
|
| "distribute_creator_fees_v2"
|
||||||
|
| "distribute_creator_fees_event"
|
||||||
|
| "get_minimum_distributable_fee"
|
||||||
|
| "minimum_distributable_fee_event" => return Some("fee".to_string()),
|
||||||
|
"add_quote_mint"
|
||||||
|
| "remove_quote_mint"
|
||||||
|
| "admin_set_creator"
|
||||||
|
| "admin_set_creator_event"
|
||||||
|
| "admin_set_idl_authority"
|
||||||
|
| "admin_set_idl_authority_event"
|
||||||
|
| "extend_account"
|
||||||
|
| "extend_account_event"
|
||||||
|
| "set_creator"
|
||||||
|
| "set_creator_event"
|
||||||
|
| "set_mayhem_virtual_params"
|
||||||
|
| "update_mayhem_virtual_params_event"
|
||||||
|
| "set_metaplex_creator"
|
||||||
|
| "set_metaplex_creator_event"
|
||||||
|
| "set_params"
|
||||||
|
| "set_params_event"
|
||||||
|
| "set_reserved_fee_recipients"
|
||||||
|
| "reserved_fee_recipients_event"
|
||||||
|
| "set_virtual_quote_reserves"
|
||||||
|
| "toggle_cashback_enabled"
|
||||||
|
| "toggle_create_v2"
|
||||||
|
| "toggle_mayhem_mode"
|
||||||
|
| "update_buyback_config"
|
||||||
|
| "update_global_authority"
|
||||||
|
| "update_global_authority_event" => return Some("admin_config".to_string()),
|
||||||
|
"initialize" => return Some("pool_create".to_string()),
|
||||||
|
_ => return infer_event_family(entry_name, entry_kind),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pump_fun_local_event_kind(entry_name: &str) -> std::option::Option<std::string::String> {
|
||||||
|
if entry_name.ends_with("_event") {
|
||||||
|
return Some(format!("pump_fun.{}", entry_name));
|
||||||
|
}
|
||||||
|
match entry_name {
|
||||||
|
"buy" => return Some("pump_fun.buy".to_string()),
|
||||||
|
"sell" => return Some("pump_fun.sell".to_string()),
|
||||||
|
"create_v2" => return Some("pump_fun.create_v2_token".to_string()),
|
||||||
|
"add_quote_mint" => return Some("pump_fun.add_quote_mint".to_string()),
|
||||||
|
"admin_set_creator" => return Some("pump_fun.admin_set_creator".to_string()),
|
||||||
|
"admin_set_idl_authority" => {
|
||||||
|
return Some("pump_fun.admin_set_idl_authority".to_string());
|
||||||
|
},
|
||||||
|
"admin_update_token_incentives" => {
|
||||||
|
return Some("pump_fun.admin_update_token_incentives".to_string());
|
||||||
|
},
|
||||||
|
"buy_exact_quote_in_v2" => {
|
||||||
|
return Some("pump_fun.buy_exact_quote_in_v2".to_string());
|
||||||
|
},
|
||||||
|
"buy_exact_sol_in" => return Some("pump_fun.buy_exact_sol_in".to_string()),
|
||||||
|
"buy_v2" => return Some("pump_fun.buy_v2".to_string()),
|
||||||
|
"claim_cashback" => return Some("pump_fun.claim_cashback".to_string()),
|
||||||
|
"claim_cashback_v2" => return Some("pump_fun.claim_cashback_v2".to_string()),
|
||||||
|
"claim_token_incentives" => {
|
||||||
|
return Some("pump_fun.claim_token_incentives".to_string());
|
||||||
|
},
|
||||||
|
"close_user_volume_accumulator" => {
|
||||||
|
return Some("pump_fun.close_user_volume_accumulator".to_string());
|
||||||
|
},
|
||||||
|
"collect_creator_fee" => return Some("pump_fun.collect_creator_fee".to_string()),
|
||||||
|
"collect_creator_fee_v2" => return Some("pump_fun.collect_creator_fee_v2".to_string()),
|
||||||
|
"create" => return Some("pump_fun.create".to_string()),
|
||||||
|
"distribute_creator_fees" => {
|
||||||
|
return Some("pump_fun.distribute_creator_fees".to_string());
|
||||||
|
},
|
||||||
|
"distribute_creator_fees_v2" => {
|
||||||
|
return Some("pump_fun.distribute_creator_fees_v2".to_string());
|
||||||
|
},
|
||||||
|
"extend_account" => return Some("pump_fun.extend_account".to_string()),
|
||||||
|
"get_minimum_distributable_fee" => {
|
||||||
|
return Some("pump_fun.get_minimum_distributable_fee".to_string());
|
||||||
|
},
|
||||||
|
"init_user_volume_accumulator" => {
|
||||||
|
return Some("pump_fun.init_user_volume_accumulator".to_string());
|
||||||
|
},
|
||||||
|
"initialize" => return Some("pump_fun.initialize".to_string()),
|
||||||
|
"migrate" => return Some("pump_fun.migrate".to_string()),
|
||||||
|
"migrate_bonding_curve_creator" => {
|
||||||
|
return Some("pump_fun.migrate_bonding_curve_creator".to_string());
|
||||||
|
},
|
||||||
|
"migrate_v2" => return Some("pump_fun.migrate_v2".to_string()),
|
||||||
|
"remove_quote_mint" => return Some("pump_fun.remove_quote_mint".to_string()),
|
||||||
|
"sell_v2" => return Some("pump_fun.sell_v2".to_string()),
|
||||||
|
"set_creator" => return Some("pump_fun.set_creator".to_string()),
|
||||||
|
"set_mayhem_virtual_params" => {
|
||||||
|
return Some("pump_fun.set_mayhem_virtual_params".to_string());
|
||||||
|
},
|
||||||
|
"set_metaplex_creator" => return Some("pump_fun.set_metaplex_creator".to_string()),
|
||||||
|
"set_params" => return Some("pump_fun.set_params".to_string()),
|
||||||
|
"set_reserved_fee_recipients" => {
|
||||||
|
return Some("pump_fun.set_reserved_fee_recipients".to_string());
|
||||||
|
},
|
||||||
|
"set_virtual_quote_reserves" => {
|
||||||
|
return Some("pump_fun.set_virtual_quote_reserves".to_string());
|
||||||
|
},
|
||||||
|
"sync_user_volume_accumulator" => {
|
||||||
|
return Some("pump_fun.sync_user_volume_accumulator".to_string());
|
||||||
|
},
|
||||||
|
"toggle_cashback_enabled" => {
|
||||||
|
return Some("pump_fun.toggle_cashback_enabled".to_string());
|
||||||
|
},
|
||||||
|
"toggle_create_v2" => return Some("pump_fun.toggle_create_v2".to_string()),
|
||||||
|
"toggle_mayhem_mode" => return Some("pump_fun.toggle_mayhem_mode".to_string()),
|
||||||
|
"update_buyback_config" => return Some("pump_fun.update_buyback_config".to_string()),
|
||||||
|
"update_global_authority" => {
|
||||||
|
return Some("pump_fun.update_global_authority".to_string());
|
||||||
|
},
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn pump_swap_local_event_kind(entry_name: &str) -> std::option::Option<std::string::String> {
|
fn pump_swap_local_event_kind(entry_name: &str) -> std::option::Option<std::string::String> {
|
||||||
if entry_name.ends_with("_event") {
|
if entry_name.ends_with("_event") {
|
||||||
return Some(format!("pump_swap.{}", entry_name));
|
return Some(format!("pump_swap.{}", entry_name));
|
||||||
@@ -716,6 +972,9 @@ fn infer_event_family_for_entry(
|
|||||||
entry_name: &str,
|
entry_name: &str,
|
||||||
entry_kind: &str,
|
entry_kind: &str,
|
||||||
) -> std::option::Option<std::string::String> {
|
) -> std::option::Option<std::string::String> {
|
||||||
|
if decoder_code == "pump_fun" {
|
||||||
|
return infer_pump_fun_event_family(entry_name, entry_kind);
|
||||||
|
}
|
||||||
if decoder_code == "pump_swap" {
|
if decoder_code == "pump_swap" {
|
||||||
return infer_pump_swap_event_family(entry_name, entry_kind);
|
return infer_pump_swap_event_family(entry_name, entry_kind);
|
||||||
}
|
}
|
||||||
@@ -1110,6 +1369,9 @@ pub(crate) fn known_local_event_kind(
|
|||||||
decoder_code: &str,
|
decoder_code: &str,
|
||||||
entry_name: &str,
|
entry_name: &str,
|
||||||
) -> std::option::Option<std::string::String> {
|
) -> std::option::Option<std::string::String> {
|
||||||
|
if decoder_code == "pump_fun" {
|
||||||
|
return pump_fun_local_event_kind(entry_name);
|
||||||
|
}
|
||||||
if decoder_code == "pump_swap" {
|
if decoder_code == "pump_swap" {
|
||||||
return pump_swap_local_event_kind(entry_name);
|
return pump_swap_local_event_kind(entry_name);
|
||||||
}
|
}
|
||||||
@@ -1488,6 +1750,85 @@ mod tests {
|
|||||||
Some("raydium_clmm.pool_created_event".to_string())
|
Some("raydium_clmm.pool_created_event".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn pump_fun_coverage_maps_local_idl_and_audit_entries() {
|
||||||
|
assert_eq!(
|
||||||
|
super::known_local_event_kind("pump_fun", "buy"),
|
||||||
|
Some("pump_fun.buy".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::known_local_event_kind("pump_fun", "create_v2"),
|
||||||
|
Some("pump_fun.create_v2_token".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::known_local_event_kind("pump_fun", "buy_v2"),
|
||||||
|
Some("pump_fun.buy_v2".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::known_local_event_kind("pump_fun", "collect_creator_fee_v2"),
|
||||||
|
Some("pump_fun.collect_creator_fee_v2".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::known_local_event_kind("pump_fun", "trade_event"),
|
||||||
|
Some("pump_fun.trade_event".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::known_local_event_kind("pump_fun", "claim_cashback_event"),
|
||||||
|
Some("pump_fun.claim_cashback_event".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::infer_event_family_for_entry("pump_fun", "create_event", crate::ENTRY_KIND_EVENT),
|
||||||
|
Some("launch".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::infer_event_family_for_entry(
|
||||||
|
"pump_fun",
|
||||||
|
"set_metaplex_creator_event",
|
||||||
|
crate::ENTRY_KIND_EVENT,
|
||||||
|
),
|
||||||
|
Some("admin_config".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::infer_event_family_for_entry(
|
||||||
|
"pump_fun",
|
||||||
|
"claim_token_incentives_event",
|
||||||
|
crate::ENTRY_KIND_EVENT,
|
||||||
|
),
|
||||||
|
Some("reward".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::infer_event_family_for_entry("pump_fun", "buy_v2", crate::ENTRY_KIND_INSTRUCTION),
|
||||||
|
Some("swap".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::infer_expected_db_target_for_entry(
|
||||||
|
"pump_fun",
|
||||||
|
"buy",
|
||||||
|
Some("swap"),
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
),
|
||||||
|
Some(crate::DexEventCoverageEntryDto::DB_TARGET_TRADE_EVENTS.to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::infer_expected_db_target_for_entry(
|
||||||
|
"pump_fun",
|
||||||
|
"buy_v2",
|
||||||
|
Some("swap"),
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
),
|
||||||
|
Some(crate::DexEventCoverageEntryDto::DB_TARGET_TRADE_EVENTS.to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::infer_expected_db_target_for_entry(
|
||||||
|
"pump_fun",
|
||||||
|
"create_v2",
|
||||||
|
Some("launch"),
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
),
|
||||||
|
Some(crate::DexEventCoverageEntryDto::DB_TARGET_LAUNCH_EVENTS.to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn launchpad_swap_instructions_materialize_as_launch_events_without_duplicate_trades() {
|
fn launchpad_swap_instructions_materialize_as_launch_events_without_duplicate_trades() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -319,6 +319,53 @@ fn resolve_instruction_name(
|
|||||||
};
|
};
|
||||||
return Some(format!("raydium_launchpad.{}", layout.instruction_name));
|
return Some(format!("raydium_launchpad.{}", layout.instruction_name));
|
||||||
}
|
}
|
||||||
|
if program_id == crate::PUMP_FUN_PROGRAM_ID || decoder_code == Some("pump_fun") {
|
||||||
|
let name = match discriminator_hex {
|
||||||
|
"e445a52e51cb9a1d" => "anchor_self_cpi_log",
|
||||||
|
"6f79153828185ed1" => "add_quote_mint",
|
||||||
|
"4519ab8e39ef0d04" => "admin_set_creator",
|
||||||
|
"08d960e79068c005" => "admin_set_idl_authority",
|
||||||
|
"d10b7357d5177ccc" => "admin_update_token_incentives",
|
||||||
|
"66063d1201daebea" => "buy",
|
||||||
|
"c2ab1c46684d5b2f" => "buy_exact_quote_in_v2",
|
||||||
|
"38fc74089edfcd5f" => "buy_exact_sol_in",
|
||||||
|
"b817ee6167c5d33d" => "buy_v2",
|
||||||
|
"253a237ebe35e4c5" => "claim_cashback",
|
||||||
|
"7af3cc415e741d37" => "claim_cashback_v2",
|
||||||
|
"1004471ccc01281b" => "claim_token_incentives",
|
||||||
|
"f945a4da9667548a" => "close_user_volume_accumulator",
|
||||||
|
"1416567bc61cdb84" => "collect_creator_fee",
|
||||||
|
"cf118af204221338" => "collect_creator_fee_v2",
|
||||||
|
"181ec828051c0777" => "create",
|
||||||
|
"d6904cec5f8b31b4" => "create_v2",
|
||||||
|
"a572670079cef751" => "distribute_creator_fees",
|
||||||
|
"ffcb134ff444089f" => "distribute_creator_fees_v2",
|
||||||
|
"ea66c2cb96483ee5" => "extend_account",
|
||||||
|
"75e17fca865f4423" => "get_minimum_distributable_fee",
|
||||||
|
"5e06ca73ff60e8b7" => "init_user_volume_accumulator",
|
||||||
|
"afaf6d1f0d989bed" => "initialize",
|
||||||
|
"9beae792ec9ea21e" => "migrate",
|
||||||
|
"577c34bf3426d6e8" => "migrate_bonding_curve_creator",
|
||||||
|
"bbcb121fceedfe29" => "migrate_v2",
|
||||||
|
"b141df2658d19e9b" => "remove_quote_mint",
|
||||||
|
"33e685a4017f83ad" => "sell",
|
||||||
|
"5df6823ce7e940b2" => "sell_v2",
|
||||||
|
"fe94ff70cf8eaaa5" => "set_creator",
|
||||||
|
"3da9bcbf99952a61" => "set_mayhem_virtual_params",
|
||||||
|
"8a60aed93055c5f6" => "set_metaplex_creator",
|
||||||
|
"1beab2349302bb8d" => "set_params",
|
||||||
|
"6faca2e87259d58e" => "set_reserved_fee_recipients",
|
||||||
|
"6587bf6809581460" => "set_virtual_quote_reserves",
|
||||||
|
"561fc057a3574fee" => "sync_user_volume_accumulator",
|
||||||
|
"7367e0ffbd5956c3" => "toggle_cashback_enabled",
|
||||||
|
"1cffe6f0ac6bcbab" => "toggle_create_v2",
|
||||||
|
"01096fd0641fffa3" => "toggle_mayhem_mode",
|
||||||
|
"fbe0ab92a01a71e9" => "update_buyback_config",
|
||||||
|
"e3b54ac4d01561d5" => "update_global_authority",
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
return Some(name.to_string());
|
||||||
|
}
|
||||||
if program_id == crate::PUMP_SWAP_PROGRAM_ID || decoder_code == Some("pump_swap") {
|
if program_id == crate::PUMP_SWAP_PROGRAM_ID || decoder_code == Some("pump_swap") {
|
||||||
let name = match discriminator_hex {
|
let name = match discriminator_hex {
|
||||||
"e445a52e51cb9a1d" => "anchor_self_cpi_log",
|
"e445a52e51cb9a1d" => "anchor_self_cpi_log",
|
||||||
|
|||||||
@@ -1177,6 +1177,8 @@ pub use dex::PumpFunCreateV2TokenDecoded;
|
|||||||
pub use dex::PumpFunDecodedEvent;
|
pub use dex::PumpFunDecodedEvent;
|
||||||
/// Pump.fun decoder.
|
/// Pump.fun decoder.
|
||||||
pub use dex::PumpFunDecoder;
|
pub use dex::PumpFunDecoder;
|
||||||
|
/// Decoded Pump.fun audit-only instruction event.
|
||||||
|
pub use dex::PumpFunInstructionAuditDecoded;
|
||||||
/// Decoded Pump.fun bonding-curve trade event.
|
/// Decoded Pump.fun bonding-curve trade event.
|
||||||
pub use dex::PumpFunTradeDecoded;
|
pub use dex::PumpFunTradeDecoded;
|
||||||
/// Decoded PumpSwap event.
|
/// Decoded PumpSwap event.
|
||||||
|
|||||||
@@ -112,6 +112,15 @@ impl NonTradeEventMaterializationService {
|
|||||||
if is_anchor_event_audit_only(&payload) {
|
if is_anchor_event_audit_only(&payload) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if should_skip_pump_fun_duplicate_non_trade_event(decoded_event, &decoded_events) {
|
||||||
|
tracing::debug!(
|
||||||
|
event_kind = %decoded_event.event_kind,
|
||||||
|
decoded_event_id = ?decoded_event.id,
|
||||||
|
signature = %transaction.signature,
|
||||||
|
"skipping duplicate pump_fun non-trade materialization"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if crate::is_dex_pool_lifecycle_event_kind(decoded_event.event_kind.as_str()) {
|
if crate::is_dex_pool_lifecycle_event_kind(decoded_event.event_kind.as_str()) {
|
||||||
let cleanup_result =
|
let cleanup_result =
|
||||||
self.delete_stale_pool_admin_event_for_lifecycle(decoded_event).await;
|
self.delete_stale_pool_admin_event_for_lifecycle(decoded_event).await;
|
||||||
@@ -140,7 +149,9 @@ impl NonTradeEventMaterializationService {
|
|||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if crate::is_dex_pool_lifecycle_event_kind(decoded_event.event_kind.as_str()) {
|
if crate::is_dex_pool_lifecycle_event_kind(decoded_event.event_kind.as_str())
|
||||||
|
&& !is_launchpad_launch_event_materializable(decoded_event.event_kind.as_str())
|
||||||
|
{
|
||||||
let materialized = self
|
let materialized = self
|
||||||
.materialize_pool_lifecycle_event(&transaction, transaction_id, decoded_event)
|
.materialize_pool_lifecycle_event(&transaction, transaction_id, decoded_event)
|
||||||
.await;
|
.await;
|
||||||
@@ -672,6 +683,10 @@ impl NonTradeEventMaterializationService {
|
|||||||
"poolState",
|
"poolState",
|
||||||
"pool_state",
|
"pool_state",
|
||||||
"poolAccount",
|
"poolAccount",
|
||||||
|
"bondingCurve",
|
||||||
|
"bonding_curve",
|
||||||
|
"sharingConfig",
|
||||||
|
"sharing_config",
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
let related_mint = extract_first_string(
|
let related_mint = extract_first_string(
|
||||||
@@ -737,9 +752,8 @@ impl NonTradeEventMaterializationService {
|
|||||||
Some(decoded_event_id) => decoded_event_id,
|
Some(decoded_event_id) => decoded_event_id,
|
||||||
None => return Ok(false),
|
None => return Ok(false),
|
||||||
};
|
};
|
||||||
let context = self
|
let context =
|
||||||
.resolve_liquidity_context(transaction, transaction_id, decoded_event)
|
self.resolve_liquidity_context(transaction, transaction_id, decoded_event).await;
|
||||||
.await;
|
|
||||||
let context = match context {
|
let context = match context {
|
||||||
Ok(context) => context,
|
Ok(context) => context,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
@@ -1018,9 +1032,8 @@ impl NonTradeEventMaterializationService {
|
|||||||
Some(decoded_event_id) => decoded_event_id,
|
Some(decoded_event_id) => decoded_event_id,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
};
|
};
|
||||||
let payload_result = serde_json::from_str::<serde_json::Value>(
|
let payload_result =
|
||||||
decoded_event.payload_json.as_str(),
|
serde_json::from_str::<serde_json::Value>(decoded_event.payload_json.as_str());
|
||||||
);
|
|
||||||
let mut object = match payload_result {
|
let mut object = match payload_result {
|
||||||
Ok(serde_json::Value::Object(object)) => object,
|
Ok(serde_json::Value::Object(object)) => object,
|
||||||
Ok(other) => {
|
Ok(other) => {
|
||||||
@@ -1179,9 +1192,8 @@ impl NonTradeEventMaterializationService {
|
|||||||
Ok(decoded_events) => decoded_events,
|
Ok(decoded_events) => decoded_events,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
};
|
};
|
||||||
let target_payload_result = serde_json::from_str::<serde_json::Value>(
|
let target_payload_result =
|
||||||
decoded_event.payload_json.as_str(),
|
serde_json::from_str::<serde_json::Value>(decoded_event.payload_json.as_str());
|
||||||
);
|
|
||||||
let target_payload = match target_payload_result {
|
let target_payload = match target_payload_result {
|
||||||
Ok(target_payload) => target_payload,
|
Ok(target_payload) => target_payload,
|
||||||
Err(_) => serde_json::Value::Object(serde_json::Map::new()),
|
Err(_) => serde_json::Value::Object(serde_json::Map::new()),
|
||||||
@@ -1193,9 +1205,8 @@ impl NonTradeEventMaterializationService {
|
|||||||
if !candidate.event_kind.starts_with("raydium_clmm.") {
|
if !candidate.event_kind.starts_with("raydium_clmm.") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let candidate_payload_result = serde_json::from_str::<serde_json::Value>(
|
let candidate_payload_result =
|
||||||
candidate.payload_json.as_str(),
|
serde_json::from_str::<serde_json::Value>(candidate.payload_json.as_str());
|
||||||
);
|
|
||||||
let candidate_payload = match candidate_payload_result {
|
let candidate_payload = match candidate_payload_result {
|
||||||
Ok(candidate_payload) => candidate_payload,
|
Ok(candidate_payload) => candidate_payload,
|
||||||
Err(_) => serde_json::Value::Object(serde_json::Map::new()),
|
Err(_) => serde_json::Value::Object(serde_json::Map::new()),
|
||||||
@@ -1425,9 +1436,8 @@ struct MaterializationAccountKeyInfo {
|
|||||||
fn token_mints_by_account_from_transaction(
|
fn token_mints_by_account_from_transaction(
|
||||||
transaction: &crate::ChainTransactionDto,
|
transaction: &crate::ChainTransactionDto,
|
||||||
) -> std::collections::HashMap<std::string::String, std::string::String> {
|
) -> std::collections::HashMap<std::string::String, std::string::String> {
|
||||||
let transaction_json = serde_json::from_str::<serde_json::Value>(
|
let transaction_json =
|
||||||
transaction.transaction_json.as_str(),
|
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
|
||||||
);
|
|
||||||
let transaction_json = match transaction_json {
|
let transaction_json = match transaction_json {
|
||||||
Ok(transaction_json) => transaction_json,
|
Ok(transaction_json) => transaction_json,
|
||||||
Err(_) => return std::collections::HashMap::new(),
|
Err(_) => return std::collections::HashMap::new(),
|
||||||
@@ -1475,10 +1485,7 @@ fn materialization_account_keys(
|
|||||||
value.get("pubkey").and_then(serde_json::Value::as_str).map(str::to_string)
|
value.get("pubkey").and_then(serde_json::Value::as_str).map(str::to_string)
|
||||||
};
|
};
|
||||||
if let Some(address) = address {
|
if let Some(address) = address {
|
||||||
account_keys.push(MaterializationAccountKeyInfo {
|
account_keys.push(MaterializationAccountKeyInfo { index: index as i64, address });
|
||||||
index: index as i64,
|
|
||||||
address,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
@@ -1507,10 +1514,7 @@ fn append_materialization_loaded_addresses(
|
|||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
let index = account_keys.len() as i64;
|
let index = account_keys.len() as i64;
|
||||||
account_keys.push(MaterializationAccountKeyInfo {
|
account_keys.push(MaterializationAccountKeyInfo { index, address: address.to_string() });
|
||||||
index,
|
|
||||||
address: address.to_string(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1567,21 +1571,15 @@ fn infer_raydium_clmm_pair_mints_from_payload_accounts(
|
|||||||
Some(accounts) => accounts,
|
Some(accounts) => accounts,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
let instruction_name = payload
|
let instruction_name = payload.get("instructionName").and_then(serde_json::Value::as_str);
|
||||||
.get("instructionName")
|
|
||||||
.and_then(serde_json::Value::as_str);
|
|
||||||
let instruction_name = match instruction_name {
|
let instruction_name = match instruction_name {
|
||||||
Some(instruction_name) => instruction_name,
|
Some(instruction_name) => instruction_name,
|
||||||
None => "",
|
None => "",
|
||||||
};
|
};
|
||||||
let candidate_pairs = raydium_clmm_token_account_candidate_pairs(instruction_name);
|
let candidate_pairs = raydium_clmm_token_account_candidate_pairs(instruction_name);
|
||||||
for pair in candidate_pairs {
|
for pair in candidate_pairs {
|
||||||
let inferred = infer_mints_from_account_pair(
|
let inferred =
|
||||||
accounts,
|
infer_mints_from_account_pair(accounts, pair.0, pair.1, token_mints_by_account);
|
||||||
pair.0,
|
|
||||||
pair.1,
|
|
||||||
token_mints_by_account,
|
|
||||||
);
|
|
||||||
if let Some(inferred) = inferred {
|
if let Some(inferred) = inferred {
|
||||||
return Some(inferred);
|
return Some(inferred);
|
||||||
}
|
}
|
||||||
@@ -1610,7 +1608,17 @@ fn raydium_clmm_token_account_candidate_pairs(
|
|||||||
if instruction_name == "increase_liquidity_v2" {
|
if instruction_name == "increase_liquidity_v2" {
|
||||||
return vec![(13, 14), (9, 10), (7, 8)];
|
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)];
|
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(
|
fn infer_mints_from_account_pair(
|
||||||
@@ -1732,6 +1740,21 @@ fn extract_account_string(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_launchpad_launch_event_materializable(event_kind: &str) -> bool {
|
fn is_launchpad_launch_event_materializable(event_kind: &str) -> bool {
|
||||||
|
if event_kind.contains("pump_fun.create_v2_token") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if event_kind == "pump_fun.create" || event_kind == "pump_fun.create_event" {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if event_kind == "pump_fun.migrate"
|
||||||
|
|| event_kind == "pump_fun.migrate_v2"
|
||||||
|
|| event_kind == "pump_fun.migrate_bonding_curve_creator"
|
||||||
|
|| event_kind == "pump_fun.migrate_bonding_curve_creator_event"
|
||||||
|
|| event_kind == "pump_fun.complete_event"
|
||||||
|
|| event_kind == "pump_fun.complete_pump_amm_migration_event"
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if event_kind.contains("raydium_launchpad.buy_exact_in") {
|
if event_kind.contains("raydium_launchpad.buy_exact_in") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1793,6 +1816,17 @@ fn launchpad_launch_event_role(event_kind: &str) -> std::string::String {
|
|||||||
if event_kind.contains("migrate_to_cpswap") {
|
if event_kind.contains("migrate_to_cpswap") {
|
||||||
return "migration_to_cpswap".to_string();
|
return "migration_to_cpswap".to_string();
|
||||||
}
|
}
|
||||||
|
if event_kind.contains("pump_fun.migrate")
|
||||||
|
|| event_kind.contains("pump_fun.complete_pump_amm_migration")
|
||||||
|
{
|
||||||
|
return "pump_fun_migration".to_string();
|
||||||
|
}
|
||||||
|
if event_kind.contains("pump_fun.complete_event") {
|
||||||
|
return "pump_fun_completion".to_string();
|
||||||
|
}
|
||||||
|
if event_kind.contains("pump_fun.create") {
|
||||||
|
return "pump_fun_launch".to_string();
|
||||||
|
}
|
||||||
return "launch".to_string();
|
return "launch".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1938,7 +1972,108 @@ fn extract_first_bool(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_skip_pump_fun_duplicate_non_trade_event(
|
||||||
|
decoded_event: &crate::DexDecodedEventDto,
|
||||||
|
decoded_events: &[crate::DexDecodedEventDto],
|
||||||
|
) -> bool {
|
||||||
|
if !decoded_event.event_kind.starts_with("pump_fun.") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let preferred_siblings =
|
||||||
|
pump_fun_preferred_non_trade_siblings(decoded_event.event_kind.as_str());
|
||||||
|
if preferred_siblings.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for sibling in decoded_events {
|
||||||
|
if sibling.id == decoded_event.id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for preferred in &preferred_siblings {
|
||||||
|
if sibling.event_kind.as_str() == *preferred {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pump_fun_preferred_non_trade_siblings(event_kind: &str) -> std::vec::Vec<&'static str> {
|
||||||
|
match event_kind {
|
||||||
|
"pump_fun.admin_set_creator" => return vec!["pump_fun.admin_set_creator_event"],
|
||||||
|
"pump_fun.admin_set_idl_authority" => {
|
||||||
|
return vec!["pump_fun.admin_set_idl_authority_event"];
|
||||||
|
},
|
||||||
|
"pump_fun.admin_update_token_incentives" => {
|
||||||
|
return vec!["pump_fun.admin_update_token_incentives_event"];
|
||||||
|
},
|
||||||
|
"pump_fun.claim_cashback" | "pump_fun.claim_cashback_v2" => {
|
||||||
|
return vec!["pump_fun.claim_cashback_event"];
|
||||||
|
},
|
||||||
|
"pump_fun.claim_token_incentives" => return vec!["pump_fun.claim_token_incentives_event"],
|
||||||
|
"pump_fun.close_user_volume_accumulator" => {
|
||||||
|
return vec!["pump_fun.close_user_volume_accumulator_event"];
|
||||||
|
},
|
||||||
|
"pump_fun.collect_creator_fee" | "pump_fun.collect_creator_fee_v2" => {
|
||||||
|
return vec!["pump_fun.collect_creator_fee_event"];
|
||||||
|
},
|
||||||
|
"pump_fun.create" => return vec!["pump_fun.create_v2_token", "pump_fun.create_event"],
|
||||||
|
"pump_fun.create_event" => return vec!["pump_fun.create_v2_token"],
|
||||||
|
"pump_fun.distribute_creator_fees" | "pump_fun.distribute_creator_fees_v2" => {
|
||||||
|
return vec!["pump_fun.distribute_creator_fees_event"];
|
||||||
|
},
|
||||||
|
"pump_fun.extend_account" => return vec!["pump_fun.extend_account_event"],
|
||||||
|
"pump_fun.get_minimum_distributable_fee" => {
|
||||||
|
return vec!["pump_fun.minimum_distributable_fee_event"];
|
||||||
|
},
|
||||||
|
"pump_fun.init_user_volume_accumulator" => {
|
||||||
|
return vec!["pump_fun.init_user_volume_accumulator_event"];
|
||||||
|
},
|
||||||
|
"pump_fun.migrate_bonding_curve_creator" => {
|
||||||
|
return vec!["pump_fun.migrate_bonding_curve_creator_event"];
|
||||||
|
},
|
||||||
|
"pump_fun.set_creator" => return vec!["pump_fun.set_creator_event"],
|
||||||
|
"pump_fun.set_metaplex_creator" => return vec!["pump_fun.set_metaplex_creator_event"],
|
||||||
|
"pump_fun.set_params" => return vec!["pump_fun.set_params_event"],
|
||||||
|
"pump_fun.set_reserved_fee_recipients" => {
|
||||||
|
return vec!["pump_fun.reserved_fee_recipients_event"];
|
||||||
|
},
|
||||||
|
"pump_fun.sync_user_volume_accumulator" => {
|
||||||
|
return vec!["pump_fun.sync_user_volume_accumulator_event"];
|
||||||
|
},
|
||||||
|
"pump_fun.update_global_authority" => {
|
||||||
|
return vec!["pump_fun.update_global_authority_event"];
|
||||||
|
},
|
||||||
|
"pump_fun.set_mayhem_virtual_params" => {
|
||||||
|
return vec!["pump_fun.update_mayhem_virtual_params_event"];
|
||||||
|
},
|
||||||
|
_ => return std::vec::Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_pump_fun_payload(payload: &serde_json::Value) -> bool {
|
||||||
|
if let Some(object) = payload.as_object() {
|
||||||
|
let protocol_name = object.get("protocolName").and_then(serde_json::Value::as_str);
|
||||||
|
if protocol_name == Some("pump_fun") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let decoder = object.get("decoder").and_then(serde_json::Value::as_str);
|
||||||
|
if decoder == Some("pump_fun") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let event_kind = object.get("eventKind").and_then(serde_json::Value::as_str);
|
||||||
|
if let Some(event_kind) = event_kind {
|
||||||
|
if event_kind.starts_with("pump_fun.") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
fn is_anchor_event_audit_only(payload: &serde_json::Value) -> bool {
|
fn is_anchor_event_audit_only(payload: &serde_json::Value) -> bool {
|
||||||
|
if is_pump_fun_payload(payload) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if let Some(object) = payload.as_object() {
|
if let Some(object) = payload.as_object() {
|
||||||
let flag = object.get("anchorEventAuditOnly");
|
let flag = object.get("anchorEventAuditOnly");
|
||||||
if let Some(flag) = flag {
|
if let Some(flag) = flag {
|
||||||
@@ -1946,6 +2081,12 @@ fn is_anchor_event_audit_only(payload: &serde_json::Value) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let flag = object.get("instructionAuditOnly");
|
||||||
|
if let Some(flag) = flag {
|
||||||
|
if flag.as_bool() == Some(true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,18 @@ impl TradeAggregationService {
|
|||||||
if !crate::is_dex_trade_event_kind(decoded_event.event_kind.as_str()) {
|
if !crate::is_dex_trade_event_kind(decoded_event.event_kind.as_str()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if crate::trade_aggregation::should_skip_pump_fun_duplicate_trade_event(
|
||||||
|
decoded_event,
|
||||||
|
&decoded_events,
|
||||||
|
) {
|
||||||
|
tracing::debug!(
|
||||||
|
event_kind = %decoded_event.event_kind,
|
||||||
|
decoded_event_id = ?decoded_event.id,
|
||||||
|
transaction_signature = %transaction.signature,
|
||||||
|
"skipping duplicate pump_fun trade_event because an instruction trade exists"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let event_context =
|
let event_context =
|
||||||
crate::trade_aggregation_context::load_trade_aggregation_decoded_event_context(
|
crate::trade_aggregation_context::load_trade_aggregation_decoded_event_context(
|
||||||
self.database.as_ref(),
|
self.database.as_ref(),
|
||||||
@@ -200,6 +212,68 @@ impl TradeAggregationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_skip_pump_fun_duplicate_trade_event(
|
||||||
|
decoded_event: &crate::DexDecodedEventDto,
|
||||||
|
decoded_events: &[crate::DexDecodedEventDto],
|
||||||
|
) -> bool {
|
||||||
|
if decoded_event.event_kind.as_str() != "pump_fun.trade_event" {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let trade_instruction_id = pump_fun_payload_instruction_id(decoded_event.payload_json.as_str());
|
||||||
|
for sibling in decoded_events {
|
||||||
|
if sibling.id == decoded_event.id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !is_direct_materialized_pump_fun_instruction_trade_kind(sibling.event_kind.as_str()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let sibling_instruction_id = pump_fun_payload_instruction_id(sibling.payload_json.as_str());
|
||||||
|
if trade_instruction_id.is_some()
|
||||||
|
&& sibling_instruction_id.is_some()
|
||||||
|
&& trade_instruction_id != sibling_instruction_id
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_direct_materialized_pump_fun_instruction_trade_kind(event_kind: &str) -> bool {
|
||||||
|
match event_kind {
|
||||||
|
"pump_fun.buy" => return true,
|
||||||
|
"pump_fun.sell" => return true,
|
||||||
|
"pump_fun.buy_exact_sol_in" => return true,
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pump_fun_payload_instruction_id(payload_json: &str) -> std::option::Option<i64> {
|
||||||
|
let parsed_result = serde_json::from_str::<serde_json::Value>(payload_json);
|
||||||
|
let parsed = match parsed_result {
|
||||||
|
Ok(parsed) => parsed,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
let object = match parsed.as_object() {
|
||||||
|
Some(object) => object,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
let value = match object.get("instructionId") {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
if let Some(number) = value.as_i64() {
|
||||||
|
return Some(number);
|
||||||
|
}
|
||||||
|
if let Some(text) = value.as_str() {
|
||||||
|
let parsed_number = text.parse::<i64>();
|
||||||
|
match parsed_number {
|
||||||
|
Ok(parsed_number) => return Some(parsed_number),
|
||||||
|
Err(_) => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
fn transaction_has_effective_error(transaction: &crate::ChainTransactionDto) -> bool {
|
fn transaction_has_effective_error(transaction: &crate::ChainTransactionDto) -> bool {
|
||||||
let err_json = match transaction.err_json.as_ref() {
|
let err_json = match transaction.err_json.as_ref() {
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ pub(crate) async fn resolve_trade_amounts(
|
|||||||
&mut base_amount_raw,
|
&mut base_amount_raw,
|
||||||
&mut quote_amount_raw,
|
&mut quote_amount_raw,
|
||||||
&mut price_quote_per_base,
|
&mut price_quote_per_base,
|
||||||
);
|
)
|
||||||
|
.await;
|
||||||
if let Err(error) = resolution_result {
|
if let Err(error) = resolution_result {
|
||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
@@ -788,7 +789,7 @@ fn apply_raydium_launchpad_side_amount_mapping(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_pump_fun_amount_fallback(
|
async fn apply_pump_fun_amount_fallback(
|
||||||
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
||||||
base_amount_raw: &mut std::option::Option<std::string::String>,
|
base_amount_raw: &mut std::option::Option<std::string::String>,
|
||||||
quote_amount_raw: &mut std::option::Option<std::string::String>,
|
quote_amount_raw: &mut std::option::Option<std::string::String>,
|
||||||
@@ -813,9 +814,183 @@ fn apply_pump_fun_amount_fallback(
|
|||||||
if price_quote_per_base.is_none() {
|
if price_quote_per_base.is_none() {
|
||||||
*price_quote_per_base = inferred.2;
|
*price_quote_per_base = inferred.2;
|
||||||
}
|
}
|
||||||
|
if base_amount_raw.is_none() || quote_amount_raw.is_none() || price_quote_per_base.is_none() {
|
||||||
|
let sibling_result = crate::trade_amount_resolution::apply_pump_fun_trade_event_sibling_amount_fallback(
|
||||||
|
input,
|
||||||
|
base_amount_raw,
|
||||||
|
quote_amount_raw,
|
||||||
|
price_quote_per_base,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
if let Err(error) = sibling_result {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn apply_pump_fun_trade_event_sibling_amount_fallback(
|
||||||
|
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
||||||
|
base_amount_raw: &mut std::option::Option<std::string::String>,
|
||||||
|
quote_amount_raw: &mut std::option::Option<std::string::String>,
|
||||||
|
price_quote_per_base: &mut std::option::Option<f64>,
|
||||||
|
) -> Result<(), crate::Error> {
|
||||||
|
if !crate::trade_amount_resolution::pump_fun_instruction_trade_can_use_trade_event_fallback(
|
||||||
|
input.decoded_event.event_kind.as_str(),
|
||||||
|
) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let sibling_events_result = crate::query_dex_decoded_events_list_by_transaction_id(
|
||||||
|
input.database,
|
||||||
|
input.decoded_event.transaction_id,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let sibling_events = match sibling_events_result {
|
||||||
|
Ok(sibling_events) => sibling_events,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
for sibling_event in sibling_events {
|
||||||
|
if sibling_event.id == input.decoded_event.id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if sibling_event.protocol_name.as_str() != "pump_fun" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if sibling_event.event_kind.as_str() != "pump_fun.trade_event" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let sibling_payload_result =
|
||||||
|
serde_json::from_str::<serde_json::Value>(sibling_event.payload_json.as_str());
|
||||||
|
let sibling_payload = match sibling_payload_result {
|
||||||
|
Ok(sibling_payload) => sibling_payload,
|
||||||
|
Err(error) => {
|
||||||
|
tracing::debug!(
|
||||||
|
decoded_event_id = ?sibling_event.id,
|
||||||
|
error = %error,
|
||||||
|
"cannot parse pump_fun trade_event sibling payload for amount fallback"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if !crate::trade_amount_resolution::pump_fun_trade_event_sibling_matches_instruction(
|
||||||
|
input.decoded_event.event_kind.as_str(),
|
||||||
|
input.payload,
|
||||||
|
&sibling_payload,
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let sibling_base_amount = crate::trade_amount_resolution::extract_amount_string(
|
||||||
|
&sibling_payload,
|
||||||
|
&["baseAmountRaw", "baseAmount", "token_amount", "tokenAmount"],
|
||||||
|
);
|
||||||
|
let sibling_quote_amount = crate::trade_amount_resolution::extract_amount_string(
|
||||||
|
&sibling_payload,
|
||||||
|
&["quoteAmountRaw", "quoteAmount", "sol_amount", "solAmount", "quote_amount"],
|
||||||
|
);
|
||||||
|
if base_amount_raw.is_none() {
|
||||||
|
*base_amount_raw = sibling_base_amount;
|
||||||
|
}
|
||||||
|
if quote_amount_raw.is_none() {
|
||||||
|
*quote_amount_raw = sibling_quote_amount;
|
||||||
|
}
|
||||||
|
if price_quote_per_base.is_none() {
|
||||||
|
*price_quote_per_base = crate::trade_metric_update::compute_price_quote_per_base_from_raw_amounts_with_decimals(
|
||||||
|
base_amount_raw.as_deref(),
|
||||||
|
quote_amount_raw.as_deref(),
|
||||||
|
input.base_token_decimals,
|
||||||
|
input.quote_token_decimals,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
tracing::debug!(
|
||||||
|
event_kind = %input.decoded_event.event_kind,
|
||||||
|
decoded_event_id = ?input.decoded_event.id,
|
||||||
|
sibling_decoded_event_id = ?sibling_event.id,
|
||||||
|
base_amount_raw = ?base_amount_raw,
|
||||||
|
quote_amount_raw = ?quote_amount_raw,
|
||||||
|
price_quote_per_base = ?price_quote_per_base,
|
||||||
|
"pump_fun instruction amounts recovered from sibling trade_event"
|
||||||
|
);
|
||||||
|
if base_amount_raw.is_some() && quote_amount_raw.is_some() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pump_fun_instruction_trade_can_use_trade_event_fallback(event_kind: &str) -> bool {
|
||||||
|
match event_kind {
|
||||||
|
"pump_fun.buy_exact_quote_in_v2" => return true,
|
||||||
|
"pump_fun.buy_exact_sol_in" => return true,
|
||||||
|
"pump_fun.buy_v2" => return true,
|
||||||
|
"pump_fun.sell_v2" => return true,
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pump_fun_trade_event_sibling_matches_instruction(
|
||||||
|
instruction_event_kind: &str,
|
||||||
|
instruction_payload: &serde_json::Value,
|
||||||
|
trade_event_payload: &serde_json::Value,
|
||||||
|
) -> bool {
|
||||||
|
let expected_is_buy = match instruction_event_kind {
|
||||||
|
"pump_fun.buy_exact_quote_in_v2" => Some(true),
|
||||||
|
"pump_fun.buy_exact_sol_in" => Some(true),
|
||||||
|
"pump_fun.buy_v2" => Some(true),
|
||||||
|
"pump_fun.sell_v2" => Some(false),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(expected_is_buy) = expected_is_buy {
|
||||||
|
let actual_is_buy = crate::trade_amount_resolution::extract_bool_by_candidate_keys(
|
||||||
|
trade_event_payload,
|
||||||
|
&["is_buy", "isBuy"],
|
||||||
|
);
|
||||||
|
match actual_is_buy {
|
||||||
|
Some(actual_is_buy) if actual_is_buy == expected_is_buy => {},
|
||||||
|
Some(_) => return false,
|
||||||
|
None => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let instruction_mint = crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||||
|
instruction_payload,
|
||||||
|
&["mint", "tokenMint", "tokenAMint"],
|
||||||
|
);
|
||||||
|
let trade_event_mint = crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||||
|
trade_event_payload,
|
||||||
|
&["mint", "tokenMint", "tokenAMint"],
|
||||||
|
);
|
||||||
|
if !crate::trade_amount_resolution::optional_string_values_match(
|
||||||
|
instruction_mint.as_deref(),
|
||||||
|
trade_event_mint.as_deref(),
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let instruction_user = crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||||
|
instruction_payload,
|
||||||
|
&["user", "actorWallet"],
|
||||||
|
);
|
||||||
|
let trade_event_user = crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||||
|
trade_event_payload,
|
||||||
|
&["user", "actorWallet"],
|
||||||
|
);
|
||||||
|
if !crate::trade_amount_resolution::optional_string_values_match(
|
||||||
|
instruction_user.as_deref(),
|
||||||
|
trade_event_user.as_deref(),
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn optional_string_values_match(
|
||||||
|
left: std::option::Option<&str>,
|
||||||
|
right: std::option::Option<&str>,
|
||||||
|
) -> bool {
|
||||||
|
match (left, right) {
|
||||||
|
(Some(left), Some(right)) => return left == right,
|
||||||
|
_ => return true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn apply_raydium_instruction_amount_fallback(
|
async fn apply_raydium_instruction_amount_fallback(
|
||||||
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
||||||
base_amount_raw: &mut std::option::Option<std::string::String>,
|
base_amount_raw: &mut std::option::Option<std::string::String>,
|
||||||
@@ -1492,6 +1667,44 @@ fn extract_amount_string(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_bool_by_candidate_keys(
|
||||||
|
value: &serde_json::Value,
|
||||||
|
candidate_keys: &[&str],
|
||||||
|
) -> std::option::Option<bool> {
|
||||||
|
if let Some(object) = value.as_object() {
|
||||||
|
for candidate_key in candidate_keys {
|
||||||
|
let direct_option = object.get(*candidate_key);
|
||||||
|
if let Some(direct) = direct_option {
|
||||||
|
if let Some(bool_value) = direct.as_bool() {
|
||||||
|
return Some(bool_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for nested_value in object.values() {
|
||||||
|
let nested_result = crate::trade_amount_resolution::extract_bool_by_candidate_keys(
|
||||||
|
nested_value,
|
||||||
|
candidate_keys,
|
||||||
|
);
|
||||||
|
if nested_result.is_some() {
|
||||||
|
return nested_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let Some(array) = value.as_array() {
|
||||||
|
for nested_value in array {
|
||||||
|
let nested_result = crate::trade_amount_resolution::extract_bool_by_candidate_keys(
|
||||||
|
nested_value,
|
||||||
|
candidate_keys,
|
||||||
|
);
|
||||||
|
if nested_result.is_some() {
|
||||||
|
return nested_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
fn extract_string_by_candidate_keys(
|
fn extract_string_by_candidate_keys(
|
||||||
value: &serde_json::Value,
|
value: &serde_json::Value,
|
||||||
candidate_keys: &[&str],
|
candidate_keys: &[&str],
|
||||||
|
|||||||
@@ -11042,6 +11042,127 @@ pub(crate) const UPSTREAM_REGISTRY_ENTRIES: &[crate::UpstreamRegistryEntry] = &[
|
|||||||
8,
|
8,
|
||||||
"decoders/pumpfun-decoder/src/instructions/admin_update_token_incentives.rs",
|
"decoders/pumpfun-decoder/src/instructions/admin_update_token_incentives.rs",
|
||||||
),
|
),
|
||||||
|
manual_solscan_discriminator_entry(
|
||||||
|
"pump_fun",
|
||||||
|
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||||
|
"pump",
|
||||||
|
"launch",
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
"add_quote_mint",
|
||||||
|
"6f79153828185ed1",
|
||||||
|
8,
|
||||||
|
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||||
|
),
|
||||||
|
manual_solscan_discriminator_entry(
|
||||||
|
"pump_fun",
|
||||||
|
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||||
|
"pump",
|
||||||
|
"launch",
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
"buy_exact_quote_in_v2",
|
||||||
|
"c2ab1c46684d5b2f",
|
||||||
|
8,
|
||||||
|
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||||
|
),
|
||||||
|
manual_solscan_discriminator_entry(
|
||||||
|
"pump_fun",
|
||||||
|
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||||
|
"pump",
|
||||||
|
"launch",
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
"buy_v2",
|
||||||
|
"b817ee6167c5d33d",
|
||||||
|
8,
|
||||||
|
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||||
|
),
|
||||||
|
manual_solscan_discriminator_entry(
|
||||||
|
"pump_fun",
|
||||||
|
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||||
|
"pump",
|
||||||
|
"launch",
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
"claim_cashback_v2",
|
||||||
|
"7af3cc415e741d37",
|
||||||
|
8,
|
||||||
|
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||||
|
),
|
||||||
|
manual_solscan_discriminator_entry(
|
||||||
|
"pump_fun",
|
||||||
|
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||||
|
"pump",
|
||||||
|
"launch",
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
"collect_creator_fee_v2",
|
||||||
|
"cf118af204221338",
|
||||||
|
8,
|
||||||
|
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||||
|
),
|
||||||
|
manual_solscan_discriminator_entry(
|
||||||
|
"pump_fun",
|
||||||
|
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||||
|
"pump",
|
||||||
|
"launch",
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
"distribute_creator_fees_v2",
|
||||||
|
"ffcb134ff444089f",
|
||||||
|
8,
|
||||||
|
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||||
|
),
|
||||||
|
manual_solscan_discriminator_entry(
|
||||||
|
"pump_fun",
|
||||||
|
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||||
|
"pump",
|
||||||
|
"launch",
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
"migrate_v2",
|
||||||
|
"bbcb121fceedfe29",
|
||||||
|
8,
|
||||||
|
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||||
|
),
|
||||||
|
manual_solscan_discriminator_entry(
|
||||||
|
"pump_fun",
|
||||||
|
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||||
|
"pump",
|
||||||
|
"launch",
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
"remove_quote_mint",
|
||||||
|
"b141df2658d19e9b",
|
||||||
|
8,
|
||||||
|
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||||
|
),
|
||||||
|
manual_solscan_discriminator_entry(
|
||||||
|
"pump_fun",
|
||||||
|
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||||
|
"pump",
|
||||||
|
"launch",
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
"sell_v2",
|
||||||
|
"5df6823ce7e940b2",
|
||||||
|
8,
|
||||||
|
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||||
|
),
|
||||||
|
manual_solscan_discriminator_entry(
|
||||||
|
"pump_fun",
|
||||||
|
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||||
|
"pump",
|
||||||
|
"launch",
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
"set_virtual_quote_reserves",
|
||||||
|
"6587bf6809581460",
|
||||||
|
8,
|
||||||
|
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||||
|
),
|
||||||
|
manual_solscan_discriminator_entry(
|
||||||
|
"pump_fun",
|
||||||
|
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||||
|
"pump",
|
||||||
|
"launch",
|
||||||
|
crate::ENTRY_KIND_INSTRUCTION,
|
||||||
|
"update_buyback_config",
|
||||||
|
"fbe0ab92a01a71e9",
|
||||||
|
8,
|
||||||
|
"idls/pump_fun.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.json",
|
||||||
|
),
|
||||||
upstream_git_discriminator_entry(
|
upstream_git_discriminator_entry(
|
||||||
"pump_fun",
|
"pump_fun",
|
||||||
Some(crate::PUMP_FUN_PROGRAM_ID),
|
Some(crate::PUMP_FUN_PROGRAM_ID),
|
||||||
|
|||||||
456
validation_sql/SQL_VALIDATION_PUMP_FUN_0_7_54.sql
Normal file
456
validation_sql/SQL_VALIDATION_PUMP_FUN_0_7_54.sql
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
-- file: validation_sql/SQL_VALIDATION_PUMP_FUN_0_7_54.sql
|
||||||
|
|
||||||
|
-- 0.7.54 pump_fun validation and corpus-seed checklist.
|
||||||
|
-- Run on a dedicated fresh SQLite database for the Pump.fun tranche.
|
||||||
|
-- Recommended replay settings after each backfill group:
|
||||||
|
-- skipDexDecode=no, forceDexDecode=yes, deferInstructionObservations=yes.
|
||||||
|
-- This file is intentionally read-only: it never mutates the database.
|
||||||
|
|
||||||
|
-- 00. Corpus seed: upstream fallback samples to backfill first.
|
||||||
|
SELECT
|
||||||
|
json_extract(de.payload_json, '$.upstreamEntryName') AS upstream_entry_name,
|
||||||
|
json_extract(de.payload_json, '$.upstreamDiscriminatorHex') AS upstream_discriminator_hex,
|
||||||
|
COUNT(*) AS fallback_count,
|
||||||
|
COUNT(DISTINCT de.transaction_id) AS tx_count,
|
||||||
|
MIN(tx.signature) AS sample_signature
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
LEFT JOIN k_sol_chain_transactions tx
|
||||||
|
ON tx.id = de.transaction_id
|
||||||
|
WHERE de.protocol_name = 'upstream_git'
|
||||||
|
AND de.event_kind = 'upstream_git.instruction_match'
|
||||||
|
AND json_extract(de.payload_json, '$.upstreamDecoderCode') = 'pump_fun'
|
||||||
|
GROUP BY upstream_entry_name, upstream_discriminator_hex
|
||||||
|
ORDER BY fallback_count DESC, upstream_entry_name, upstream_discriminator_hex;
|
||||||
|
|
||||||
|
-- 01. Corpus seed: local instruction observations.
|
||||||
|
SELECT
|
||||||
|
instruction_name,
|
||||||
|
discriminator_hex,
|
||||||
|
COUNT(*) AS observed_count,
|
||||||
|
COUNT(DISTINCT signature) AS tx_count,
|
||||||
|
MIN(signature) AS sample_signature
|
||||||
|
FROM k_sol_instruction_observations
|
||||||
|
WHERE decoder_code = 'pump_fun'
|
||||||
|
GROUP BY instruction_name, discriminator_hex
|
||||||
|
ORDER BY observed_count DESC, instruction_name, discriminator_hex;
|
||||||
|
|
||||||
|
-- 02. Coverage pump_fun.
|
||||||
|
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 = 'pump_fun'
|
||||||
|
ORDER BY entry_kind, entry_name, discriminator_hex;
|
||||||
|
|
||||||
|
-- 03. Decoded events summary.
|
||||||
|
SELECT
|
||||||
|
de.event_kind,
|
||||||
|
COUNT(*) AS decoded_count,
|
||||||
|
COUNT(DISTINCT de.transaction_id) AS tx_count,
|
||||||
|
MIN(tx.signature) AS sample_signature
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
LEFT JOIN k_sol_chain_transactions tx
|
||||||
|
ON tx.id = de.transaction_id
|
||||||
|
WHERE de.protocol_name = 'pump_fun'
|
||||||
|
GROUP BY de.event_kind
|
||||||
|
ORDER BY decoded_count DESC, de.event_kind;
|
||||||
|
|
||||||
|
-- 04. Decoded pump_fun events without coverage.
|
||||||
|
-- Target after closure: empty for all locally decoded pump_fun rows.
|
||||||
|
SELECT
|
||||||
|
de.event_kind,
|
||||||
|
COUNT(*) AS decoded_count,
|
||||||
|
COUNT(DISTINCT de.transaction_id) AS tx_count,
|
||||||
|
MIN(tx.signature) AS sample_signature
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
LEFT 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 = 'pump_fun'
|
||||||
|
AND ce.local_event_kind = de.event_kind
|
||||||
|
WHERE de.protocol_name = 'pump_fun'
|
||||||
|
AND ce.id IS NULL
|
||||||
|
GROUP BY de.event_kind
|
||||||
|
ORDER BY decoded_count DESC, de.event_kind;
|
||||||
|
|
||||||
|
-- 05. Residual upstream fallback for covered local entries.
|
||||||
|
-- Target after local promotion: empty for every entry that has a local_event_kind.
|
||||||
|
SELECT
|
||||||
|
json_extract(ug.payload_json, '$.upstreamEntryName') AS upstream_entry_name,
|
||||||
|
json_extract(ug.payload_json, '$.upstreamDiscriminatorHex') AS upstream_discriminator_hex,
|
||||||
|
json_extract(ug.payload_json, '$.upstreamSourceRepo') AS source_repo,
|
||||||
|
ce.local_event_kind,
|
||||||
|
ce.expected_db_target,
|
||||||
|
ce.proof_status,
|
||||||
|
COUNT(*) AS fallback_count,
|
||||||
|
COUNT(DISTINCT ug.transaction_id) AS tx_count,
|
||||||
|
MIN(tx.signature) AS sample_signature
|
||||||
|
FROM k_sol_dex_decoded_events ug
|
||||||
|
LEFT JOIN k_sol_chain_transactions tx
|
||||||
|
ON tx.id = ug.transaction_id
|
||||||
|
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') = 'pump_fun'
|
||||||
|
GROUP BY upstream_entry_name, upstream_discriminator_hex, source_repo, ce.local_event_kind, ce.expected_db_target, ce.proof_status
|
||||||
|
ORDER BY fallback_count DESC, upstream_entry_name;
|
||||||
|
|
||||||
|
-- 06. Successful non-materialized events without explicit skip reason.
|
||||||
|
-- Target after closure: empty, or documented exceptions with explicit skip reason in payload_json.
|
||||||
|
SELECT
|
||||||
|
de.event_kind,
|
||||||
|
COUNT(*) AS unexplained_count,
|
||||||
|
MIN(tx.signature) AS sample_signature
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_chain_transactions tx
|
||||||
|
ON tx.id = de.transaction_id
|
||||||
|
LEFT JOIN k_sol_trade_events te
|
||||||
|
ON te.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_launch_events lae
|
||||||
|
ON lae.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_liquidity_events lie
|
||||||
|
ON lie.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_lifecycle_events ple
|
||||||
|
ON ple.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_fee_events fee
|
||||||
|
ON fee.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_reward_events rew
|
||||||
|
ON rew.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_admin_events adm
|
||||||
|
ON adm.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_orderbook_events obe
|
||||||
|
ON obe.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_token_account_events tae
|
||||||
|
ON tae.decoded_event_id = de.id
|
||||||
|
WHERE de.protocol_name = 'pump_fun'
|
||||||
|
AND (
|
||||||
|
tx.err_json IS NULL
|
||||||
|
OR tx.err_json = ''
|
||||||
|
OR tx.err_json = 'null'
|
||||||
|
)
|
||||||
|
AND te.id IS NULL
|
||||||
|
AND lae.id IS NULL
|
||||||
|
AND lie.id IS NULL
|
||||||
|
AND ple.id IS NULL
|
||||||
|
AND fee.id IS NULL
|
||||||
|
AND rew.id IS NULL
|
||||||
|
AND adm.id IS NULL
|
||||||
|
AND obe.id IS NULL
|
||||||
|
AND tae.id IS NULL
|
||||||
|
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipTradeReason')), '') = ''
|
||||||
|
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipCandleReason')), '') = ''
|
||||||
|
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipLiquidityReason')), '') = ''
|
||||||
|
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipLifecycleReason')), '') = ''
|
||||||
|
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipCatalogReason')), '') = ''
|
||||||
|
GROUP BY de.event_kind
|
||||||
|
ORDER BY unexplained_count DESC, de.event_kind;
|
||||||
|
|
||||||
|
-- 07. Failed transaction materialization safety.
|
||||||
|
-- Target after closure: empty. Failed transactions may be decoded for audit, but must not be business-materialized.
|
||||||
|
SELECT
|
||||||
|
de.event_kind,
|
||||||
|
COUNT(DISTINCT de.id) AS decoded_failed_count,
|
||||||
|
COUNT(DISTINCT te.id) AS trade_count,
|
||||||
|
COUNT(DISTINCT lae.id) AS launch_count,
|
||||||
|
COUNT(DISTINCT lie.id) AS liquidity_count,
|
||||||
|
COUNT(DISTINCT ple.id) AS lifecycle_count,
|
||||||
|
COUNT(DISTINCT fee.id) AS fee_count,
|
||||||
|
COUNT(DISTINCT rew.id) AS reward_count,
|
||||||
|
COUNT(DISTINCT adm.id) AS admin_count,
|
||||||
|
COUNT(DISTINCT obe.id) AS orderbook_count,
|
||||||
|
COUNT(DISTINCT tae.id) AS token_account_count,
|
||||||
|
MIN(tx.signature) AS sample_signature
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_chain_transactions tx
|
||||||
|
ON tx.id = de.transaction_id
|
||||||
|
LEFT JOIN k_sol_trade_events te
|
||||||
|
ON te.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_launch_events lae
|
||||||
|
ON lae.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_liquidity_events lie
|
||||||
|
ON lie.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_lifecycle_events ple
|
||||||
|
ON ple.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_fee_events fee
|
||||||
|
ON fee.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_reward_events rew
|
||||||
|
ON rew.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_admin_events adm
|
||||||
|
ON adm.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_orderbook_events obe
|
||||||
|
ON obe.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_token_account_events tae
|
||||||
|
ON tae.decoded_event_id = de.id
|
||||||
|
WHERE de.protocol_name = 'pump_fun'
|
||||||
|
AND tx.err_json IS NOT NULL
|
||||||
|
AND tx.err_json <> ''
|
||||||
|
AND tx.err_json <> 'null'
|
||||||
|
GROUP BY de.event_kind
|
||||||
|
HAVING trade_count > 0
|
||||||
|
OR launch_count > 0
|
||||||
|
OR liquidity_count > 0
|
||||||
|
OR lifecycle_count > 0
|
||||||
|
OR fee_count > 0
|
||||||
|
OR reward_count > 0
|
||||||
|
OR admin_count > 0
|
||||||
|
OR orderbook_count > 0
|
||||||
|
OR token_account_count > 0
|
||||||
|
ORDER BY de.event_kind;
|
||||||
|
|
||||||
|
-- 08. Multi-target materialization safety.
|
||||||
|
-- Target after closure: empty. One decoded event must not feed multiple business targets.
|
||||||
|
SELECT
|
||||||
|
de.event_kind,
|
||||||
|
COUNT(DISTINCT de.id) AS decoded_count,
|
||||||
|
COUNT(DISTINCT te.id) AS trade_count,
|
||||||
|
COUNT(DISTINCT lae.id) AS launch_count,
|
||||||
|
COUNT(DISTINCT lie.id) AS liquidity_count,
|
||||||
|
COUNT(DISTINCT ple.id) AS lifecycle_count,
|
||||||
|
COUNT(DISTINCT fee.id) AS fee_count,
|
||||||
|
COUNT(DISTINCT rew.id) AS reward_count,
|
||||||
|
COUNT(DISTINCT adm.id) AS admin_count,
|
||||||
|
COUNT(DISTINCT obe.id) AS orderbook_count,
|
||||||
|
COUNT(DISTINCT tae.id) AS token_account_count,
|
||||||
|
(
|
||||||
|
CASE WHEN COUNT(DISTINCT te.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
+ CASE WHEN COUNT(DISTINCT lae.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
+ CASE WHEN COUNT(DISTINCT lie.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
+ CASE WHEN COUNT(DISTINCT ple.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
+ CASE WHEN COUNT(DISTINCT fee.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
+ CASE WHEN COUNT(DISTINCT rew.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
+ CASE WHEN COUNT(DISTINCT adm.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
+ CASE WHEN COUNT(DISTINCT obe.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
+ CASE WHEN COUNT(DISTINCT tae.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
) AS materialized_target_count
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
LEFT JOIN k_sol_trade_events te
|
||||||
|
ON te.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_launch_events lae
|
||||||
|
ON lae.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_liquidity_events lie
|
||||||
|
ON lie.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_lifecycle_events ple
|
||||||
|
ON ple.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_fee_events fee
|
||||||
|
ON fee.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_reward_events rew
|
||||||
|
ON rew.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_admin_events adm
|
||||||
|
ON adm.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_orderbook_events obe
|
||||||
|
ON obe.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_token_account_events tae
|
||||||
|
ON tae.decoded_event_id = de.id
|
||||||
|
WHERE de.protocol_name = 'pump_fun'
|
||||||
|
GROUP BY de.event_kind
|
||||||
|
HAVING materialized_target_count > 1
|
||||||
|
ORDER BY materialized_target_count DESC, de.event_kind;
|
||||||
|
|
||||||
|
-- 09. Materialization summary.
|
||||||
|
SELECT
|
||||||
|
de.event_kind,
|
||||||
|
COUNT(DISTINCT de.id) AS decoded_count,
|
||||||
|
COUNT(DISTINCT te.id) AS trade_count,
|
||||||
|
COUNT(DISTINCT lae.id) AS launch_count,
|
||||||
|
COUNT(DISTINCT lie.id) AS liquidity_count,
|
||||||
|
COUNT(DISTINCT ple.id) AS lifecycle_count,
|
||||||
|
COUNT(DISTINCT fee.id) AS fee_count,
|
||||||
|
COUNT(DISTINCT rew.id) AS reward_count,
|
||||||
|
COUNT(DISTINCT adm.id) AS admin_count,
|
||||||
|
COUNT(DISTINCT obe.id) AS orderbook_count,
|
||||||
|
COUNT(DISTINCT tae.id) AS token_account_count
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
LEFT JOIN k_sol_trade_events te
|
||||||
|
ON te.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_launch_events lae
|
||||||
|
ON lae.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_liquidity_events lie
|
||||||
|
ON lie.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_lifecycle_events ple
|
||||||
|
ON ple.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_fee_events fee
|
||||||
|
ON fee.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_reward_events rew
|
||||||
|
ON rew.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_admin_events adm
|
||||||
|
ON adm.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_orderbook_events obe
|
||||||
|
ON obe.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_token_account_events tae
|
||||||
|
ON tae.decoded_event_id = de.id
|
||||||
|
WHERE de.protocol_name = 'pump_fun'
|
||||||
|
GROUP BY de.event_kind
|
||||||
|
ORDER BY de.event_kind;
|
||||||
|
|
||||||
|
-- 10. Instruction observation versus coverage.
|
||||||
|
-- Target after closure: every observed non-transport discriminator is covered or documented.
|
||||||
|
WITH normalized_io AS (
|
||||||
|
SELECT
|
||||||
|
io.decoder_code,
|
||||||
|
io.instruction_name,
|
||||||
|
CASE
|
||||||
|
WHEN io.instruction_name LIKE 'pump_fun.%'
|
||||||
|
THEN SUBSTR(io.instruction_name, LENGTH('pump_fun') + 2)
|
||||||
|
ELSE io.instruction_name
|
||||||
|
END AS normalized_entry_name,
|
||||||
|
io.discriminator_hex,
|
||||||
|
io.signature
|
||||||
|
FROM k_sol_instruction_observations io
|
||||||
|
WHERE io.decoder_code = 'pump_fun'
|
||||||
|
AND io.discriminator_hex IS NOT NULL
|
||||||
|
AND io.discriminator_hex <> ''
|
||||||
|
AND io.discriminator_hex <> 'e445a52e51cb9a1d'
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
nio.instruction_name,
|
||||||
|
nio.normalized_entry_name,
|
||||||
|
nio.discriminator_hex,
|
||||||
|
COUNT(*) AS observed_count,
|
||||||
|
COUNT(DISTINCT nio.signature) AS tx_count,
|
||||||
|
MIN(nio.signature) AS sample_signature,
|
||||||
|
CASE
|
||||||
|
WHEN ce.id IS NULL THEN 'coverage_gap'
|
||||||
|
ELSE 'covered'
|
||||||
|
END AS observation_coverage_status,
|
||||||
|
ce.local_event_kind,
|
||||||
|
ce.expected_db_target,
|
||||||
|
ce.proof_status
|
||||||
|
FROM normalized_io nio
|
||||||
|
LEFT JOIN k_sol_dex_event_coverage_entries ce
|
||||||
|
ON ce.decoder_code = 'pump_fun'
|
||||||
|
AND COALESCE(ce.discriminator_hex, '') = COALESCE(nio.discriminator_hex, '')
|
||||||
|
AND (
|
||||||
|
COALESCE(TRIM(nio.instruction_name), '') = ''
|
||||||
|
OR ce.entry_name = nio.instruction_name
|
||||||
|
OR ce.entry_name = nio.normalized_entry_name
|
||||||
|
OR ce.local_event_kind = nio.instruction_name
|
||||||
|
OR ce.local_event_kind = ('pump_fun.' || nio.normalized_entry_name)
|
||||||
|
)
|
||||||
|
GROUP BY
|
||||||
|
nio.instruction_name,
|
||||||
|
nio.normalized_entry_name,
|
||||||
|
nio.discriminator_hex,
|
||||||
|
observation_coverage_status,
|
||||||
|
ce.local_event_kind,
|
||||||
|
ce.expected_db_target,
|
||||||
|
ce.proof_status
|
||||||
|
ORDER BY observed_count DESC, nio.instruction_name, nio.discriminator_hex;
|
||||||
|
|
||||||
|
-- 11. Pump.fun successful trade candidates without materialized trade.
|
||||||
|
-- Target after closure: only rows with explicit skipTradeReason when exact amounts/direction are not proven.
|
||||||
|
SELECT
|
||||||
|
de.event_kind,
|
||||||
|
json_extract(de.payload_json, '$.amountSource') AS amount_source,
|
||||||
|
json_extract(de.payload_json, '$.skipTradeReason') AS skip_trade_reason,
|
||||||
|
COUNT(*) AS decoded_count,
|
||||||
|
COUNT(te.id) AS trade_count,
|
||||||
|
MIN(tx.signature) AS sample_signature
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_chain_transactions tx
|
||||||
|
ON tx.id = de.transaction_id
|
||||||
|
LEFT JOIN k_sol_trade_events te
|
||||||
|
ON te.decoded_event_id = de.id
|
||||||
|
WHERE de.protocol_name = 'pump_fun'
|
||||||
|
AND de.event_kind IN (
|
||||||
|
'pump_fun.buy',
|
||||||
|
'pump_fun.sell',
|
||||||
|
'pump_fun.buy_v2',
|
||||||
|
'pump_fun.sell_v2',
|
||||||
|
'pump_fun.buy_exact_sol_in',
|
||||||
|
'pump_fun.buy_exact_quote_in_v2',
|
||||||
|
'pump_fun.trade_event'
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
tx.err_json IS NULL
|
||||||
|
OR tx.err_json = ''
|
||||||
|
OR tx.err_json = 'null'
|
||||||
|
)
|
||||||
|
GROUP BY de.event_kind, amount_source, skip_trade_reason
|
||||||
|
HAVING COUNT(te.id) = 0
|
||||||
|
AND COALESCE(TRIM(skip_trade_reason), '') = ''
|
||||||
|
ORDER BY decoded_count DESC, de.event_kind, amount_source;
|
||||||
|
|
||||||
|
-- 12. Global watchlist after pump_fun replay.
|
||||||
|
-- Expected after local promotion: pump_fun rows should no longer dominate this list unless explicitly deferred.
|
||||||
|
SELECT
|
||||||
|
json_extract(de.payload_json, '$.upstreamDecoderCode') AS upstream_decoder_code,
|
||||||
|
json_extract(de.payload_json, '$.upstreamEntryName') AS upstream_entry_name,
|
||||||
|
json_extract(de.payload_json, '$.upstreamDiscriminatorHex') AS upstream_discriminator_hex,
|
||||||
|
COUNT(*) AS decoded_count,
|
||||||
|
COUNT(DISTINCT de.transaction_id) AS tx_count,
|
||||||
|
MIN(tx.signature) AS sample_signature
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
LEFT JOIN k_sol_chain_transactions tx
|
||||||
|
ON tx.id = de.transaction_id
|
||||||
|
WHERE de.protocol_name = 'upstream_git'
|
||||||
|
AND de.event_kind = 'upstream_git.instruction_match'
|
||||||
|
GROUP BY upstream_decoder_code, upstream_entry_name, upstream_discriminator_hex
|
||||||
|
ORDER BY decoded_count DESC, upstream_decoder_code, upstream_entry_name;
|
||||||
|
|
||||||
|
-- 13. Pump.fun Solscan-IDL-only instruction coverage.
|
||||||
|
-- Target after the first Rust delta: all rows below must be covered after coverage sync.
|
||||||
|
SELECT
|
||||||
|
ce.entry_name,
|
||||||
|
ce.discriminator_hex,
|
||||||
|
ce.source_repo,
|
||||||
|
ce.source_path,
|
||||||
|
ce.local_event_kind,
|
||||||
|
ce.expected_db_target,
|
||||||
|
ce.proof_status,
|
||||||
|
ce.observed_count,
|
||||||
|
ce.materialized_count,
|
||||||
|
ce.trade_count
|
||||||
|
FROM k_sol_dex_event_coverage_entries ce
|
||||||
|
WHERE ce.decoder_code = 'pump_fun'
|
||||||
|
AND ce.source_repo = 'manual-solscan'
|
||||||
|
ORDER BY ce.entry_name;
|
||||||
|
|
||||||
|
|
||||||
|
-- 14. Pump.fun Anchor event coverage local kind check.
|
||||||
|
-- Target after full decoder delta: every Pump.fun event registry row has a local_event_kind.
|
||||||
|
SELECT
|
||||||
|
ce.entry_name,
|
||||||
|
ce.discriminator_hex,
|
||||||
|
ce.local_event_kind,
|
||||||
|
ce.expected_db_target,
|
||||||
|
ce.proof_status,
|
||||||
|
ce.observed_count,
|
||||||
|
ce.materialized_count
|
||||||
|
FROM k_sol_dex_event_coverage_entries ce
|
||||||
|
WHERE ce.decoder_code = 'pump_fun'
|
||||||
|
AND ce.entry_kind = 'event'
|
||||||
|
AND (
|
||||||
|
ce.local_event_kind IS NULL
|
||||||
|
OR TRIM(ce.local_event_kind) = ''
|
||||||
|
)
|
||||||
|
ORDER BY ce.entry_name;
|
||||||
|
|
||||||
|
-- 15. Pump.fun decoded Anchor events summary.
|
||||||
|
-- Informational: real corpus may be empty until an Anchor event log/self-CPI appears.
|
||||||
|
SELECT
|
||||||
|
de.event_kind,
|
||||||
|
json_extract(de.payload_json, '$.anchorEventName') AS anchor_event_name,
|
||||||
|
json_extract(de.payload_json, '$.anchorEventDiscriminatorHex') AS anchor_event_discriminator_hex,
|
||||||
|
COUNT(*) AS decoded_count,
|
||||||
|
COUNT(DISTINCT de.transaction_id) AS tx_count,
|
||||||
|
MIN(tx.signature) AS sample_signature
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
LEFT JOIN k_sol_chain_transactions tx
|
||||||
|
ON tx.id = de.transaction_id
|
||||||
|
WHERE de.protocol_name = 'pump_fun'
|
||||||
|
AND COALESCE(TRIM(json_extract(de.payload_json, '$.anchorEventName')), '') <> ''
|
||||||
|
GROUP BY de.event_kind, anchor_event_name, anchor_event_discriminator_hex
|
||||||
|
ORDER BY decoded_count DESC, de.event_kind;
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
-- Pump.fun 0.7.54 materialization follow-up validation.
|
||||||
|
-- Run after a forced replay with skipDexDecode=no, forceDexDecode=yes, deferInstructionObservations=yes.
|
||||||
|
|
||||||
|
-- 01. Materialization summary by decoded event kind.
|
||||||
|
SELECT
|
||||||
|
de.event_kind,
|
||||||
|
COUNT(DISTINCT de.id) AS decoded_count,
|
||||||
|
COUNT(DISTINCT te.id) AS trade_count,
|
||||||
|
COUNT(DISTINCT lae.id) AS launch_count,
|
||||||
|
COUNT(DISTINCT fee.id) AS fee_count,
|
||||||
|
COUNT(DISTINCT rew.id) AS reward_count,
|
||||||
|
COUNT(DISTINCT adm.id) AS admin_count,
|
||||||
|
COUNT(DISTINCT ple.id) AS lifecycle_count,
|
||||||
|
COUNT(DISTINCT tae.id) AS token_account_count,
|
||||||
|
MIN(tx.signature) AS sample_signature
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_chain_transactions tx ON tx.id = de.transaction_id
|
||||||
|
LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_launch_events lae ON lae.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_fee_events fee ON fee.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_reward_events rew ON rew.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_admin_events adm ON adm.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_lifecycle_events ple ON ple.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_token_account_events tae ON tae.decoded_event_id = de.id
|
||||||
|
WHERE de.protocol_name = 'pump_fun'
|
||||||
|
GROUP BY de.event_kind
|
||||||
|
ORDER BY de.event_kind;
|
||||||
|
|
||||||
|
-- 02. Hard safety: no materialization on failed transactions.
|
||||||
|
SELECT
|
||||||
|
de.event_kind,
|
||||||
|
COUNT(DISTINCT de.id) AS decoded_failed_count,
|
||||||
|
COUNT(DISTINCT te.id) AS trade_count,
|
||||||
|
COUNT(DISTINCT lae.id) AS launch_count,
|
||||||
|
COUNT(DISTINCT fee.id) AS fee_count,
|
||||||
|
COUNT(DISTINCT rew.id) AS reward_count,
|
||||||
|
COUNT(DISTINCT adm.id) AS admin_count,
|
||||||
|
COUNT(DISTINCT ple.id) AS lifecycle_count,
|
||||||
|
MIN(tx.signature) AS sample_signature
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_chain_transactions tx ON tx.id = de.transaction_id
|
||||||
|
LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_launch_events lae ON lae.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_fee_events fee ON fee.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_reward_events rew ON rew.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_admin_events adm ON adm.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_lifecycle_events ple ON ple.decoded_event_id = de.id
|
||||||
|
WHERE de.protocol_name = 'pump_fun'
|
||||||
|
AND tx.err_json IS NOT NULL
|
||||||
|
AND tx.err_json <> ''
|
||||||
|
AND tx.err_json <> 'null'
|
||||||
|
GROUP BY de.event_kind
|
||||||
|
HAVING trade_count > 0
|
||||||
|
OR launch_count > 0
|
||||||
|
OR fee_count > 0
|
||||||
|
OR reward_count > 0
|
||||||
|
OR admin_count > 0
|
||||||
|
OR lifecycle_count > 0
|
||||||
|
ORDER BY de.event_kind;
|
||||||
|
|
||||||
|
-- 03. Hard safety: one decoded event must not feed multiple business targets.
|
||||||
|
SELECT
|
||||||
|
de.event_kind,
|
||||||
|
COUNT(DISTINCT de.id) AS decoded_count,
|
||||||
|
COUNT(DISTINCT te.id) AS trade_count,
|
||||||
|
COUNT(DISTINCT lae.id) AS launch_count,
|
||||||
|
COUNT(DISTINCT fee.id) AS fee_count,
|
||||||
|
COUNT(DISTINCT rew.id) AS reward_count,
|
||||||
|
COUNT(DISTINCT adm.id) AS admin_count,
|
||||||
|
COUNT(DISTINCT ple.id) AS lifecycle_count,
|
||||||
|
(
|
||||||
|
CASE WHEN COUNT(DISTINCT te.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
+ CASE WHEN COUNT(DISTINCT lae.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
+ CASE WHEN COUNT(DISTINCT fee.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
+ CASE WHEN COUNT(DISTINCT rew.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
+ CASE WHEN COUNT(DISTINCT adm.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
+ CASE WHEN COUNT(DISTINCT ple.id) > 0 THEN 1 ELSE 0 END
|
||||||
|
) AS target_count
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_launch_events lae ON lae.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_fee_events fee ON fee.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_reward_events rew ON rew.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_admin_events adm ON adm.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_lifecycle_events ple ON ple.decoded_event_id = de.id
|
||||||
|
WHERE de.protocol_name = 'pump_fun'
|
||||||
|
GROUP BY de.event_kind
|
||||||
|
HAVING target_count > 1
|
||||||
|
ORDER BY target_count DESC, de.event_kind;
|
||||||
|
|
||||||
|
-- 04. Trade duplicate safety: do not materialize trade_event when an instruction trade in the same tx was materialized.
|
||||||
|
WITH materialized_pump_fun_trades AS (
|
||||||
|
SELECT
|
||||||
|
tx.signature,
|
||||||
|
de.event_kind,
|
||||||
|
COUNT(te.id) AS trade_count
|
||||||
|
FROM k_sol_trade_events te
|
||||||
|
JOIN k_sol_dex_decoded_events de ON de.id = te.decoded_event_id
|
||||||
|
JOIN k_sol_chain_transactions tx ON tx.id = de.transaction_id
|
||||||
|
WHERE de.protocol_name = 'pump_fun'
|
||||||
|
GROUP BY tx.signature, de.event_kind
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
signature,
|
||||||
|
SUM(CASE WHEN event_kind = 'pump_fun.trade_event' THEN trade_count ELSE 0 END) AS trade_event_materialized,
|
||||||
|
SUM(CASE WHEN event_kind IN (
|
||||||
|
'pump_fun.buy',
|
||||||
|
'pump_fun.sell',
|
||||||
|
'pump_fun.buy_v2',
|
||||||
|
'pump_fun.sell_v2',
|
||||||
|
'pump_fun.buy_exact_sol_in',
|
||||||
|
'pump_fun.buy_exact_quote_in_v2'
|
||||||
|
) THEN trade_count ELSE 0 END) AS instruction_trade_materialized
|
||||||
|
FROM materialized_pump_fun_trades
|
||||||
|
GROUP BY signature
|
||||||
|
HAVING trade_event_materialized > 0
|
||||||
|
AND instruction_trade_materialized > 0
|
||||||
|
ORDER BY signature;
|
||||||
|
|
||||||
|
-- 05. Residual successful materializable rows without a business target and without skip reason.
|
||||||
|
SELECT
|
||||||
|
de.event_kind,
|
||||||
|
COUNT(*) AS unexplained_count,
|
||||||
|
MIN(tx.signature) AS sample_signature
|
||||||
|
FROM k_sol_dex_decoded_events de
|
||||||
|
JOIN k_sol_chain_transactions tx ON tx.id = de.transaction_id
|
||||||
|
LEFT JOIN k_sol_trade_events te ON te.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_launch_events lae ON lae.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_fee_events fee ON fee.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_reward_events rew ON rew.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_admin_events adm ON adm.decoded_event_id = de.id
|
||||||
|
LEFT JOIN k_sol_pool_lifecycle_events ple ON ple.decoded_event_id = de.id
|
||||||
|
WHERE de.protocol_name = 'pump_fun'
|
||||||
|
AND (tx.err_json IS NULL OR tx.err_json = '' OR tx.err_json = 'null')
|
||||||
|
AND te.id IS NULL
|
||||||
|
AND lae.id IS NULL
|
||||||
|
AND fee.id IS NULL
|
||||||
|
AND rew.id IS NULL
|
||||||
|
AND adm.id IS NULL
|
||||||
|
AND ple.id IS NULL
|
||||||
|
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipTradeReason')), '') = ''
|
||||||
|
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipCandleReason')), '') = ''
|
||||||
|
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipLifecycleReason')), '') = ''
|
||||||
|
AND COALESCE(TRIM(json_extract(de.payload_json, '$.skipCatalogReason')), '') = ''
|
||||||
|
GROUP BY de.event_kind
|
||||||
|
ORDER BY unexplained_count DESC, de.event_kind;
|
||||||
Reference in New Issue
Block a user