pump_swap correction.
This commit is contained in:
@@ -90,12 +90,12 @@ Documents de référence :
|
||||
docs/reports/METEORA_DLMM_EVENT_COVERAGE_REPORT.md
|
||||
docs/VALIDATION_STATUS_0_7_57_FINAL.md
|
||||
validation_sql/SQL_VALIDATION_METEORA_DLMM_0_7_57.sql
|
||||
docs/prompts/PROMPT_0_7_58_DEMO4_PROGRAM_SURFACE_DISCOVERY.md
|
||||
docs/prompts/PROMPT_0_7_59_SQLITE_DB_TRANSACTION_MERGER_BINARY.md
|
||||
docs/prompts/PROMPT_0_7_58_SQLITE_DB_TRANSACTION_MERGER_BINARY.md
|
||||
docs/prompts/PROMPT_0_7_59_DEMO4_PROGRAM_SURFACE_DISCOVERY.md
|
||||
docs/prompts/PROMPT_0_7_60_METEORA_DAMM_NEXT_DEX.md
|
||||
```
|
||||
|
||||
Prochaine étape recommandée : `0.7.58 demo4 / program surface discovery`, une fonctionnalité d'affichage et de scoring des contenus non encore pris en compte (`program_id`, discriminators, Anchor logs/events, upstream fallback), sans matérialisation automatique et sans promotion automatique dans les decoders.
|
||||
Prochaine étape recommandée : `0.7.58 sqlite_db_transaction_merger`, un binaire de fusion de bases SQLite transactionnelles pour construire `final.db`/`final.next.db` et lancer des replays anti-régression cross-DEX Pump/Raydium/Meteora. `demo4 / program surface discovery` est déplacé en `0.7.59` et exploitera cette base consolidée sans matérialisation automatique ni promotion automatique.
|
||||
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ validation_sql/SQL_VALIDATION_METEORA_DBC_0_7_56.sql
|
||||
docs/prompts/PROMPT_0_7_57_METEORA_DLMM_FULL_DECODE_MATERIALIZATION.md
|
||||
```
|
||||
|
||||
La tranche suivante `0.7.57 meteora_dlmm` est désormais clôturée. La prochaine étape recommandée est `0.7.58 demo4 / program surface discovery`, puis `0.7.59` pour le binaire de consolidation de bases SQLite.
|
||||
La tranche suivante `0.7.57 meteora_dlmm` est désormais clôturée. La prochaine étape recommandée est `0.7.58 sqlite_db_transaction_merger`, afin de construire `final.db`/`final.next.db` par merge de bases dédiées et de rejouer la base consolidée pour détecter les régressions cross-DEX. `0.7.59` est réservé à `demo4 / program surface discovery`.
|
||||
|
||||
## État final validé `0.7.55` — `pump_fees`
|
||||
|
||||
|
||||
26
ROADMAP.md
26
ROADMAP.md
@@ -2,7 +2,7 @@
|
||||
|
||||
# Roadmap — khadhroony-bobobot
|
||||
|
||||
## État courant — clôture `0.7.57 meteora_dlmm` et plan intermédiaire `demo4` / consolidation DB
|
||||
## État courant — clôture `0.7.57 meteora_dlmm` et plan intermédiaire consolidation DB / demo4
|
||||
|
||||
### `0.7.57 meteora_dlmm` — clos
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
### Politique de découverte automatique future
|
||||
|
||||
La suite ne doit pas matérialiser automatiquement des contenus inconnus. La future `demo4` doit seulement afficher, scorer et regrouper les surfaces non encore prises en compte pour permettre une programmation manuelle ou semi-automatique contrôlée.
|
||||
La suite ajoute d'abord le binaire de fusion SQLite en `0.7.58`, puis `demo4` en `0.7.59`. La future `demo4` ne doit pas matérialiser automatiquement des contenus inconnus : elle doit seulement afficher, scorer et regrouper les surfaces non encore prises en compte pour permettre une programmation manuelle ou semi-automatique contrôlée.
|
||||
|
||||
À observer sans promotion automatique :
|
||||
|
||||
@@ -34,14 +34,16 @@ La suite ne doit pas matérialiser automatiquement des contenus inconnus. La fut
|
||||
|
||||
| Priorité | Tranche | Surface | Objectif |
|
||||
|---:|---|---|---|
|
||||
| 1 | `0.7.58` | `demo4_program_surface_discovery` | UI/queries de découverte des contenus non pris en compte : aucun auto-decode métier, aucune auto-materialization, aucune auto-promotion. |
|
||||
| 2 | `0.7.59` | `sqlite_db_transaction_merger` | Nouveau module binaire utilisant `kb_lib` pour fusionner plusieurs bases SQLite de corpus existants dans une base output, sans rescanner/backfill les signatures déjà disponibles. |
|
||||
| 1 | `0.7.58` | `sqlite_db_transaction_merger` | Binaire de fusion de bases SQLite transactionnelles vers `final.db` / `final.next.db`, sans RPC/backfill/décodage pendant le merge, puis replay anti-régression cross-DEX Pump/Raydium/Meteora. |
|
||||
| 2 | `0.7.59` | `demo4_program_surface_discovery` | UI/queries de découverte des contenus non pris en compte depuis la base consolidée : aucun auto-decode métier, aucune auto-materialization, aucune auto-promotion. |
|
||||
| 3 | `0.7.60` | `meteora_damm_v1` | Clôture séparée DAMM v1 : pools, swaps, liquidity, lock, fees/admin, coverage et matérialisation complète. |
|
||||
| 4 | `0.7.61` | `meteora_damm_v2` | Clôture séparée DAMM v2 : create/custom pools, swaps, liquidity, dynamic config, fees/admin. |
|
||||
| 5 | `0.7.62` | `meteora_vault` | Vault deposit/withdraw/fee/accounting ; pas de candle directe. |
|
||||
|
||||
Décision de planification : ne pas fusionner DAMM v1 et DAMM v2 dans une seule tranche. Ils peuvent partager des helpers, des tests et des conventions de matérialisation, mais les `program_id`, IDL, discriminants et corpus doivent rester séparés afin de garder les validations SQL lisibles et de réduire les risques de faux positif.
|
||||
|
||||
Décision anti-régression : pour les futurs développements de décodeurs/materializers, travailler d'abord sur une base vide dédiée au DEX, puis fusionner cette base dans une copie de `final.db` avec le binaire `0.7.58`. La clôture d'une tranche DEX doit inclure un replay de la base fusionnée afin de détecter les régressions cross-surface, par exemple un decoder PumpFees qui interrompt une matérialisation PumpSwap dans la même transaction.
|
||||
|
||||
## 0.7.47-1FE5 — Décision de planification : ne plus viser “tous les events en une session”
|
||||
|
||||
La phase `0.7.47` a montré que l’objectif “réimplémenter tous les décodeurs Carbon et toutes les sources en un seul bloc” est trop large. Le plan est donc redécoupé en **un DEX/version par tranche**, avec une matrice documentaire dédiée : `docs/DEX_DECODER_MATRIX.md`.
|
||||
@@ -82,8 +84,8 @@ Les comptes non-programmes (`platform_config`, token authority, comptes de confi
|
||||
| `0.7.55` | `pump_fees` | `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` | Pump / fee | **Clos** : `29` instructions, `20` events Anchor, fee/reward/admin/lifecycle, `get_fees` decoded-only, failed tx audit-only, aucun trade/candle direct. |
|
||||
| `0.7.56` | `meteora_dbc` | `dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN` | Meteora / DBC | Clos : `28` instructions et `23` events Anchor couverts, swaps `swap/swap2`, lifecycle/admin/fees, `k_sol_fee_event_amounts`, validations SQL propres. |
|
||||
| `0.7.57` | `meteora_dlmm` | `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo` | Meteora / DLMM | **Clos** : full decode + full materialization ; `76` instructions IDL, `30` events Anchor, swaps, bins, positions, liquidity, fees/rewards/admin/limit-order, sans double-count. |
|
||||
| `0.7.58` | `demo4_program_surface_discovery` | n/a | Outillage / discovery | Afficher les contenus non pris en compte : program ids, discriminators, Anchor logs/events, upstream fallbacks, layouts et samples ; aucune matérialisation automatique. |
|
||||
| `0.7.59` | `sqlite_db_transaction_merger` | n/a | Outillage / consolidation DB | Ajouter un binaire utilisant `kb_lib` pour fusionner plusieurs `.db` de corpus existants vers une base output sans RPC/backfill. |
|
||||
| `0.7.58` | `sqlite_db_transaction_merger` | n/a | Outillage / consolidation DB | Ajouter un binaire utilisant `kb_lib` pour fusionner des bases `.db` transactionnelles vers `final.db`/`final.next.db`, puis rejouer la base consolidée pour détecter les régressions cross-DEX Pump/Raydium/Meteora. |
|
||||
| `0.7.59` | `demo4_program_surface_discovery` | n/a | Outillage / discovery | Afficher depuis la base consolidée les contenus non pris en compte : program ids, discriminators, Anchor logs/events, upstream fallbacks, layouts et samples ; aucune matérialisation automatique. |
|
||||
| `0.7.60` | `meteora_damm_v1` | `Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB` | Meteora / DAMM v1 | Parité upstream finale : pools, swaps, liquidity, lock, fees/admin. |
|
||||
| `0.7.61` | `meteora_damm_v2` | `cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG` | Meteora / DAMM v2 | Couverture complète : create/custom pools, swaps, liquidity, dynamic config, fees/admin. |
|
||||
| `0.7.62` | `meteora_vault` | `24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi` | Meteora / vault | Vault deposit/withdraw/fee/accounting ; pas de candle directe. |
|
||||
@@ -1437,8 +1439,8 @@ Objectif : maintenir un phasage lisible après la clôture `0.7.57 meteora_dlmm`
|
||||
|
||||
Décisions actives :
|
||||
|
||||
- `0.7.58` est réservé à `demo4_program_surface_discovery` : affichage et revue de surfaces non prises en compte, sans matérialisation automatique.
|
||||
- `0.7.59` est réservé au binaire de consolidation de bases SQLite : fusion de corpus transactionnels déjà présents, sans RPC/backfill.
|
||||
- `0.7.58` est réservé au binaire de consolidation de bases SQLite : fusion de corpus transactionnels déjà présents, sans RPC/backfill, puis replay `final.db`/`final.next.db` pour non-régression cross-DEX.
|
||||
- `0.7.59` est réservé à `demo4_program_surface_discovery` : affichage et revue de surfaces non prises en compte depuis la base consolidée, sans matérialisation automatique.
|
||||
- `0.7.60` reprend le travail DEX avec `meteora_damm_v1`.
|
||||
- `0.7.61` cible `meteora_damm_v2`.
|
||||
- `0.7.62` cible `meteora_vault`.
|
||||
@@ -1451,8 +1453,8 @@ Décisions actives :
|
||||
| `0.7.55` | `pump_fees` | `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` | Pump / fee | **Clos** : `29` instructions, `20` events Anchor, fee/reward/admin/lifecycle, `get_fees` decoded-only, failed tx audit-only, aucun trade/candle direct. |
|
||||
| `0.7.56` | `meteora_dbc` | `dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN` | Meteora / DBC | Clos : `28` instructions et `23` events Anchor couverts, swaps `swap/swap2`, lifecycle/admin/fees, `k_sol_fee_event_amounts`, validations SQL propres. |
|
||||
| `0.7.57` | `meteora_dlmm` | `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo` | Meteora / DLMM | **Clos** : full decode + full materialization ; `76` instructions IDL, `30` events Anchor, swaps, bins, positions, liquidity, fees/rewards/admin/limit-order, sans double-count. |
|
||||
| `0.7.58` | `demo4_program_surface_discovery` | n/a | Outillage / discovery | Afficher les contenus non pris en compte : program ids, discriminators, Anchor logs/events, upstream fallbacks, layouts et samples ; aucune matérialisation automatique. |
|
||||
| `0.7.59` | `sqlite_db_transaction_merger` | n/a | Outillage / consolidation DB | Ajouter un binaire utilisant `kb_lib` pour fusionner plusieurs `.db` de corpus existants vers une base output sans RPC/backfill. |
|
||||
| `0.7.58` | `sqlite_db_transaction_merger` | n/a | Outillage / consolidation DB | Ajouter un binaire utilisant `kb_lib` pour fusionner des bases `.db` transactionnelles vers `final.db`/`final.next.db`, puis rejouer la base consolidée pour détecter les régressions cross-DEX Pump/Raydium/Meteora. |
|
||||
| `0.7.59` | `demo4_program_surface_discovery` | n/a | Outillage / discovery | Afficher depuis la base consolidée les contenus non pris en compte : program ids, discriminators, Anchor logs/events, upstream fallbacks, layouts et samples ; aucune matérialisation automatique. |
|
||||
| `0.7.60` | `meteora_damm_v1` | `Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB` | Meteora / DAMM v1 | Parité upstream finale : pools, swaps, liquidity, lock, fees/admin. |
|
||||
| `0.7.61` | `meteora_damm_v2` | `cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG` | Meteora / DAMM v2 | Couverture complète : create/custom pools, swaps, liquidity, dynamic config, fees/admin. |
|
||||
| `0.7.62` | `meteora_vault` | `24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi` | Meteora / vault | Vault deposit/withdraw/fee/accounting ; pas de candle directe. |
|
||||
@@ -1738,8 +1740,8 @@ Ordre de travail recommandé pour la suite :
|
||||
13. `0.7.55` : `pump_fees` — clos ;
|
||||
14. `0.7.56` : `meteora_dbc` — clos ;
|
||||
15. `0.7.57` : `meteora_dlmm` — clos ;
|
||||
16. `0.7.58` : `demo4_program_surface_discovery` — à faire ;
|
||||
17. `0.7.59` : `sqlite_db_transaction_merger` — à faire ;
|
||||
16. `0.7.58` : `sqlite_db_transaction_merger` + validation anti-régression Pump/Raydium/Meteora — à faire ;
|
||||
17. `0.7.59` : `demo4_program_surface_discovery` — à faire ;
|
||||
18. `0.7.60` : `meteora_damm_v1` — prochaine tranche DEX ;
|
||||
19. `0.7.61` : `meteora_damm_v2` — après DAMM v1 ;
|
||||
20. `0.7.62+` : appliquer le phasage actif défini en section `6.085`.
|
||||
|
||||
@@ -1,245 +1,18 @@
|
||||
<!-- file: docs/prompts/prompt_name_0_7_58_demo4_program_surface_discovery.md -->
|
||||
<!-- file: docs/prompts/PROMPT_0_7_58_demo4_program_surface_discovery.md -->
|
||||
|
||||
# Prompt — 0.7.58 Demo4 Program Surface Discovery
|
||||
# OBSOLETE — Prompt déplacé
|
||||
|
||||
## Contexte
|
||||
Ce prompt n'est plus la cible `0.7.58`.
|
||||
|
||||
Nous reprenons le workspace Rust/Tauri `khadhroony-bobobot` après la clôture de `0.7.57 meteora_dlmm`.
|
||||
|
||||
État validé :
|
||||
Nouvel ordre validé :
|
||||
|
||||
```text
|
||||
0.7.57 meteora_dlmm clos
|
||||
cargo test -p kb_lib -> 460 passed
|
||||
cargo clippy -p kb_lib --all-targets -- -D warnings -> OK
|
||||
replay -> 769 replayed, 106 trades, 664 liquidity, 1107 lifecycle, 424 candles
|
||||
instructionObservations = 8062
|
||||
catalogue = 169 tokens / 218 pools / 218 pairs
|
||||
0.7.58 -> sqlite_db_transaction_merger
|
||||
0.7.59 -> demo4_program_surface_discovery
|
||||
```
|
||||
|
||||
Contraintes projet à respecter :
|
||||
|
||||
- Rust 2024.
|
||||
- Code async-first.
|
||||
- Pas de `?`, `unwrap`, `expect` dans le code applicatif.
|
||||
- Pas de `anyhow`, pas de `thiserror`.
|
||||
- Pas de `mod.rs`.
|
||||
- Pas de `pub mod` ; utiliser `mod` privé + `pub use`.
|
||||
- Imports uniquement pour les traits quand c’est possible.
|
||||
- `#![deny(unreachable_pub)]` et `#![warn(missing_docs)]` doivent rester propres.
|
||||
- Tests offline.
|
||||
- Les nouvelles requêtes DB doivent mettre à jour les re-exports dans `kb_lib/src/db.rs` puis `kb_lib/src/lib.rs`.
|
||||
- Les structs DB doivent aller dans `kb_lib/src/db/entities/*.rs` et `kb_lib/src/db/dtos/*.rs`, pas dans les fichiers de requêtes.
|
||||
|
||||
## Objectif
|
||||
|
||||
Ajouter `Demo4` dans `kb_demo_app` et les fonctions de lecture associées dans `kb_lib` pour afficher les surfaces de programmes non encore prises en compte par le système.
|
||||
|
||||
Cette fonctionnalité est strictement un cockpit de découverte et de revue.
|
||||
|
||||
Elle ne doit pas :
|
||||
|
||||
- matérialiser automatiquement des contenus inconnus ;
|
||||
- promouvoir automatiquement des discriminators inconnus dans les décodeurs officiels ;
|
||||
- marquer automatiquement un `program_id` comme supporté ;
|
||||
- créer automatiquement des lignes `trade`, `candle`, `fee`, `liquidity`, `reward`, `admin` ou `orderbook` depuis un contenu inconnu.
|
||||
|
||||
Elle doit :
|
||||
|
||||
- afficher les surfaces inconnues ou non supportées ;
|
||||
- les regrouper de manière exploitable ;
|
||||
- montrer les preuves et signatures samples ;
|
||||
- aider à produire des prompts/patchs futurs validés manuellement.
|
||||
|
||||
## Comportement attendu côté utilisateur
|
||||
|
||||
Créer une nouvelle page ou fenêtre, selon le pattern existant :
|
||||
Utiliser :
|
||||
|
||||
```text
|
||||
kb_demo_app/src/demo4.html
|
||||
kb_demo_app/src/demo4.ts
|
||||
docs/prompts/PROMPT_0_7_59_demo4_program_surface_discovery.md
|
||||
```
|
||||
|
||||
ou l’équivalent si l’application utilise un autre routage.
|
||||
|
||||
Demo4 doit proposer des vues pour :
|
||||
|
||||
1. discriminators inconnus sur des decoders connus ;
|
||||
2. `program_id` connus contenant des instructions non mappées par le decoder spécialisé ;
|
||||
3. `upstream_git.instruction_match` groupés par `upstreamDecoderCode`, `upstreamEntryName`, `upstreamDiscriminatorHex` ;
|
||||
4. `program_id` observés dans les transactions mais absents de la matrice de support ;
|
||||
5. logs Anchor `Program log: Instruction: ...` non mappés localement ;
|
||||
6. events Anchor `Program data:` non mappés localement ;
|
||||
7. layouts de comptes récurrents sur instructions inconnues ;
|
||||
8. répartition success/failed pour chaque surface candidate.
|
||||
|
||||
Filtres souhaités :
|
||||
|
||||
```text
|
||||
program_id
|
||||
candidate_decoder_code
|
||||
discriminator_hex
|
||||
instruction_name / nom log Anchor
|
||||
upstream decoder
|
||||
upstream entry name
|
||||
success only / failed only / both
|
||||
minimum observation count
|
||||
minimum tx count
|
||||
from slot / to slot
|
||||
signature contains
|
||||
show only unknown
|
||||
show only upstream fallback
|
||||
show only high-confidence candidates
|
||||
```
|
||||
|
||||
Colonnes souhaitées :
|
||||
|
||||
```text
|
||||
candidate_kind
|
||||
program_id
|
||||
candidate_decoder
|
||||
known/unknown status
|
||||
discriminator_hex
|
||||
log_instruction_name
|
||||
anchor_event_discriminator
|
||||
account_count
|
||||
layout_hash
|
||||
observed_count
|
||||
tx_count
|
||||
success_count
|
||||
failed_count
|
||||
first_slot
|
||||
last_slot
|
||||
sample_signature
|
||||
confidence_score
|
||||
confidence_reason
|
||||
status
|
||||
```
|
||||
|
||||
Le panneau de détail doit afficher :
|
||||
|
||||
```text
|
||||
sample signatures
|
||||
accounts_json
|
||||
data_json / data prefix
|
||||
logs de transaction
|
||||
inner instructions
|
||||
parsed_json si disponible
|
||||
decoded_payload_json si disponible
|
||||
entrées upstream registry correspondantes
|
||||
preuve de hash Anchor si un nom d’instruction est disponible
|
||||
```
|
||||
|
||||
## Design `kb_lib`
|
||||
|
||||
Ajouter des requêtes read-only et des DTOs. Fichiers suggérés :
|
||||
|
||||
```text
|
||||
kb_lib/src/program_surface_discovery.rs
|
||||
kb_lib/src/db/dtos/program_surface_candidate_summary.rs
|
||||
kb_lib/src/db/dtos/program_surface_candidate_sample.rs
|
||||
kb_lib/src/db/queries/program_surface_discovery.rs
|
||||
```
|
||||
|
||||
Si un statut persistant est nécessaire, ajouter :
|
||||
|
||||
```text
|
||||
kb_lib/src/db/entities/program_surface_candidate.rs
|
||||
kb_lib/src/db/queries/program_surface_candidates.rs
|
||||
```
|
||||
|
||||
Ne pas placer les DTO/entities dans les fichiers de requêtes.
|
||||
|
||||
Statuts candidats suggérés :
|
||||
|
||||
```text
|
||||
new
|
||||
reviewed
|
||||
accepted
|
||||
rejected
|
||||
promoted
|
||||
ignored
|
||||
```
|
||||
|
||||
Si une table persistante est ajoutée, la migration doit être explicite, idempotente et testée.
|
||||
|
||||
## Scoring de découverte
|
||||
|
||||
Le scoring est indicatif uniquement. Il ne doit jamais déclencher une matérialisation.
|
||||
|
||||
Signaux de confiance possibles :
|
||||
|
||||
| Signal | Effet |
|
||||
|---|---:|
|
||||
| `program_id` connu, discriminator inconnu | +20 |
|
||||
| log Anchor `Instruction: X` trouvé | +30 |
|
||||
| `sha256("global:x")` correspond au discriminator | +50 |
|
||||
| observations success répétées | +10 |
|
||||
| account count/layout stable | +10 |
|
||||
| inner instruction family cohérente avec l’action | +10 |
|
||||
| uniquement des transactions failed | -20 |
|
||||
| nom très générique comme `swap`, `claim`, `initialize` sans autre preuve | -15 |
|
||||
| `program_id` inconnu et observation unique | -25 |
|
||||
|
||||
## Helper Anchor
|
||||
|
||||
Ajouter un helper qui normalise les noms d’instructions Anchor depuis les logs :
|
||||
|
||||
```text
|
||||
Program log: Instruction: InitializePresetParameterV2
|
||||
-> initialize_preset_parameter_v2
|
||||
-> sha256("global:initialize_preset_parameter_v2")[0..8]
|
||||
```
|
||||
|
||||
Ce helper doit seulement produire une preuve/candidate record. Il ne doit pas modifier les mappings de decoder.
|
||||
|
||||
## Export Markdown
|
||||
|
||||
Demo4 doit permettre d’exporter un groupe candidat en Markdown pour une future session d’implémentation.
|
||||
|
||||
L’export doit inclure :
|
||||
|
||||
```text
|
||||
candidate decoder
|
||||
program_id
|
||||
discriminator_hex
|
||||
candidate name
|
||||
confidence score
|
||||
preuves/evidence
|
||||
sample signatures
|
||||
account layout
|
||||
logs
|
||||
inner instruction summary
|
||||
proposed target table éventuelle
|
||||
warning explicite : non implémenté, non matérialisé
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Ajouter des tests offline pour :
|
||||
|
||||
- normalisation des noms Anchor depuis logs ;
|
||||
- calcul de discriminators Anchor pour des noms samples ;
|
||||
- groupement par program/discriminator/layout ;
|
||||
- ordre de score ;
|
||||
- sérialisation/désérialisation des DTOs si utilisés ;
|
||||
- validation des paramètres de commandes UI.
|
||||
|
||||
Aucun test ne doit dépendre de RPC ou du réseau.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
cargo fmt
|
||||
cargo test -p kb_lib program_surface
|
||||
cargo test -p kb_lib
|
||||
cargo clippy -p kb_lib --all-targets -- -D warnings
|
||||
```
|
||||
|
||||
Pour Tauri/frontend, lancer la commande de build/test existante du projet si elle existe.
|
||||
|
||||
## Résultat attendu
|
||||
|
||||
L’utilisateur peut ouvrir Demo4 et voir immédiatement les surfaces unsupported/upstream/unknown présentes dans la base SQLite courante, sans rescanner la blockchain.
|
||||
|
||||
Demo4 est un cockpit de découverte uniquement. Il ne change pas la matérialisation métier.
|
||||
|
||||
@@ -0,0 +1,447 @@
|
||||
<!-- file: docs/prompts/PROMPT_0_7_58_sqlite_db_transaction_merger_binary.md -->
|
||||
|
||||
# Prompt — 0.7.58 Binaire de fusion de bases SQLite transactionnelles + validation anti-régression inter-DEX
|
||||
|
||||
## Contexte
|
||||
|
||||
Nous reprenons le workspace Rust `khadhroony-bobobot` après :
|
||||
|
||||
```text
|
||||
0.7.57 -> meteora_dlmm clos
|
||||
0.7.57-pre.015..023 -> nettoyage PumpSwap / guard PumpFees après régression cross-surface
|
||||
```
|
||||
|
||||
Décision de planification mise à jour :
|
||||
|
||||
```text
|
||||
0.7.58 -> sqlite_db_transaction_merger
|
||||
0.7.59 -> demo4_program_surface_discovery
|
||||
0.7.60 -> meteora_damm_v1
|
||||
0.7.61 -> meteora_damm_v2
|
||||
```
|
||||
|
||||
Le binaire de fusion passe avant `demo4`, car il devient un outil de non-régression indispensable pour les futurs décodeurs/materializers.
|
||||
|
||||
## Objectif principal
|
||||
|
||||
Créer un outil binaire de fusion de bases SQLite transactionnelles permettant de construire une base consolidée de corpus, par exemple `final.db`, à partir :
|
||||
|
||||
```text
|
||||
final.db existante
|
||||
+ base dédiée pump_swap.db
|
||||
+ base dédiée pump_fun.db
|
||||
+ base dédiée pump_fees.db
|
||||
+ base dédiée raydium_*.db
|
||||
+ base dédiée meteora_*.db
|
||||
```
|
||||
|
||||
Le binaire doit fusionner les transactions/instructions déjà backfillées sans RPC, sans redécoder et sans matérialiser pendant le merge.
|
||||
|
||||
Ensuite, la base consolidée doit être rejouable localement avec :
|
||||
|
||||
```text
|
||||
metadata=no
|
||||
skipDexDecode=no
|
||||
forceDexDecode=yes
|
||||
deferInstructionObservations=yes
|
||||
```
|
||||
|
||||
But : détecter les régressions inter-DEX comme celle observée où un décodage `pump_fees` tronqué interrompait la matérialisation `pump_swap.buy_exact_quote_in` dans la même transaction.
|
||||
|
||||
## Règle de workflow future
|
||||
|
||||
Pour chaque nouveau DEX ou nouvelle surface :
|
||||
|
||||
1. Créer une base vide dédiée au DEX en cours.
|
||||
2. Backfiller uniquement les signatures/pools nécessaires pour ce DEX.
|
||||
3. Développer le decoder/materializer sur cette base dédiée.
|
||||
4. Clore avec les SQL propres du DEX.
|
||||
5. Fusionner la base dédiée dans `final.db` ou dans une copie `final.next.db`.
|
||||
6. Rejouer `final.next.db` avec `forceDexDecode=yes`.
|
||||
7. Lancer les validations globales anti-régression sur Pump/Raydium/Meteora et sur les surfaces closes.
|
||||
8. Promouvoir `final.next.db` en `final.db` seulement si les gates sont propres.
|
||||
|
||||
Commande conceptuelle :
|
||||
|
||||
```bash
|
||||
cargo run -p kb_tools --bin kb_db_merge -- --output ./final.next.db --input ./final.db --input ./corpus/pump_swap.db --input ./corpus/pump_fees.db --mode raw-corpus --dry-run
|
||||
```
|
||||
|
||||
Puis, après validation dry-run :
|
||||
|
||||
```bash
|
||||
cargo run -p kb_tools --bin kb_db_merge -- --output ./final.next.db --input ./final.db --input ./corpus/new_dex.db --mode raw-corpus --replace-output
|
||||
```
|
||||
|
||||
## Emplacement recommandé
|
||||
|
||||
Créer un package outil séparé :
|
||||
|
||||
```text
|
||||
kb_tools/
|
||||
Cargo.toml
|
||||
src/bin/kb_db_merge.rs
|
||||
```
|
||||
|
||||
Ajouter `kb_tools` dans le workspace principal.
|
||||
|
||||
Alternative acceptable si la structure workspace rend cela plus simple :
|
||||
|
||||
```text
|
||||
kb_lib/src/bin/kb_db_merge.rs
|
||||
```
|
||||
|
||||
Mais privilégier `kb_tools` pour éviter de mélanger outil CLI et librairie métier.
|
||||
|
||||
## Contraintes de code
|
||||
|
||||
Respecter les contraintes projet :
|
||||
|
||||
- Rust 2024 ;
|
||||
- async-first si accès DB async ;
|
||||
- tracing obligatoire ;
|
||||
- pas de `?` dans le code applicatif ;
|
||||
- pas de `unwrap` / `expect` hors tests ;
|
||||
- pas de `anyhow` / `thiserror` ;
|
||||
- pas de `mod.rs` ;
|
||||
- pas de `pub mod`, utiliser `mod` + `pub use` ;
|
||||
- imports directs seulement pour les traits ;
|
||||
- tests offline ;
|
||||
- types DB sous `kb_lib/src/db/entities/` et `kb_lib/src/db/dtos/`, pas sous `queries/` ;
|
||||
- si des requêtes DB sont ajoutées/modifiées : mettre à jour les re-exports dans `kb_lib/src/db.rs` puis `kb_lib/src/lib.rs`.
|
||||
|
||||
## Modes CLI
|
||||
|
||||
Modes initiaux :
|
||||
|
||||
```text
|
||||
raw-corpus
|
||||
signatures-only
|
||||
full-copy-safe
|
||||
```
|
||||
|
||||
Le mode par défaut doit être :
|
||||
|
||||
```text
|
||||
raw-corpus
|
||||
```
|
||||
|
||||
### `raw-corpus`
|
||||
|
||||
Copier uniquement les données nécessaires à un replay local fiable :
|
||||
|
||||
```text
|
||||
k_sol_chain_transactions
|
||||
k_sol_chain_instructions
|
||||
```
|
||||
|
||||
Ne pas copier les tables matérialisées par défaut :
|
||||
|
||||
```text
|
||||
k_sol_dex_decoded_events
|
||||
k_sol_trade_events
|
||||
k_sol_liquidity_events
|
||||
k_sol_pool_lifecycle_events
|
||||
k_sol_pair_candles
|
||||
k_sol_fee_events
|
||||
k_sol_reward_events
|
||||
k_sol_pool_admin_events
|
||||
k_sol_orderbook_events
|
||||
k_sol_token_account_events
|
||||
k_sol_instruction_observations
|
||||
k_sol_dex_event_coverage_entries
|
||||
```
|
||||
|
||||
Raison : ces tables dépendent de la version de decoder/materializer et doivent être reconstruites par replay local.
|
||||
|
||||
### `signatures-only`
|
||||
|
||||
Créer une table de staging de signatures/slots/source, sans copier les transactions complètes. Ce mode est utile pour audit ou pour préparer des backfills contrôlés.
|
||||
|
||||
### `full-copy-safe`
|
||||
|
||||
Mode optionnel, non prioritaire.
|
||||
|
||||
Copier aussi des lignes décodées/matérialisées uniquement si :
|
||||
|
||||
- le schema version est compatible ;
|
||||
- la version logique de decoder est identique ;
|
||||
- toutes les foreign keys peuvent être remappées ;
|
||||
- le mode est explicitement demandé.
|
||||
|
||||
Ne jamais en faire le mode par défaut.
|
||||
|
||||
## Options CLI minimales
|
||||
|
||||
```text
|
||||
--output <path>
|
||||
--input <path> répétable
|
||||
--mode <raw-corpus|signatures-only|full-copy-safe>
|
||||
--dry-run
|
||||
--replace-output
|
||||
--source-label <label> répétable ou inféré depuis le fichier
|
||||
--limit <n> optionnel pour tests
|
||||
--batch-size <n> optionnel
|
||||
```
|
||||
|
||||
Options utiles pour `final.db` :
|
||||
|
||||
```text
|
||||
--allow-existing-output
|
||||
--merge-into-copy-of <path>
|
||||
--conflict-policy <record|prefer-complete|fail>
|
||||
```
|
||||
|
||||
Par défaut : ne pas écraser l’output existant sans `--replace-output` ou `--merge-into-copy-of`.
|
||||
|
||||
## Politique de déduplication
|
||||
|
||||
Identité canonique :
|
||||
|
||||
```text
|
||||
signature
|
||||
```
|
||||
|
||||
Règles :
|
||||
|
||||
- Une signature déjà présente dans l’output ne doit pas être dupliquée.
|
||||
- Si `slot`, `err_json`, `transaction_json`, `meta_json` ou les instructions diffèrent, enregistrer un conflit.
|
||||
- Ne pas écraser silencieusement.
|
||||
- Si une version est plus complète, ne la préférer que si la politique `prefer-complete` est demandée et que le conflit est journalisé.
|
||||
- Garder une provenance complète source -> output.
|
||||
|
||||
Tables suggérées :
|
||||
|
||||
```text
|
||||
k_sol_db_merge_sources
|
||||
k_sol_db_merge_transactions
|
||||
k_sol_db_merge_conflicts
|
||||
```
|
||||
|
||||
Champs source suggérés :
|
||||
|
||||
```text
|
||||
source_id
|
||||
source_path
|
||||
source_label
|
||||
source_schema_version
|
||||
source_created_at
|
||||
input_transaction_count
|
||||
copied_transaction_count
|
||||
skipped_duplicate_count
|
||||
conflict_count
|
||||
```
|
||||
|
||||
Champs transaction de merge suggérés :
|
||||
|
||||
```text
|
||||
signature
|
||||
slot
|
||||
source_id
|
||||
source_transaction_id
|
||||
output_transaction_id
|
||||
copy_status
|
||||
conflict_status
|
||||
copied_at
|
||||
```
|
||||
|
||||
## Compatibilité de schéma
|
||||
|
||||
Le binaire doit inspecter chaque input via :
|
||||
|
||||
```sql
|
||||
SELECT name, sql FROM sqlite_master WHERE type = 'table';
|
||||
PRAGMA table_info(k_sol_chain_transactions);
|
||||
PRAGMA table_info(k_sol_chain_instructions);
|
||||
```
|
||||
|
||||
Pour `raw-corpus`, refuser proprement une DB qui ne contient pas :
|
||||
|
||||
```text
|
||||
k_sol_chain_transactions
|
||||
k_sol_chain_instructions
|
||||
```
|
||||
|
||||
ou les colonnes nécessaires au replay local.
|
||||
|
||||
Le message d’erreur doit indiquer :
|
||||
|
||||
```text
|
||||
source DB
|
||||
missing table
|
||||
missing column
|
||||
mode demandé
|
||||
```
|
||||
|
||||
## Stratégie de copie
|
||||
|
||||
Algorithme recommandé :
|
||||
|
||||
1. Créer ou initialiser l’output avec le schéma courant `kb_lib`.
|
||||
2. Enregistrer chaque input dans `k_sol_db_merge_sources`.
|
||||
3. Lire les transactions source par slot/signature.
|
||||
4. Pour chaque transaction :
|
||||
- si signature absente de l’output : insérer transaction + instructions enfants ;
|
||||
- si signature présente : comparer les champs canoniques ;
|
||||
- si identique : enregistrer duplicate skipped ;
|
||||
- si différent : enregistrer conflit.
|
||||
5. Remapper `source_transaction_id -> output_transaction_id` pour les instructions.
|
||||
6. Commit par batch.
|
||||
7. Imprimer un résumé final.
|
||||
|
||||
## Validation anti-régression intégrée à 0.7.58
|
||||
|
||||
Ajouter une documentation et, si possible, un script SQL :
|
||||
|
||||
```text
|
||||
validation_sql/SQL_VALIDATION_DB_MERGE_0_7_58.sql
|
||||
validation_sql/SQL_VALIDATION_CROSS_DEX_REGRESSION_0_7_58.sql
|
||||
```
|
||||
|
||||
Checks merge minimaux :
|
||||
|
||||
```sql
|
||||
-- duplicate signatures in output should be empty
|
||||
SELECT signature, COUNT(*)
|
||||
FROM k_sol_chain_transactions
|
||||
GROUP BY signature
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- orphan instructions should be empty
|
||||
SELECT ins.id
|
||||
FROM k_sol_chain_instructions ins
|
||||
LEFT JOIN k_sol_chain_transactions tx ON tx.id = ins.transaction_id
|
||||
WHERE tx.id IS NULL;
|
||||
|
||||
-- conflicts summary
|
||||
SELECT *
|
||||
FROM k_sol_db_merge_conflicts
|
||||
ORDER BY id DESC
|
||||
LIMIT 100;
|
||||
```
|
||||
|
||||
Checks anti-régression DEX après replay de `final.next.db` :
|
||||
|
||||
```sql
|
||||
-- successful decoded events without any materialized target
|
||||
SELECT
|
||||
de.protocol_name,
|
||||
de.event_kind,
|
||||
json_extract(de.payload_json, '$.eventActionability') AS event_actionability,
|
||||
json_extract(de.payload_json, '$.skipTradeReason') AS skip_trade_reason,
|
||||
COUNT(*) AS missing_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_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 (tx.err_json IS NULL OR tx.err_json = '' OR tx.err_json = 'null')
|
||||
AND te.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
|
||||
GROUP BY de.protocol_name, de.event_kind, event_actionability, skip_trade_reason
|
||||
ORDER BY missing_count DESC, de.protocol_name, de.event_kind;
|
||||
```
|
||||
|
||||
Cette requête ne doit pas forcément être vide, mais tout résidu doit être explicitement classé : failed-only, decoded-only justifié, non_actionable prouvé, ou nouveau bug.
|
||||
|
||||
## Vérification/correction Pump/Raydium incluse dans 0.7.58
|
||||
|
||||
La tranche `0.7.58` doit inclure un passage de non-régression sur les surfaces déjà closes, au minimum :
|
||||
|
||||
```text
|
||||
pump_swap
|
||||
pump_fun
|
||||
pump_fees
|
||||
raydium_cpmm
|
||||
raydium_clmm
|
||||
raydium_amm_v4
|
||||
raydium_stable_swap
|
||||
raydium_launchpad
|
||||
meteora_dbc
|
||||
meteora_dlmm
|
||||
```
|
||||
|
||||
Objectif : détecter et corriger les régressions cross-surface, par exemple :
|
||||
|
||||
```text
|
||||
un decoder A ne doit pas faire échouer la matérialisation d’un decoder B dans la même transaction.
|
||||
```
|
||||
|
||||
Règle de decoder :
|
||||
|
||||
- un payload tronqué/incompatible sur une surface secondaire ne doit pas aborter toute la transaction si l’entrée peut être ignorée proprement ;
|
||||
- préférer `Ok(None)` + observation/debug contrôlé pour les payloads tronqués connus ;
|
||||
- conserver `Err` pour corruption critique, violation de schéma interne ou incohérence qui rend la transaction non fiable ;
|
||||
- ajouter des tests synthétiques pour chaque guard.
|
||||
|
||||
## Tests attendus
|
||||
|
||||
Ajouter tests unitaires/offline pour :
|
||||
|
||||
1. merge d’une DB vers output vide ;
|
||||
2. merge de deux DBs disjointes ;
|
||||
3. duplicate signature identique ;
|
||||
4. duplicate signature conflictuelle ;
|
||||
5. conflit instruction enfant ;
|
||||
6. dry-run sans écriture ;
|
||||
7. output existant refusé sans option explicite ;
|
||||
8. source provenance enregistrée ;
|
||||
9. replay final DB possible après merge raw-corpus ;
|
||||
10. non-régression PumpSwap/PumpFees : payload PumpFees tronqué n’empêche pas PumpSwap de matérialiser un trade exact.
|
||||
|
||||
## Livrables attendus
|
||||
|
||||
```text
|
||||
kb_tools/Cargo.toml
|
||||
kb_tools/src/bin/kb_db_merge.rs
|
||||
kb_lib/src/db/entities/db_merge_source.rs
|
||||
kb_lib/src/db/entities/db_merge_transaction.rs
|
||||
kb_lib/src/db/entities/db_merge_conflict.rs
|
||||
kb_lib/src/db/dtos/db_merge_*.rs
|
||||
kb_lib/src/db/queries/db_merge_*.rs
|
||||
validation_sql/SQL_VALIDATION_DB_MERGE_0_7_58.sql
|
||||
validation_sql/SQL_VALIDATION_CROSS_DEX_REGRESSION_0_7_58.sql
|
||||
docs/reports/SQLITE_DB_TRANSACTION_MERGER_0_7_58.md
|
||||
```
|
||||
|
||||
Mettre à jour :
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
kb_lib/src/db.rs
|
||||
kb_lib/src/lib.rs
|
||||
README.md
|
||||
ROADMAP.md
|
||||
CHANGELOG.md
|
||||
```
|
||||
|
||||
## Critères de clôture 0.7.58
|
||||
|
||||
- `cargo test -p kb_lib` OK ;
|
||||
- `cargo test -p kb_tools` OK si package séparé ;
|
||||
- clippy `-D warnings` OK ;
|
||||
- dry-run merge lisible ;
|
||||
- merge réel de plusieurs bases OK ;
|
||||
- pas de duplicate signatures ;
|
||||
- pas d’instructions orphelines ;
|
||||
- conflits journalisés ;
|
||||
- replay local de `final.next.db` OK ;
|
||||
- validation anti-régression Pump/Raydium/Meteora exécutée et documentée.
|
||||
|
||||
## Note finale
|
||||
|
||||
Le merger ne remplace pas `demo3` ni `demo4`.
|
||||
|
||||
Il sert à construire un corpus consolidé de non-régression à partir des bases dédiées. `demo4`, déplacé en `0.7.59`, servira ensuite à explorer les surfaces encore inconnues dans cette base consolidée.
|
||||
@@ -1,245 +1,18 @@
|
||||
<!-- file: docs/prompts/PROMPT_0_7_58_demo4_program_surface_discovery.md -->
|
||||
|
||||
# Prompt — 0.7.58 Demo4 Program Surface Discovery
|
||||
# OBSOLETE — Prompt déplacé
|
||||
|
||||
## Contexte
|
||||
Ce prompt n'est plus la cible `0.7.58`.
|
||||
|
||||
Nous reprenons le workspace Rust/Tauri `khadhroony-bobobot` après la clôture de `0.7.57 meteora_dlmm`.
|
||||
|
||||
État validé :
|
||||
Nouvel ordre validé :
|
||||
|
||||
```text
|
||||
0.7.57 meteora_dlmm clos
|
||||
cargo test -p kb_lib -> 460 passed
|
||||
cargo clippy -p kb_lib --all-targets -- -D warnings -> OK
|
||||
replay -> 769 replayed, 106 trades, 664 liquidity, 1107 lifecycle, 424 candles
|
||||
instructionObservations = 8062
|
||||
catalogue = 169 tokens / 218 pools / 218 pairs
|
||||
0.7.58 -> sqlite_db_transaction_merger
|
||||
0.7.59 -> demo4_program_surface_discovery
|
||||
```
|
||||
|
||||
Contraintes projet à respecter :
|
||||
|
||||
- Rust 2024.
|
||||
- Code async-first.
|
||||
- Pas de `?`, `unwrap`, `expect` dans le code applicatif.
|
||||
- Pas de `anyhow`, pas de `thiserror`.
|
||||
- Pas de `mod.rs`.
|
||||
- Pas de `pub mod` ; utiliser `mod` privé + `pub use`.
|
||||
- Imports uniquement pour les traits quand c’est possible.
|
||||
- `#![deny(unreachable_pub)]` et `#![warn(missing_docs)]` doivent rester propres.
|
||||
- Tests offline.
|
||||
- Les nouvelles requêtes DB doivent mettre à jour les re-exports dans `kb_lib/src/db.rs` puis `kb_lib/src/lib.rs`.
|
||||
- Les structs DB doivent aller dans `kb_lib/src/db/entities/*.rs` et `kb_lib/src/db/dtos/*.rs`, pas dans les fichiers de requêtes.
|
||||
|
||||
## Objectif
|
||||
|
||||
Ajouter `Demo4` dans `kb_demo_app` et les fonctions de lecture associées dans `kb_lib` pour afficher les surfaces de programmes non encore prises en compte par le système.
|
||||
|
||||
Cette fonctionnalité est strictement un cockpit de découverte et de revue.
|
||||
|
||||
Elle ne doit pas :
|
||||
|
||||
- matérialiser automatiquement des contenus inconnus ;
|
||||
- promouvoir automatiquement des discriminators inconnus dans les décodeurs officiels ;
|
||||
- marquer automatiquement un `program_id` comme supporté ;
|
||||
- créer automatiquement des lignes `trade`, `candle`, `fee`, `liquidity`, `reward`, `admin` ou `orderbook` depuis un contenu inconnu.
|
||||
|
||||
Elle doit :
|
||||
|
||||
- afficher les surfaces inconnues ou non supportées ;
|
||||
- les regrouper de manière exploitable ;
|
||||
- montrer les preuves et signatures samples ;
|
||||
- aider à produire des prompts/patchs futurs validés manuellement.
|
||||
|
||||
## Comportement attendu côté utilisateur
|
||||
|
||||
Créer une nouvelle page ou fenêtre, selon le pattern existant :
|
||||
Utiliser :
|
||||
|
||||
```text
|
||||
kb_demo_app/src/demo4.html
|
||||
kb_demo_app/src/demo4.ts
|
||||
docs/prompts/PROMPT_0_7_59_demo4_program_surface_discovery.md
|
||||
```
|
||||
|
||||
ou l’équivalent si l’application utilise un autre routage.
|
||||
|
||||
Demo4 doit proposer des vues pour :
|
||||
|
||||
1. discriminators inconnus sur des decoders connus ;
|
||||
2. `program_id` connus contenant des instructions non mappées par le decoder spécialisé ;
|
||||
3. `upstream_git.instruction_match` groupés par `upstreamDecoderCode`, `upstreamEntryName`, `upstreamDiscriminatorHex` ;
|
||||
4. `program_id` observés dans les transactions mais absents de la matrice de support ;
|
||||
5. logs Anchor `Program log: Instruction: ...` non mappés localement ;
|
||||
6. events Anchor `Program data:` non mappés localement ;
|
||||
7. layouts de comptes récurrents sur instructions inconnues ;
|
||||
8. répartition success/failed pour chaque surface candidate.
|
||||
|
||||
Filtres souhaités :
|
||||
|
||||
```text
|
||||
program_id
|
||||
candidate_decoder_code
|
||||
discriminator_hex
|
||||
instruction_name / nom log Anchor
|
||||
upstream decoder
|
||||
upstream entry name
|
||||
success only / failed only / both
|
||||
minimum observation count
|
||||
minimum tx count
|
||||
from slot / to slot
|
||||
signature contains
|
||||
show only unknown
|
||||
show only upstream fallback
|
||||
show only high-confidence candidates
|
||||
```
|
||||
|
||||
Colonnes souhaitées :
|
||||
|
||||
```text
|
||||
candidate_kind
|
||||
program_id
|
||||
candidate_decoder
|
||||
known/unknown status
|
||||
discriminator_hex
|
||||
log_instruction_name
|
||||
anchor_event_discriminator
|
||||
account_count
|
||||
layout_hash
|
||||
observed_count
|
||||
tx_count
|
||||
success_count
|
||||
failed_count
|
||||
first_slot
|
||||
last_slot
|
||||
sample_signature
|
||||
confidence_score
|
||||
confidence_reason
|
||||
status
|
||||
```
|
||||
|
||||
Le panneau de détail doit afficher :
|
||||
|
||||
```text
|
||||
sample signatures
|
||||
accounts_json
|
||||
data_json / data prefix
|
||||
logs de transaction
|
||||
inner instructions
|
||||
parsed_json si disponible
|
||||
decoded_payload_json si disponible
|
||||
entrées upstream registry correspondantes
|
||||
preuve de hash Anchor si un nom d’instruction est disponible
|
||||
```
|
||||
|
||||
## Design `kb_lib`
|
||||
|
||||
Ajouter des requêtes read-only et des DTOs. Fichiers suggérés :
|
||||
|
||||
```text
|
||||
kb_lib/src/program_surface_discovery.rs
|
||||
kb_lib/src/db/dtos/program_surface_candidate_summary.rs
|
||||
kb_lib/src/db/dtos/program_surface_candidate_sample.rs
|
||||
kb_lib/src/db/queries/program_surface_discovery.rs
|
||||
```
|
||||
|
||||
Si un statut persistant est nécessaire, ajouter :
|
||||
|
||||
```text
|
||||
kb_lib/src/db/entities/program_surface_candidate.rs
|
||||
kb_lib/src/db/queries/program_surface_candidates.rs
|
||||
```
|
||||
|
||||
Ne pas placer les DTO/entities dans les fichiers de requêtes.
|
||||
|
||||
Statuts candidats suggérés :
|
||||
|
||||
```text
|
||||
new
|
||||
reviewed
|
||||
accepted
|
||||
rejected
|
||||
promoted
|
||||
ignored
|
||||
```
|
||||
|
||||
Si une table persistante est ajoutée, la migration doit être explicite, idempotente et testée.
|
||||
|
||||
## Scoring de découverte
|
||||
|
||||
Le scoring est indicatif uniquement. Il ne doit jamais déclencher une matérialisation.
|
||||
|
||||
Signaux de confiance possibles :
|
||||
|
||||
| Signal | Effet |
|
||||
|---|---:|
|
||||
| `program_id` connu, discriminator inconnu | +20 |
|
||||
| log Anchor `Instruction: X` trouvé | +30 |
|
||||
| `sha256("global:x")` correspond au discriminator | +50 |
|
||||
| observations success répétées | +10 |
|
||||
| account count/layout stable | +10 |
|
||||
| inner instruction family cohérente avec l’action | +10 |
|
||||
| uniquement des transactions failed | -20 |
|
||||
| nom très générique comme `swap`, `claim`, `initialize` sans autre preuve | -15 |
|
||||
| `program_id` inconnu et observation unique | -25 |
|
||||
|
||||
## Helper Anchor
|
||||
|
||||
Ajouter un helper qui normalise les noms d’instructions Anchor depuis les logs :
|
||||
|
||||
```text
|
||||
Program log: Instruction: InitializePresetParameterV2
|
||||
-> initialize_preset_parameter_v2
|
||||
-> sha256("global:initialize_preset_parameter_v2")[0..8]
|
||||
```
|
||||
|
||||
Ce helper doit seulement produire une preuve/candidate record. Il ne doit pas modifier les mappings de decoder.
|
||||
|
||||
## Export Markdown
|
||||
|
||||
Demo4 doit permettre d’exporter un groupe candidat en Markdown pour une future session d’implémentation.
|
||||
|
||||
L’export doit inclure :
|
||||
|
||||
```text
|
||||
candidate decoder
|
||||
program_id
|
||||
discriminator_hex
|
||||
candidate name
|
||||
confidence score
|
||||
preuves/evidence
|
||||
sample signatures
|
||||
account layout
|
||||
logs
|
||||
inner instruction summary
|
||||
proposed target table éventuelle
|
||||
warning explicite : non implémenté, non matérialisé
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Ajouter des tests offline pour :
|
||||
|
||||
- normalisation des noms Anchor depuis logs ;
|
||||
- calcul de discriminators Anchor pour des noms samples ;
|
||||
- groupement par program/discriminator/layout ;
|
||||
- ordre de score ;
|
||||
- sérialisation/désérialisation des DTOs si utilisés ;
|
||||
- validation des paramètres de commandes UI.
|
||||
|
||||
Aucun test ne doit dépendre de RPC ou du réseau.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
cargo fmt
|
||||
cargo test -p kb_lib program_surface
|
||||
cargo test -p kb_lib
|
||||
cargo clippy -p kb_lib --all-targets -- -D warnings
|
||||
```
|
||||
|
||||
Pour Tauri/frontend, lancer la commande de build/test existante du projet si elle existe.
|
||||
|
||||
## Résultat attendu
|
||||
|
||||
L’utilisateur peut ouvrir Demo4 et voir immédiatement les surfaces unsupported/upstream/unknown présentes dans la base SQLite courante, sans rescanner la blockchain.
|
||||
|
||||
Demo4 est un cockpit de découverte uniquement. Il ne change pas la matérialisation métier.
|
||||
|
||||
@@ -0,0 +1,447 @@
|
||||
<!-- file: docs/prompts/PROMPT_0_7_58_sqlite_db_transaction_merger_binary.md -->
|
||||
|
||||
# Prompt — 0.7.58 Binaire de fusion de bases SQLite transactionnelles + validation anti-régression inter-DEX
|
||||
|
||||
## Contexte
|
||||
|
||||
Nous reprenons le workspace Rust `khadhroony-bobobot` après :
|
||||
|
||||
```text
|
||||
0.7.57 -> meteora_dlmm clos
|
||||
0.7.57-pre.015..023 -> nettoyage PumpSwap / guard PumpFees après régression cross-surface
|
||||
```
|
||||
|
||||
Décision de planification mise à jour :
|
||||
|
||||
```text
|
||||
0.7.58 -> sqlite_db_transaction_merger
|
||||
0.7.59 -> demo4_program_surface_discovery
|
||||
0.7.60 -> meteora_damm_v1
|
||||
0.7.61 -> meteora_damm_v2
|
||||
```
|
||||
|
||||
Le binaire de fusion passe avant `demo4`, car il devient un outil de non-régression indispensable pour les futurs décodeurs/materializers.
|
||||
|
||||
## Objectif principal
|
||||
|
||||
Créer un outil binaire de fusion de bases SQLite transactionnelles permettant de construire une base consolidée de corpus, par exemple `final.db`, à partir :
|
||||
|
||||
```text
|
||||
final.db existante
|
||||
+ base dédiée pump_swap.db
|
||||
+ base dédiée pump_fun.db
|
||||
+ base dédiée pump_fees.db
|
||||
+ base dédiée raydium_*.db
|
||||
+ base dédiée meteora_*.db
|
||||
```
|
||||
|
||||
Le binaire doit fusionner les transactions/instructions déjà backfillées sans RPC, sans redécoder et sans matérialiser pendant le merge.
|
||||
|
||||
Ensuite, la base consolidée doit être rejouable localement avec :
|
||||
|
||||
```text
|
||||
metadata=no
|
||||
skipDexDecode=no
|
||||
forceDexDecode=yes
|
||||
deferInstructionObservations=yes
|
||||
```
|
||||
|
||||
But : détecter les régressions inter-DEX comme celle observée où un décodage `pump_fees` tronqué interrompait la matérialisation `pump_swap.buy_exact_quote_in` dans la même transaction.
|
||||
|
||||
## Règle de workflow future
|
||||
|
||||
Pour chaque nouveau DEX ou nouvelle surface :
|
||||
|
||||
1. Créer une base vide dédiée au DEX en cours.
|
||||
2. Backfiller uniquement les signatures/pools nécessaires pour ce DEX.
|
||||
3. Développer le decoder/materializer sur cette base dédiée.
|
||||
4. Clore avec les SQL propres du DEX.
|
||||
5. Fusionner la base dédiée dans `final.db` ou dans une copie `final.next.db`.
|
||||
6. Rejouer `final.next.db` avec `forceDexDecode=yes`.
|
||||
7. Lancer les validations globales anti-régression sur Pump/Raydium/Meteora et sur les surfaces closes.
|
||||
8. Promouvoir `final.next.db` en `final.db` seulement si les gates sont propres.
|
||||
|
||||
Commande conceptuelle :
|
||||
|
||||
```bash
|
||||
cargo run -p kb_tools --bin kb_db_merge -- --output ./final.next.db --input ./final.db --input ./corpus/pump_swap.db --input ./corpus/pump_fees.db --mode raw-corpus --dry-run
|
||||
```
|
||||
|
||||
Puis, après validation dry-run :
|
||||
|
||||
```bash
|
||||
cargo run -p kb_tools --bin kb_db_merge -- --output ./final.next.db --input ./final.db --input ./corpus/new_dex.db --mode raw-corpus --replace-output
|
||||
```
|
||||
|
||||
## Emplacement recommandé
|
||||
|
||||
Créer un package outil séparé :
|
||||
|
||||
```text
|
||||
kb_tools/
|
||||
Cargo.toml
|
||||
src/bin/kb_db_merge.rs
|
||||
```
|
||||
|
||||
Ajouter `kb_tools` dans le workspace principal.
|
||||
|
||||
Alternative acceptable si la structure workspace rend cela plus simple :
|
||||
|
||||
```text
|
||||
kb_lib/src/bin/kb_db_merge.rs
|
||||
```
|
||||
|
||||
Mais privilégier `kb_tools` pour éviter de mélanger outil CLI et librairie métier.
|
||||
|
||||
## Contraintes de code
|
||||
|
||||
Respecter les contraintes projet :
|
||||
|
||||
- Rust 2024 ;
|
||||
- async-first si accès DB async ;
|
||||
- tracing obligatoire ;
|
||||
- pas de `?` dans le code applicatif ;
|
||||
- pas de `unwrap` / `expect` hors tests ;
|
||||
- pas de `anyhow` / `thiserror` ;
|
||||
- pas de `mod.rs` ;
|
||||
- pas de `pub mod`, utiliser `mod` + `pub use` ;
|
||||
- imports directs seulement pour les traits ;
|
||||
- tests offline ;
|
||||
- types DB sous `kb_lib/src/db/entities/` et `kb_lib/src/db/dtos/`, pas sous `queries/` ;
|
||||
- si des requêtes DB sont ajoutées/modifiées : mettre à jour les re-exports dans `kb_lib/src/db.rs` puis `kb_lib/src/lib.rs`.
|
||||
|
||||
## Modes CLI
|
||||
|
||||
Modes initiaux :
|
||||
|
||||
```text
|
||||
raw-corpus
|
||||
signatures-only
|
||||
full-copy-safe
|
||||
```
|
||||
|
||||
Le mode par défaut doit être :
|
||||
|
||||
```text
|
||||
raw-corpus
|
||||
```
|
||||
|
||||
### `raw-corpus`
|
||||
|
||||
Copier uniquement les données nécessaires à un replay local fiable :
|
||||
|
||||
```text
|
||||
k_sol_chain_transactions
|
||||
k_sol_chain_instructions
|
||||
```
|
||||
|
||||
Ne pas copier les tables matérialisées par défaut :
|
||||
|
||||
```text
|
||||
k_sol_dex_decoded_events
|
||||
k_sol_trade_events
|
||||
k_sol_liquidity_events
|
||||
k_sol_pool_lifecycle_events
|
||||
k_sol_pair_candles
|
||||
k_sol_fee_events
|
||||
k_sol_reward_events
|
||||
k_sol_pool_admin_events
|
||||
k_sol_orderbook_events
|
||||
k_sol_token_account_events
|
||||
k_sol_instruction_observations
|
||||
k_sol_dex_event_coverage_entries
|
||||
```
|
||||
|
||||
Raison : ces tables dépendent de la version de decoder/materializer et doivent être reconstruites par replay local.
|
||||
|
||||
### `signatures-only`
|
||||
|
||||
Créer une table de staging de signatures/slots/source, sans copier les transactions complètes. Ce mode est utile pour audit ou pour préparer des backfills contrôlés.
|
||||
|
||||
### `full-copy-safe`
|
||||
|
||||
Mode optionnel, non prioritaire.
|
||||
|
||||
Copier aussi des lignes décodées/matérialisées uniquement si :
|
||||
|
||||
- le schema version est compatible ;
|
||||
- la version logique de decoder est identique ;
|
||||
- toutes les foreign keys peuvent être remappées ;
|
||||
- le mode est explicitement demandé.
|
||||
|
||||
Ne jamais en faire le mode par défaut.
|
||||
|
||||
## Options CLI minimales
|
||||
|
||||
```text
|
||||
--output <path>
|
||||
--input <path> répétable
|
||||
--mode <raw-corpus|signatures-only|full-copy-safe>
|
||||
--dry-run
|
||||
--replace-output
|
||||
--source-label <label> répétable ou inféré depuis le fichier
|
||||
--limit <n> optionnel pour tests
|
||||
--batch-size <n> optionnel
|
||||
```
|
||||
|
||||
Options utiles pour `final.db` :
|
||||
|
||||
```text
|
||||
--allow-existing-output
|
||||
--merge-into-copy-of <path>
|
||||
--conflict-policy <record|prefer-complete|fail>
|
||||
```
|
||||
|
||||
Par défaut : ne pas écraser l’output existant sans `--replace-output` ou `--merge-into-copy-of`.
|
||||
|
||||
## Politique de déduplication
|
||||
|
||||
Identité canonique :
|
||||
|
||||
```text
|
||||
signature
|
||||
```
|
||||
|
||||
Règles :
|
||||
|
||||
- Une signature déjà présente dans l’output ne doit pas être dupliquée.
|
||||
- Si `slot`, `err_json`, `transaction_json`, `meta_json` ou les instructions diffèrent, enregistrer un conflit.
|
||||
- Ne pas écraser silencieusement.
|
||||
- Si une version est plus complète, ne la préférer que si la politique `prefer-complete` est demandée et que le conflit est journalisé.
|
||||
- Garder une provenance complète source -> output.
|
||||
|
||||
Tables suggérées :
|
||||
|
||||
```text
|
||||
k_sol_db_merge_sources
|
||||
k_sol_db_merge_transactions
|
||||
k_sol_db_merge_conflicts
|
||||
```
|
||||
|
||||
Champs source suggérés :
|
||||
|
||||
```text
|
||||
source_id
|
||||
source_path
|
||||
source_label
|
||||
source_schema_version
|
||||
source_created_at
|
||||
input_transaction_count
|
||||
copied_transaction_count
|
||||
skipped_duplicate_count
|
||||
conflict_count
|
||||
```
|
||||
|
||||
Champs transaction de merge suggérés :
|
||||
|
||||
```text
|
||||
signature
|
||||
slot
|
||||
source_id
|
||||
source_transaction_id
|
||||
output_transaction_id
|
||||
copy_status
|
||||
conflict_status
|
||||
copied_at
|
||||
```
|
||||
|
||||
## Compatibilité de schéma
|
||||
|
||||
Le binaire doit inspecter chaque input via :
|
||||
|
||||
```sql
|
||||
SELECT name, sql FROM sqlite_master WHERE type = 'table';
|
||||
PRAGMA table_info(k_sol_chain_transactions);
|
||||
PRAGMA table_info(k_sol_chain_instructions);
|
||||
```
|
||||
|
||||
Pour `raw-corpus`, refuser proprement une DB qui ne contient pas :
|
||||
|
||||
```text
|
||||
k_sol_chain_transactions
|
||||
k_sol_chain_instructions
|
||||
```
|
||||
|
||||
ou les colonnes nécessaires au replay local.
|
||||
|
||||
Le message d’erreur doit indiquer :
|
||||
|
||||
```text
|
||||
source DB
|
||||
missing table
|
||||
missing column
|
||||
mode demandé
|
||||
```
|
||||
|
||||
## Stratégie de copie
|
||||
|
||||
Algorithme recommandé :
|
||||
|
||||
1. Créer ou initialiser l’output avec le schéma courant `kb_lib`.
|
||||
2. Enregistrer chaque input dans `k_sol_db_merge_sources`.
|
||||
3. Lire les transactions source par slot/signature.
|
||||
4. Pour chaque transaction :
|
||||
- si signature absente de l’output : insérer transaction + instructions enfants ;
|
||||
- si signature présente : comparer les champs canoniques ;
|
||||
- si identique : enregistrer duplicate skipped ;
|
||||
- si différent : enregistrer conflit.
|
||||
5. Remapper `source_transaction_id -> output_transaction_id` pour les instructions.
|
||||
6. Commit par batch.
|
||||
7. Imprimer un résumé final.
|
||||
|
||||
## Validation anti-régression intégrée à 0.7.58
|
||||
|
||||
Ajouter une documentation et, si possible, un script SQL :
|
||||
|
||||
```text
|
||||
validation_sql/SQL_VALIDATION_DB_MERGE_0_7_58.sql
|
||||
validation_sql/SQL_VALIDATION_CROSS_DEX_REGRESSION_0_7_58.sql
|
||||
```
|
||||
|
||||
Checks merge minimaux :
|
||||
|
||||
```sql
|
||||
-- duplicate signatures in output should be empty
|
||||
SELECT signature, COUNT(*)
|
||||
FROM k_sol_chain_transactions
|
||||
GROUP BY signature
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- orphan instructions should be empty
|
||||
SELECT ins.id
|
||||
FROM k_sol_chain_instructions ins
|
||||
LEFT JOIN k_sol_chain_transactions tx ON tx.id = ins.transaction_id
|
||||
WHERE tx.id IS NULL;
|
||||
|
||||
-- conflicts summary
|
||||
SELECT *
|
||||
FROM k_sol_db_merge_conflicts
|
||||
ORDER BY id DESC
|
||||
LIMIT 100;
|
||||
```
|
||||
|
||||
Checks anti-régression DEX après replay de `final.next.db` :
|
||||
|
||||
```sql
|
||||
-- successful decoded events without any materialized target
|
||||
SELECT
|
||||
de.protocol_name,
|
||||
de.event_kind,
|
||||
json_extract(de.payload_json, '$.eventActionability') AS event_actionability,
|
||||
json_extract(de.payload_json, '$.skipTradeReason') AS skip_trade_reason,
|
||||
COUNT(*) AS missing_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_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 (tx.err_json IS NULL OR tx.err_json = '' OR tx.err_json = 'null')
|
||||
AND te.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
|
||||
GROUP BY de.protocol_name, de.event_kind, event_actionability, skip_trade_reason
|
||||
ORDER BY missing_count DESC, de.protocol_name, de.event_kind;
|
||||
```
|
||||
|
||||
Cette requête ne doit pas forcément être vide, mais tout résidu doit être explicitement classé : failed-only, decoded-only justifié, non_actionable prouvé, ou nouveau bug.
|
||||
|
||||
## Vérification/correction Pump/Raydium incluse dans 0.7.58
|
||||
|
||||
La tranche `0.7.58` doit inclure un passage de non-régression sur les surfaces déjà closes, au minimum :
|
||||
|
||||
```text
|
||||
pump_swap
|
||||
pump_fun
|
||||
pump_fees
|
||||
raydium_cpmm
|
||||
raydium_clmm
|
||||
raydium_amm_v4
|
||||
raydium_stable_swap
|
||||
raydium_launchpad
|
||||
meteora_dbc
|
||||
meteora_dlmm
|
||||
```
|
||||
|
||||
Objectif : détecter et corriger les régressions cross-surface, par exemple :
|
||||
|
||||
```text
|
||||
un decoder A ne doit pas faire échouer la matérialisation d’un decoder B dans la même transaction.
|
||||
```
|
||||
|
||||
Règle de decoder :
|
||||
|
||||
- un payload tronqué/incompatible sur une surface secondaire ne doit pas aborter toute la transaction si l’entrée peut être ignorée proprement ;
|
||||
- préférer `Ok(None)` + observation/debug contrôlé pour les payloads tronqués connus ;
|
||||
- conserver `Err` pour corruption critique, violation de schéma interne ou incohérence qui rend la transaction non fiable ;
|
||||
- ajouter des tests synthétiques pour chaque guard.
|
||||
|
||||
## Tests attendus
|
||||
|
||||
Ajouter tests unitaires/offline pour :
|
||||
|
||||
1. merge d’une DB vers output vide ;
|
||||
2. merge de deux DBs disjointes ;
|
||||
3. duplicate signature identique ;
|
||||
4. duplicate signature conflictuelle ;
|
||||
5. conflit instruction enfant ;
|
||||
6. dry-run sans écriture ;
|
||||
7. output existant refusé sans option explicite ;
|
||||
8. source provenance enregistrée ;
|
||||
9. replay final DB possible après merge raw-corpus ;
|
||||
10. non-régression PumpSwap/PumpFees : payload PumpFees tronqué n’empêche pas PumpSwap de matérialiser un trade exact.
|
||||
|
||||
## Livrables attendus
|
||||
|
||||
```text
|
||||
kb_tools/Cargo.toml
|
||||
kb_tools/src/bin/kb_db_merge.rs
|
||||
kb_lib/src/db/entities/db_merge_source.rs
|
||||
kb_lib/src/db/entities/db_merge_transaction.rs
|
||||
kb_lib/src/db/entities/db_merge_conflict.rs
|
||||
kb_lib/src/db/dtos/db_merge_*.rs
|
||||
kb_lib/src/db/queries/db_merge_*.rs
|
||||
validation_sql/SQL_VALIDATION_DB_MERGE_0_7_58.sql
|
||||
validation_sql/SQL_VALIDATION_CROSS_DEX_REGRESSION_0_7_58.sql
|
||||
docs/reports/SQLITE_DB_TRANSACTION_MERGER_0_7_58.md
|
||||
```
|
||||
|
||||
Mettre à jour :
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
kb_lib/src/db.rs
|
||||
kb_lib/src/lib.rs
|
||||
README.md
|
||||
ROADMAP.md
|
||||
CHANGELOG.md
|
||||
```
|
||||
|
||||
## Critères de clôture 0.7.58
|
||||
|
||||
- `cargo test -p kb_lib` OK ;
|
||||
- `cargo test -p kb_tools` OK si package séparé ;
|
||||
- clippy `-D warnings` OK ;
|
||||
- dry-run merge lisible ;
|
||||
- merge réel de plusieurs bases OK ;
|
||||
- pas de duplicate signatures ;
|
||||
- pas d’instructions orphelines ;
|
||||
- conflits journalisés ;
|
||||
- replay local de `final.next.db` OK ;
|
||||
- validation anti-régression Pump/Raydium/Meteora exécutée et documentée.
|
||||
|
||||
## Note finale
|
||||
|
||||
Le merger ne remplace pas `demo3` ni `demo4`.
|
||||
|
||||
Il sert à construire un corpus consolidé de non-régression à partir des bases dédiées. `demo4`, déplacé en `0.7.59`, servira ensuite à explorer les surfaces encore inconnues dans cette base consolidée.
|
||||
330
docs/prompts/PROMPT_0_7_59_DEMO4_PROGRAM_SURFACE_DISCOVERY.md
Normal file
330
docs/prompts/PROMPT_0_7_59_DEMO4_PROGRAM_SURFACE_DISCOVERY.md
Normal file
@@ -0,0 +1,330 @@
|
||||
<!-- file: docs/prompts/PROMPT_0_7_59_demo4_program_surface_discovery.md -->
|
||||
|
||||
# Prompt — 0.7.59 Demo4 Program Surface Discovery
|
||||
|
||||
## Contexte
|
||||
|
||||
Nous reprenons le workspace Rust `khadhroony-bobobot` après :
|
||||
|
||||
```text
|
||||
0.7.57 -> meteora_dlmm clos
|
||||
0.7.58 -> sqlite_db_transaction_merger + validation anti-régression cross-DEX
|
||||
```
|
||||
|
||||
Décision de planification : `demo4_program_surface_discovery` est déplacé de `0.7.58` vers `0.7.59` afin de permettre d’abord la création d’une base consolidée `final.db`.
|
||||
|
||||
Cette base consolidée doit permettre à Demo4 d’explorer :
|
||||
|
||||
```text
|
||||
program_id inconnus
|
||||
program_id connus avec discriminants non mappés
|
||||
Anchor logs Instruction non mappés
|
||||
Program data events non mappés
|
||||
upstream fallbacks
|
||||
layouts répétitifs d’accounts/data
|
||||
success/failed split
|
||||
```
|
||||
|
||||
## Objectif principal
|
||||
|
||||
Ajouter une fenêtre `Demo4` dans `kb_demo_app` et les requêtes `kb_lib` associées pour afficher les surfaces non prises en compte sans les promouvoir automatiquement.
|
||||
|
||||
Demo4 est un outil d’observation, de tri et de préparation de futures tranches de decoder. Il ne doit pas créer de trade, candle, pool, pair, fee, reward, admin ou lifecycle métier.
|
||||
|
||||
## Règles strictes
|
||||
|
||||
Demo4 doit être read-only par défaut.
|
||||
|
||||
Interdictions :
|
||||
|
||||
- ne pas matérialiser automatiquement ;
|
||||
- ne pas ajouter automatiquement de coverage comme supportée ;
|
||||
- ne pas marquer automatiquement un `program_id` comme connu ;
|
||||
- ne pas générer de decoder métier ;
|
||||
- ne pas promouvoir un upstream fallback vers un decoder local sans intervention humaine ;
|
||||
- ne pas masquer les failed transactions ;
|
||||
- ne pas supprimer les observations existantes.
|
||||
|
||||
Actions autorisées :
|
||||
|
||||
- lister ;
|
||||
- grouper ;
|
||||
- scorer ;
|
||||
- exporter Markdown/CSV/JSON ;
|
||||
- marquer manuellement un candidat comme `reviewed`, `ignored`, `accepted_for_future_decoder`, si table de statut ajoutée ;
|
||||
- ouvrir une signature dans Demo Pipeline 2 ou Demo3.
|
||||
|
||||
## Sources de données prioritaires
|
||||
|
||||
Demo4 doit exploiter la base consolidée construite par `0.7.58`, par exemple :
|
||||
|
||||
```text
|
||||
final.db
|
||||
final.next.db
|
||||
```
|
||||
|
||||
Cette base est issue de merges raw-corpus de bases dédiées :
|
||||
|
||||
```text
|
||||
pump_swap.db
|
||||
pump_fun.db
|
||||
pump_fees.db
|
||||
raydium_*.db
|
||||
meteora_*.db
|
||||
```
|
||||
|
||||
Le bénéfice attendu : détecter les surfaces inconnues et les régressions qui n’apparaissent pas dans une base DEX isolée.
|
||||
|
||||
## Surfaces à afficher
|
||||
|
||||
### 1. Program IDs non routés
|
||||
|
||||
Afficher les `program_id` présents dans `k_sol_chain_instructions` qui ne correspondent pas à une surface supportée ou connue.
|
||||
|
||||
Colonnes utiles :
|
||||
|
||||
```text
|
||||
program_id
|
||||
instruction_count
|
||||
tx_count
|
||||
success_count
|
||||
failed_count
|
||||
first_slot
|
||||
last_slot
|
||||
sample_signature
|
||||
sample_data_prefix
|
||||
sample_account_count
|
||||
```
|
||||
|
||||
### 2. Discriminators non mappés sur program id connu
|
||||
|
||||
Pour un `program_id` déjà supporté, afficher les discriminants inconnus ou encore en `instruction_audit`.
|
||||
|
||||
Colonnes utiles :
|
||||
|
||||
```text
|
||||
protocol_guess
|
||||
program_id
|
||||
discriminator_hex
|
||||
data_prefix
|
||||
instruction_count
|
||||
tx_count
|
||||
success_count
|
||||
failed_count
|
||||
sample_signature
|
||||
known_anchor_log_name si disponible
|
||||
upstream_match_status si disponible
|
||||
```
|
||||
|
||||
### 3. Anchor logs `Instruction: ...`
|
||||
|
||||
Extraire depuis `meta_json.logMessages` :
|
||||
|
||||
```text
|
||||
Program log: Instruction: <Name>
|
||||
```
|
||||
|
||||
Normaliser le nom :
|
||||
|
||||
```text
|
||||
InitializePresetParameterV2 -> initialize_preset_parameter_v2
|
||||
```
|
||||
|
||||
Calculer si possible :
|
||||
|
||||
```text
|
||||
sha256("global:<snake_case_name>")[0..8]
|
||||
```
|
||||
|
||||
Comparer au discriminator observé.
|
||||
|
||||
### 4. Program data / Anchor events non mappés
|
||||
|
||||
Lister les `Program data:` ou events Anchor observés mais non classés localement.
|
||||
|
||||
Colonnes utiles :
|
||||
|
||||
```text
|
||||
program_id
|
||||
anchor_event_discriminator_hex
|
||||
payload_size
|
||||
tx_count
|
||||
success_count
|
||||
failed_count
|
||||
sample_signature
|
||||
possible_event_name
|
||||
upstream_match_status
|
||||
```
|
||||
|
||||
### 5. Upstream fallbacks
|
||||
|
||||
Afficher les événements qui viennent encore de :
|
||||
|
||||
```text
|
||||
upstream_git.instruction_match
|
||||
upstream_git.event_match
|
||||
instruction_audit
|
||||
```
|
||||
|
||||
Demo4 doit aider à décider si l’entrée doit devenir :
|
||||
|
||||
```text
|
||||
local decoder
|
||||
coverage 0/0 conservée
|
||||
decoded-only justifié
|
||||
materialized admin/lifecycle/fee/reward/liquidity/orderbook
|
||||
ignored
|
||||
```
|
||||
|
||||
## UI attendue
|
||||
|
||||
Fichiers probables :
|
||||
|
||||
```text
|
||||
kb_demo_app/src/demo4.html
|
||||
kb_demo_app/src/demo4.ts
|
||||
kb_demo_app/src/main.ts
|
||||
kb_demo_app/src/shared/api.ts
|
||||
kb_demo_app/src/shared/types.ts
|
||||
```
|
||||
|
||||
Ajouter une fenêtre Tauri si nécessaire dans :
|
||||
|
||||
```text
|
||||
kb_demo_app/src-tauri/src/main.rs
|
||||
kb_demo_app/tauri.conf.json
|
||||
```
|
||||
|
||||
Organisation UI suggérée :
|
||||
|
||||
```text
|
||||
Filters:
|
||||
- program_id
|
||||
- protocol_guess
|
||||
- success/failed/all
|
||||
- minimum tx_count
|
||||
- known/unknown program
|
||||
- audit/upstream/unknown discriminator
|
||||
- date/slot range si disponible
|
||||
|
||||
Tabs:
|
||||
1. Unknown programs
|
||||
2. Known program unknown discriminators
|
||||
3. Anchor instruction logs
|
||||
4. Program data events
|
||||
5. Upstream fallbacks
|
||||
6. Regression suspects
|
||||
```
|
||||
|
||||
## API / services kb_lib suggérés
|
||||
|
||||
Créer des DTOs sous :
|
||||
|
||||
```text
|
||||
kb_lib/src/db/dtos/program_surface_candidate_summary.rs
|
||||
kb_lib/src/db/dtos/program_surface_candidate_sample.rs
|
||||
```
|
||||
|
||||
Créer des requêtes sous :
|
||||
|
||||
```text
|
||||
kb_lib/src/db/queries/program_surface_discovery.rs
|
||||
```
|
||||
|
||||
Créer éventuellement une façade :
|
||||
|
||||
```text
|
||||
kb_lib/src/program_surface_discovery.rs
|
||||
```
|
||||
|
||||
Rappel : si des requêtes DB sont ajoutées, mettre à jour les re-exports dans :
|
||||
|
||||
```text
|
||||
kb_lib/src/db.rs
|
||||
kb_lib/src/lib.rs
|
||||
```
|
||||
|
||||
## Table de statut optionnelle
|
||||
|
||||
Si utile, ajouter :
|
||||
|
||||
```text
|
||||
k_sol_program_surface_candidates
|
||||
```
|
||||
|
||||
Statuts possibles :
|
||||
|
||||
```text
|
||||
new
|
||||
reviewed
|
||||
accepted_for_future_decoder
|
||||
ignored
|
||||
promoted_manually
|
||||
rejected
|
||||
```
|
||||
|
||||
Ne jamais passer automatiquement à `promoted_manually`.
|
||||
|
||||
## Intégration avec 0.7.58
|
||||
|
||||
Demo4 doit intégrer explicitement la notion de source DB/merge si les tables `k_sol_db_merge_*` existent.
|
||||
|
||||
Exemples d’affichages utiles :
|
||||
|
||||
```text
|
||||
candidate observed in sources: pump_swap.db, pump_fees.db
|
||||
candidate observed only after final.db merge
|
||||
candidate correlated with replay decode failure
|
||||
candidate appears in transaction with multiple DEX protocols
|
||||
```
|
||||
|
||||
Objectif : rendre visibles les régressions cross-surface comme le cas PumpSwap/PumpFees.
|
||||
|
||||
## SQL de validation Demo4
|
||||
|
||||
Ajouter :
|
||||
|
||||
```text
|
||||
validation_sql/SQL_VALIDATION_DEMO4_PROGRAM_SURFACE_DISCOVERY_0_7_59.sql
|
||||
```
|
||||
|
||||
Checks :
|
||||
|
||||
```sql
|
||||
-- Demo4 queries should not create decoded/materialized rows
|
||||
SELECT COUNT(*) FROM k_sol_dex_decoded_events;
|
||||
SELECT COUNT(*) FROM k_sol_trade_events;
|
||||
|
||||
-- candidate status table should not auto-promote anything
|
||||
SELECT status, COUNT(*)
|
||||
FROM k_sol_program_surface_candidates
|
||||
GROUP BY status;
|
||||
```
|
||||
|
||||
Adapter selon schéma réel.
|
||||
|
||||
## Tests attendus
|
||||
|
||||
- requêtes unknown program sur fixture minimale ;
|
||||
- discriminants inconnus sur program connu ;
|
||||
- extraction Anchor `Instruction: Name` ;
|
||||
- hash Anchor discriminator ;
|
||||
- split success/failed ;
|
||||
- no write en mode read-only ;
|
||||
- export Markdown/CSV stable ;
|
||||
- table statut manuelle si implémentée.
|
||||
|
||||
## Critères de clôture 0.7.59
|
||||
|
||||
- `cargo test -p kb_lib` OK ;
|
||||
- `cargo test` UI/Tauri si existant OK ;
|
||||
- clippy `-D warnings` OK ;
|
||||
- Demo4 affiche les surfaces inconnues depuis `final.db` ;
|
||||
- aucun auto-decode métier ;
|
||||
- aucune auto-materialization ;
|
||||
- export exploitable pour préparer `0.7.60 meteora_damm_v1` ou une tranche future ;
|
||||
- README/ROADMAP/CHANGELOG mis à jour.
|
||||
|
||||
## Note finale
|
||||
|
||||
Demo4 ne remplace pas les prompts DEX. Il prépare les prochaines tranches en rendant les surfaces observées lisibles, priorisées et reproductibles.
|
||||
@@ -1,249 +1,18 @@
|
||||
<!-- file: docs/prompts/prompt_name_0_7_59_sqlite_db_transaction_merger_binary.md -->
|
||||
<!-- file: docs/prompts/PROMPT_0_7_59_sqlite_db_transaction_merger_binary.md -->
|
||||
|
||||
# Prompt — 0.7.59 Binaire de fusion de bases SQLite transactionnelles
|
||||
# OBSOLETE — Prompt déplacé
|
||||
|
||||
## Contexte
|
||||
Ce prompt n'est plus la cible `0.7.59`.
|
||||
|
||||
Nous reprenons le workspace Rust `khadhroony-bobobot` après la clôture de `0.7.57 meteora_dlmm` et après/ou en parallèle de `0.7.58 demo4_program_surface_discovery`.
|
||||
|
||||
L’utilisateur possède plusieurs bases SQLite dédiées construites pendant les tranches Raydium, Pump et Meteora. L’objectif est d’éviter de rescanner/backfiller des signatures déjà présentes dans ces bases.
|
||||
|
||||
## Objectif
|
||||
|
||||
Ajouter un module binaire qui utilise `kb_lib` pour fusionner des données de corpus transactionnel depuis plusieurs fichiers SQLite `.db` vers une base SQLite de sortie unique.
|
||||
|
||||
Le merger doit copier suffisamment de données brutes transaction/instruction pour permettre un replay local dans la base consolidée, sans refaire les appels RPC.
|
||||
|
||||
Ce binaire est un outil de consolidation de corpus. Ce n’est pas un scanner blockchain.
|
||||
|
||||
## Forme recommandée
|
||||
|
||||
Créer un nouveau package workspace :
|
||||
Nouvel ordre validé :
|
||||
|
||||
```text
|
||||
kb_tools/
|
||||
Cargo.toml
|
||||
src/bin/kb_db_merge.rs
|
||||
0.7.58 -> sqlite_db_transaction_merger
|
||||
0.7.59 -> demo4_program_surface_discovery
|
||||
```
|
||||
|
||||
Le binaire doit dépendre de `kb_lib` et réutiliser autant que possible les fonctions de schéma/setup/query existantes.
|
||||
|
||||
Alternative acceptable si le workspace préfère ce pattern :
|
||||
Utiliser :
|
||||
|
||||
```text
|
||||
kb_lib/src/bin/kb_db_merge.rs
|
||||
docs/prompts/PROMPT_0_7_58_sqlite_db_transaction_merger_binary.md
|
||||
```
|
||||
|
||||
Préférer `kb_tools` si cela évite de mélanger du code CLI-only dans `kb_lib`.
|
||||
|
||||
## Interface CLI
|
||||
|
||||
Commande suggérée :
|
||||
|
||||
```bash
|
||||
cargo run -p kb_tools --bin kb_db_merge -- \
|
||||
--output ./merged_corpus.db \
|
||||
--input ./raydium.db \
|
||||
--input ./pump.db \
|
||||
--input ./meteora.db \
|
||||
--mode raw-corpus \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
Options requises ou utiles :
|
||||
|
||||
```text
|
||||
--output <path>
|
||||
--input <path> répétable
|
||||
--mode <mode>
|
||||
--dry-run optionnel
|
||||
--replace-output optionnel ; sinon refuser si output existe
|
||||
--source-label optionnel ou inféré depuis le nom de fichier
|
||||
--limit optionnel pour tests
|
||||
```
|
||||
|
||||
Modes initiaux :
|
||||
|
||||
```text
|
||||
raw-corpus
|
||||
full-copy-safe
|
||||
signatures-only
|
||||
```
|
||||
|
||||
## Mode `raw-corpus`
|
||||
|
||||
Mode par défaut recommandé.
|
||||
|
||||
Copier les données brutes canonique nécessaires au replay local sans RPC :
|
||||
|
||||
```text
|
||||
k_sol_chain_transactions
|
||||
k_sol_chain_instructions
|
||||
```
|
||||
|
||||
Si le schéma contient d’autres tables brutes/contextuelles strictement nécessaires, les inspecter avant de les ajouter. Ne copier que les tables sûres, sans duplication sémantique.
|
||||
|
||||
La base output doit ensuite pouvoir exécuter le replay/décodage local depuis les transactions persistées.
|
||||
|
||||
## Mode `full-copy-safe`
|
||||
|
||||
Mode optionnel pour plus tard.
|
||||
|
||||
Copier aussi des lignes décodées/matérialisées seulement si les foreign keys, versions de decoder et versions de schéma sont cohérentes.
|
||||
|
||||
Ne pas l’implémenter comme mode par défaut. Il est plus dangereux car les lignes matérialisées dépendent de la version du code.
|
||||
|
||||
## Mode `signatures-only`
|
||||
|
||||
Copier uniquement les signatures/slots dans une table de staging afin qu’un outil futur décide quoi importer/rejouer.
|
||||
|
||||
## Politique de déduplication
|
||||
|
||||
Identité primaire :
|
||||
|
||||
```text
|
||||
signature
|
||||
```
|
||||
|
||||
Règles :
|
||||
|
||||
- Une même signature dans plusieurs inputs doit produire une seule transaction dans l’output.
|
||||
- Si `meta_json`, `transaction_json`, `slot` ou `err_json` diffèrent pour une même signature, enregistrer un conflit ; ne pas écraser silencieusement.
|
||||
- Préférer la ligne brute la plus complète uniquement si le conflit est explicable et journalisé.
|
||||
- Conserver la provenance dans des tables d’audit de merge.
|
||||
|
||||
Tables suggérées :
|
||||
|
||||
```text
|
||||
k_sol_db_merge_sources
|
||||
k_sol_db_merge_transactions
|
||||
k_sol_db_merge_conflicts
|
||||
```
|
||||
|
||||
Champs source suggérés :
|
||||
|
||||
```text
|
||||
source_id
|
||||
source_path
|
||||
source_label
|
||||
source_schema_version
|
||||
source_created_at
|
||||
input_transaction_count
|
||||
copied_transaction_count
|
||||
skipped_duplicate_count
|
||||
conflict_count
|
||||
```
|
||||
|
||||
Pour chaque signature copiée :
|
||||
|
||||
```text
|
||||
signature
|
||||
slot
|
||||
source_id
|
||||
source_transaction_id
|
||||
copied_at
|
||||
copy_status
|
||||
conflict_status
|
||||
```
|
||||
|
||||
## Règles de sécurité
|
||||
|
||||
- Ne pas appeler RPC.
|
||||
- Ne pas backfiller.
|
||||
- Ne pas décoder DEX pendant le merge.
|
||||
- Ne pas matérialiser trades/candles pendant le merge.
|
||||
- Ne pas supposer une compatibilité de schéma sans inspection.
|
||||
- Ne pas utiliser `ATTACH` avec des chemins non validés/échappés.
|
||||
- Pas de `?`, `unwrap`, `expect` dans le code applicatif.
|
||||
- Les erreurs doivent être explicites, typées projet, et lisibles.
|
||||
- Le mode dry-run doit indiquer exactement ce qui serait copié, ignoré ou marqué conflictuel.
|
||||
|
||||
## Compatibilité de schéma
|
||||
|
||||
Le binaire doit inspecter `sqlite_master` et `PRAGMA table_info(...)` dans chaque DB input.
|
||||
|
||||
Tables requises pour `raw-corpus` :
|
||||
|
||||
```text
|
||||
k_sol_chain_transactions
|
||||
k_sol_chain_instructions
|
||||
```
|
||||
|
||||
Vérifier les colonnes requises par nom. Si une colonne optionnelle manque, l’indiquer clairement et continuer seulement si la copie reste suffisante pour un replay local fiable.
|
||||
|
||||
Si le schéma est incompatible, échouer proprement avec un résumé court.
|
||||
|
||||
## Stratégie de copie
|
||||
|
||||
Algorithme recommandé :
|
||||
|
||||
1. Ouvrir/créer la base output et initialiser le schéma courant via `kb_lib`.
|
||||
2. Pour chaque input DB :
|
||||
- inspecter le schéma ;
|
||||
- enregistrer la source ;
|
||||
- itérer les transactions par slot/signature ;
|
||||
- si la signature est absente de l’output, insérer la transaction et ses instructions enfants ;
|
||||
- si la signature existe, comparer les champs canoniques et ignorer ou enregistrer un conflit ;
|
||||
- préserver le mapping `source_transaction_id -> output_transaction_id` pour les instructions.
|
||||
3. Commit par batches.
|
||||
4. Imprimer un résumé final.
|
||||
|
||||
La taille de batch doit être configurable ou conservatrice.
|
||||
|
||||
## SQL de validation
|
||||
|
||||
Ajouter :
|
||||
|
||||
```text
|
||||
validation_sql/SQL_VALIDATION_DB_MERGE_0_7_59.sql
|
||||
```
|
||||
|
||||
Checks minimaux :
|
||||
|
||||
```sql
|
||||
-- duplicate signatures in output should be empty
|
||||
SELECT signature, COUNT(*)
|
||||
FROM k_sol_chain_transactions
|
||||
GROUP BY signature
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- orphan instructions should be empty
|
||||
SELECT ins.id
|
||||
FROM k_sol_chain_instructions ins
|
||||
LEFT JOIN k_sol_chain_transactions tx ON tx.id = ins.transaction_id
|
||||
WHERE tx.id IS NULL;
|
||||
|
||||
-- conflicts summary
|
||||
SELECT *
|
||||
FROM k_sol_db_merge_conflicts
|
||||
ORDER BY id DESC
|
||||
LIMIT 100;
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Ajouter des tests offline avec bases SQLite temporaires :
|
||||
|
||||
1. merge d’une DB vers un output vide ;
|
||||
2. merge de deux DBs avec signatures disjointes ;
|
||||
3. merge de deux DBs avec signature identique et contenu identique ;
|
||||
4. signature dupliquée avec conflit slot/meta et ligne conflict enregistrée ;
|
||||
5. instructions recopiées avec le nouveau `transaction_id` output ;
|
||||
6. dry-run sans écriture des lignes output ;
|
||||
7. table requise manquante -> erreur propre.
|
||||
|
||||
## Documentation
|
||||
|
||||
Mettre à jour :
|
||||
|
||||
```text
|
||||
README.md
|
||||
ROADMAP.md
|
||||
CHANGELOG.md
|
||||
validation_sql/SQL_VALIDATION_DB_MERGE_0_7_59.sql
|
||||
docs/reports/SQLITE_DB_TRANSACTION_MERGER_0_7_59.md
|
||||
```
|
||||
|
||||
## Résultat attendu
|
||||
|
||||
L’utilisateur peut consolider ses anciennes bases Raydium/Pump/Meteora dans une base output et relancer un replay local depuis les lignes brutes existantes, sans refaire les backfills RPC de signatures.
|
||||
|
||||
330
docs/prompts/PROMPT_0_7_59_demo4_program_surface_discovery.md
Normal file
330
docs/prompts/PROMPT_0_7_59_demo4_program_surface_discovery.md
Normal file
@@ -0,0 +1,330 @@
|
||||
<!-- file: docs/prompts/PROMPT_0_7_59_demo4_program_surface_discovery.md -->
|
||||
|
||||
# Prompt — 0.7.59 Demo4 Program Surface Discovery
|
||||
|
||||
## Contexte
|
||||
|
||||
Nous reprenons le workspace Rust `khadhroony-bobobot` après :
|
||||
|
||||
```text
|
||||
0.7.57 -> meteora_dlmm clos
|
||||
0.7.58 -> sqlite_db_transaction_merger + validation anti-régression cross-DEX
|
||||
```
|
||||
|
||||
Décision de planification : `demo4_program_surface_discovery` est déplacé de `0.7.58` vers `0.7.59` afin de permettre d’abord la création d’une base consolidée `final.db`.
|
||||
|
||||
Cette base consolidée doit permettre à Demo4 d’explorer :
|
||||
|
||||
```text
|
||||
program_id inconnus
|
||||
program_id connus avec discriminants non mappés
|
||||
Anchor logs Instruction non mappés
|
||||
Program data events non mappés
|
||||
upstream fallbacks
|
||||
layouts répétitifs d’accounts/data
|
||||
success/failed split
|
||||
```
|
||||
|
||||
## Objectif principal
|
||||
|
||||
Ajouter une fenêtre `Demo4` dans `kb_demo_app` et les requêtes `kb_lib` associées pour afficher les surfaces non prises en compte sans les promouvoir automatiquement.
|
||||
|
||||
Demo4 est un outil d’observation, de tri et de préparation de futures tranches de decoder. Il ne doit pas créer de trade, candle, pool, pair, fee, reward, admin ou lifecycle métier.
|
||||
|
||||
## Règles strictes
|
||||
|
||||
Demo4 doit être read-only par défaut.
|
||||
|
||||
Interdictions :
|
||||
|
||||
- ne pas matérialiser automatiquement ;
|
||||
- ne pas ajouter automatiquement de coverage comme supportée ;
|
||||
- ne pas marquer automatiquement un `program_id` comme connu ;
|
||||
- ne pas générer de decoder métier ;
|
||||
- ne pas promouvoir un upstream fallback vers un decoder local sans intervention humaine ;
|
||||
- ne pas masquer les failed transactions ;
|
||||
- ne pas supprimer les observations existantes.
|
||||
|
||||
Actions autorisées :
|
||||
|
||||
- lister ;
|
||||
- grouper ;
|
||||
- scorer ;
|
||||
- exporter Markdown/CSV/JSON ;
|
||||
- marquer manuellement un candidat comme `reviewed`, `ignored`, `accepted_for_future_decoder`, si table de statut ajoutée ;
|
||||
- ouvrir une signature dans Demo Pipeline 2 ou Demo3.
|
||||
|
||||
## Sources de données prioritaires
|
||||
|
||||
Demo4 doit exploiter la base consolidée construite par `0.7.58`, par exemple :
|
||||
|
||||
```text
|
||||
final.db
|
||||
final.next.db
|
||||
```
|
||||
|
||||
Cette base est issue de merges raw-corpus de bases dédiées :
|
||||
|
||||
```text
|
||||
pump_swap.db
|
||||
pump_fun.db
|
||||
pump_fees.db
|
||||
raydium_*.db
|
||||
meteora_*.db
|
||||
```
|
||||
|
||||
Le bénéfice attendu : détecter les surfaces inconnues et les régressions qui n’apparaissent pas dans une base DEX isolée.
|
||||
|
||||
## Surfaces à afficher
|
||||
|
||||
### 1. Program IDs non routés
|
||||
|
||||
Afficher les `program_id` présents dans `k_sol_chain_instructions` qui ne correspondent pas à une surface supportée ou connue.
|
||||
|
||||
Colonnes utiles :
|
||||
|
||||
```text
|
||||
program_id
|
||||
instruction_count
|
||||
tx_count
|
||||
success_count
|
||||
failed_count
|
||||
first_slot
|
||||
last_slot
|
||||
sample_signature
|
||||
sample_data_prefix
|
||||
sample_account_count
|
||||
```
|
||||
|
||||
### 2. Discriminators non mappés sur program id connu
|
||||
|
||||
Pour un `program_id` déjà supporté, afficher les discriminants inconnus ou encore en `instruction_audit`.
|
||||
|
||||
Colonnes utiles :
|
||||
|
||||
```text
|
||||
protocol_guess
|
||||
program_id
|
||||
discriminator_hex
|
||||
data_prefix
|
||||
instruction_count
|
||||
tx_count
|
||||
success_count
|
||||
failed_count
|
||||
sample_signature
|
||||
known_anchor_log_name si disponible
|
||||
upstream_match_status si disponible
|
||||
```
|
||||
|
||||
### 3. Anchor logs `Instruction: ...`
|
||||
|
||||
Extraire depuis `meta_json.logMessages` :
|
||||
|
||||
```text
|
||||
Program log: Instruction: <Name>
|
||||
```
|
||||
|
||||
Normaliser le nom :
|
||||
|
||||
```text
|
||||
InitializePresetParameterV2 -> initialize_preset_parameter_v2
|
||||
```
|
||||
|
||||
Calculer si possible :
|
||||
|
||||
```text
|
||||
sha256("global:<snake_case_name>")[0..8]
|
||||
```
|
||||
|
||||
Comparer au discriminator observé.
|
||||
|
||||
### 4. Program data / Anchor events non mappés
|
||||
|
||||
Lister les `Program data:` ou events Anchor observés mais non classés localement.
|
||||
|
||||
Colonnes utiles :
|
||||
|
||||
```text
|
||||
program_id
|
||||
anchor_event_discriminator_hex
|
||||
payload_size
|
||||
tx_count
|
||||
success_count
|
||||
failed_count
|
||||
sample_signature
|
||||
possible_event_name
|
||||
upstream_match_status
|
||||
```
|
||||
|
||||
### 5. Upstream fallbacks
|
||||
|
||||
Afficher les événements qui viennent encore de :
|
||||
|
||||
```text
|
||||
upstream_git.instruction_match
|
||||
upstream_git.event_match
|
||||
instruction_audit
|
||||
```
|
||||
|
||||
Demo4 doit aider à décider si l’entrée doit devenir :
|
||||
|
||||
```text
|
||||
local decoder
|
||||
coverage 0/0 conservée
|
||||
decoded-only justifié
|
||||
materialized admin/lifecycle/fee/reward/liquidity/orderbook
|
||||
ignored
|
||||
```
|
||||
|
||||
## UI attendue
|
||||
|
||||
Fichiers probables :
|
||||
|
||||
```text
|
||||
kb_demo_app/src/demo4.html
|
||||
kb_demo_app/src/demo4.ts
|
||||
kb_demo_app/src/main.ts
|
||||
kb_demo_app/src/shared/api.ts
|
||||
kb_demo_app/src/shared/types.ts
|
||||
```
|
||||
|
||||
Ajouter une fenêtre Tauri si nécessaire dans :
|
||||
|
||||
```text
|
||||
kb_demo_app/src-tauri/src/main.rs
|
||||
kb_demo_app/tauri.conf.json
|
||||
```
|
||||
|
||||
Organisation UI suggérée :
|
||||
|
||||
```text
|
||||
Filters:
|
||||
- program_id
|
||||
- protocol_guess
|
||||
- success/failed/all
|
||||
- minimum tx_count
|
||||
- known/unknown program
|
||||
- audit/upstream/unknown discriminator
|
||||
- date/slot range si disponible
|
||||
|
||||
Tabs:
|
||||
1. Unknown programs
|
||||
2. Known program unknown discriminators
|
||||
3. Anchor instruction logs
|
||||
4. Program data events
|
||||
5. Upstream fallbacks
|
||||
6. Regression suspects
|
||||
```
|
||||
|
||||
## API / services kb_lib suggérés
|
||||
|
||||
Créer des DTOs sous :
|
||||
|
||||
```text
|
||||
kb_lib/src/db/dtos/program_surface_candidate_summary.rs
|
||||
kb_lib/src/db/dtos/program_surface_candidate_sample.rs
|
||||
```
|
||||
|
||||
Créer des requêtes sous :
|
||||
|
||||
```text
|
||||
kb_lib/src/db/queries/program_surface_discovery.rs
|
||||
```
|
||||
|
||||
Créer éventuellement une façade :
|
||||
|
||||
```text
|
||||
kb_lib/src/program_surface_discovery.rs
|
||||
```
|
||||
|
||||
Rappel : si des requêtes DB sont ajoutées, mettre à jour les re-exports dans :
|
||||
|
||||
```text
|
||||
kb_lib/src/db.rs
|
||||
kb_lib/src/lib.rs
|
||||
```
|
||||
|
||||
## Table de statut optionnelle
|
||||
|
||||
Si utile, ajouter :
|
||||
|
||||
```text
|
||||
k_sol_program_surface_candidates
|
||||
```
|
||||
|
||||
Statuts possibles :
|
||||
|
||||
```text
|
||||
new
|
||||
reviewed
|
||||
accepted_for_future_decoder
|
||||
ignored
|
||||
promoted_manually
|
||||
rejected
|
||||
```
|
||||
|
||||
Ne jamais passer automatiquement à `promoted_manually`.
|
||||
|
||||
## Intégration avec 0.7.58
|
||||
|
||||
Demo4 doit intégrer explicitement la notion de source DB/merge si les tables `k_sol_db_merge_*` existent.
|
||||
|
||||
Exemples d’affichages utiles :
|
||||
|
||||
```text
|
||||
candidate observed in sources: pump_swap.db, pump_fees.db
|
||||
candidate observed only after final.db merge
|
||||
candidate correlated with replay decode failure
|
||||
candidate appears in transaction with multiple DEX protocols
|
||||
```
|
||||
|
||||
Objectif : rendre visibles les régressions cross-surface comme le cas PumpSwap/PumpFees.
|
||||
|
||||
## SQL de validation Demo4
|
||||
|
||||
Ajouter :
|
||||
|
||||
```text
|
||||
validation_sql/SQL_VALIDATION_DEMO4_PROGRAM_SURFACE_DISCOVERY_0_7_59.sql
|
||||
```
|
||||
|
||||
Checks :
|
||||
|
||||
```sql
|
||||
-- Demo4 queries should not create decoded/materialized rows
|
||||
SELECT COUNT(*) FROM k_sol_dex_decoded_events;
|
||||
SELECT COUNT(*) FROM k_sol_trade_events;
|
||||
|
||||
-- candidate status table should not auto-promote anything
|
||||
SELECT status, COUNT(*)
|
||||
FROM k_sol_program_surface_candidates
|
||||
GROUP BY status;
|
||||
```
|
||||
|
||||
Adapter selon schéma réel.
|
||||
|
||||
## Tests attendus
|
||||
|
||||
- requêtes unknown program sur fixture minimale ;
|
||||
- discriminants inconnus sur program connu ;
|
||||
- extraction Anchor `Instruction: Name` ;
|
||||
- hash Anchor discriminator ;
|
||||
- split success/failed ;
|
||||
- no write en mode read-only ;
|
||||
- export Markdown/CSV stable ;
|
||||
- table statut manuelle si implémentée.
|
||||
|
||||
## Critères de clôture 0.7.59
|
||||
|
||||
- `cargo test -p kb_lib` OK ;
|
||||
- `cargo test` UI/Tauri si existant OK ;
|
||||
- clippy `-D warnings` OK ;
|
||||
- Demo4 affiche les surfaces inconnues depuis `final.db` ;
|
||||
- aucun auto-decode métier ;
|
||||
- aucune auto-materialization ;
|
||||
- export exploitable pour préparer `0.7.60 meteora_damm_v1` ou une tranche future ;
|
||||
- README/ROADMAP/CHANGELOG mis à jour.
|
||||
|
||||
## Note finale
|
||||
|
||||
Demo4 ne remplace pas les prompts DEX. Il prépare les prochaines tranches en rendant les surfaces observées lisibles, priorisées et reproductibles.
|
||||
@@ -1,249 +1,18 @@
|
||||
<!-- file: docs/prompts/PROMPT_0_7_59_sqlite_db_transaction_merger_binary.md -->
|
||||
|
||||
# Prompt — 0.7.59 Binaire de fusion de bases SQLite transactionnelles
|
||||
# OBSOLETE — Prompt déplacé
|
||||
|
||||
## Contexte
|
||||
Ce prompt n'est plus la cible `0.7.59`.
|
||||
|
||||
Nous reprenons le workspace Rust `khadhroony-bobobot` après la clôture de `0.7.57 meteora_dlmm` et après/ou en parallèle de `0.7.58 demo4_program_surface_discovery`.
|
||||
|
||||
L’utilisateur possède plusieurs bases SQLite dédiées construites pendant les tranches Raydium, Pump et Meteora. L’objectif est d’éviter de rescanner/backfiller des signatures déjà présentes dans ces bases.
|
||||
|
||||
## Objectif
|
||||
|
||||
Ajouter un module binaire qui utilise `kb_lib` pour fusionner des données de corpus transactionnel depuis plusieurs fichiers SQLite `.db` vers une base SQLite de sortie unique.
|
||||
|
||||
Le merger doit copier suffisamment de données brutes transaction/instruction pour permettre un replay local dans la base consolidée, sans refaire les appels RPC.
|
||||
|
||||
Ce binaire est un outil de consolidation de corpus. Ce n’est pas un scanner blockchain.
|
||||
|
||||
## Forme recommandée
|
||||
|
||||
Créer un nouveau package workspace :
|
||||
Nouvel ordre validé :
|
||||
|
||||
```text
|
||||
kb_tools/
|
||||
Cargo.toml
|
||||
src/bin/kb_db_merge.rs
|
||||
0.7.58 -> sqlite_db_transaction_merger
|
||||
0.7.59 -> demo4_program_surface_discovery
|
||||
```
|
||||
|
||||
Le binaire doit dépendre de `kb_lib` et réutiliser autant que possible les fonctions de schéma/setup/query existantes.
|
||||
|
||||
Alternative acceptable si le workspace préfère ce pattern :
|
||||
Utiliser :
|
||||
|
||||
```text
|
||||
kb_lib/src/bin/kb_db_merge.rs
|
||||
docs/prompts/PROMPT_0_7_58_sqlite_db_transaction_merger_binary.md
|
||||
```
|
||||
|
||||
Préférer `kb_tools` si cela évite de mélanger du code CLI-only dans `kb_lib`.
|
||||
|
||||
## Interface CLI
|
||||
|
||||
Commande suggérée :
|
||||
|
||||
```bash
|
||||
cargo run -p kb_tools --bin kb_db_merge -- \
|
||||
--output ./merged_corpus.db \
|
||||
--input ./raydium.db \
|
||||
--input ./pump.db \
|
||||
--input ./meteora.db \
|
||||
--mode raw-corpus \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
Options requises ou utiles :
|
||||
|
||||
```text
|
||||
--output <path>
|
||||
--input <path> répétable
|
||||
--mode <mode>
|
||||
--dry-run optionnel
|
||||
--replace-output optionnel ; sinon refuser si output existe
|
||||
--source-label optionnel ou inféré depuis le nom de fichier
|
||||
--limit optionnel pour tests
|
||||
```
|
||||
|
||||
Modes initiaux :
|
||||
|
||||
```text
|
||||
raw-corpus
|
||||
full-copy-safe
|
||||
signatures-only
|
||||
```
|
||||
|
||||
## Mode `raw-corpus`
|
||||
|
||||
Mode par défaut recommandé.
|
||||
|
||||
Copier les données brutes canonique nécessaires au replay local sans RPC :
|
||||
|
||||
```text
|
||||
k_sol_chain_transactions
|
||||
k_sol_chain_instructions
|
||||
```
|
||||
|
||||
Si le schéma contient d’autres tables brutes/contextuelles strictement nécessaires, les inspecter avant de les ajouter. Ne copier que les tables sûres, sans duplication sémantique.
|
||||
|
||||
La base output doit ensuite pouvoir exécuter le replay/décodage local depuis les transactions persistées.
|
||||
|
||||
## Mode `full-copy-safe`
|
||||
|
||||
Mode optionnel pour plus tard.
|
||||
|
||||
Copier aussi des lignes décodées/matérialisées seulement si les foreign keys, versions de decoder et versions de schéma sont cohérentes.
|
||||
|
||||
Ne pas l’implémenter comme mode par défaut. Il est plus dangereux car les lignes matérialisées dépendent de la version du code.
|
||||
|
||||
## Mode `signatures-only`
|
||||
|
||||
Copier uniquement les signatures/slots dans une table de staging afin qu’un outil futur décide quoi importer/rejouer.
|
||||
|
||||
## Politique de déduplication
|
||||
|
||||
Identité primaire :
|
||||
|
||||
```text
|
||||
signature
|
||||
```
|
||||
|
||||
Règles :
|
||||
|
||||
- Une même signature dans plusieurs inputs doit produire une seule transaction dans l’output.
|
||||
- Si `meta_json`, `transaction_json`, `slot` ou `err_json` diffèrent pour une même signature, enregistrer un conflit ; ne pas écraser silencieusement.
|
||||
- Préférer la ligne brute la plus complète uniquement si le conflit est explicable et journalisé.
|
||||
- Conserver la provenance dans des tables d’audit de merge.
|
||||
|
||||
Tables suggérées :
|
||||
|
||||
```text
|
||||
k_sol_db_merge_sources
|
||||
k_sol_db_merge_transactions
|
||||
k_sol_db_merge_conflicts
|
||||
```
|
||||
|
||||
Champs source suggérés :
|
||||
|
||||
```text
|
||||
source_id
|
||||
source_path
|
||||
source_label
|
||||
source_schema_version
|
||||
source_created_at
|
||||
input_transaction_count
|
||||
copied_transaction_count
|
||||
skipped_duplicate_count
|
||||
conflict_count
|
||||
```
|
||||
|
||||
Pour chaque signature copiée :
|
||||
|
||||
```text
|
||||
signature
|
||||
slot
|
||||
source_id
|
||||
source_transaction_id
|
||||
copied_at
|
||||
copy_status
|
||||
conflict_status
|
||||
```
|
||||
|
||||
## Règles de sécurité
|
||||
|
||||
- Ne pas appeler RPC.
|
||||
- Ne pas backfiller.
|
||||
- Ne pas décoder DEX pendant le merge.
|
||||
- Ne pas matérialiser trades/candles pendant le merge.
|
||||
- Ne pas supposer une compatibilité de schéma sans inspection.
|
||||
- Ne pas utiliser `ATTACH` avec des chemins non validés/échappés.
|
||||
- Pas de `?`, `unwrap`, `expect` dans le code applicatif.
|
||||
- Les erreurs doivent être explicites, typées projet, et lisibles.
|
||||
- Le mode dry-run doit indiquer exactement ce qui serait copié, ignoré ou marqué conflictuel.
|
||||
|
||||
## Compatibilité de schéma
|
||||
|
||||
Le binaire doit inspecter `sqlite_master` et `PRAGMA table_info(...)` dans chaque DB input.
|
||||
|
||||
Tables requises pour `raw-corpus` :
|
||||
|
||||
```text
|
||||
k_sol_chain_transactions
|
||||
k_sol_chain_instructions
|
||||
```
|
||||
|
||||
Vérifier les colonnes requises par nom. Si une colonne optionnelle manque, l’indiquer clairement et continuer seulement si la copie reste suffisante pour un replay local fiable.
|
||||
|
||||
Si le schéma est incompatible, échouer proprement avec un résumé court.
|
||||
|
||||
## Stratégie de copie
|
||||
|
||||
Algorithme recommandé :
|
||||
|
||||
1. Ouvrir/créer la base output et initialiser le schéma courant via `kb_lib`.
|
||||
2. Pour chaque input DB :
|
||||
- inspecter le schéma ;
|
||||
- enregistrer la source ;
|
||||
- itérer les transactions par slot/signature ;
|
||||
- si la signature est absente de l’output, insérer la transaction et ses instructions enfants ;
|
||||
- si la signature existe, comparer les champs canoniques et ignorer ou enregistrer un conflit ;
|
||||
- préserver le mapping `source_transaction_id -> output_transaction_id` pour les instructions.
|
||||
3. Commit par batches.
|
||||
4. Imprimer un résumé final.
|
||||
|
||||
La taille de batch doit être configurable ou conservatrice.
|
||||
|
||||
## SQL de validation
|
||||
|
||||
Ajouter :
|
||||
|
||||
```text
|
||||
validation_sql/SQL_VALIDATION_DB_MERGE_0_7_59.sql
|
||||
```
|
||||
|
||||
Checks minimaux :
|
||||
|
||||
```sql
|
||||
-- duplicate signatures in output should be empty
|
||||
SELECT signature, COUNT(*)
|
||||
FROM k_sol_chain_transactions
|
||||
GROUP BY signature
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- orphan instructions should be empty
|
||||
SELECT ins.id
|
||||
FROM k_sol_chain_instructions ins
|
||||
LEFT JOIN k_sol_chain_transactions tx ON tx.id = ins.transaction_id
|
||||
WHERE tx.id IS NULL;
|
||||
|
||||
-- conflicts summary
|
||||
SELECT *
|
||||
FROM k_sol_db_merge_conflicts
|
||||
ORDER BY id DESC
|
||||
LIMIT 100;
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Ajouter des tests offline avec bases SQLite temporaires :
|
||||
|
||||
1. merge d’une DB vers un output vide ;
|
||||
2. merge de deux DBs avec signatures disjointes ;
|
||||
3. merge de deux DBs avec signature identique et contenu identique ;
|
||||
4. signature dupliquée avec conflit slot/meta et ligne conflict enregistrée ;
|
||||
5. instructions recopiées avec le nouveau `transaction_id` output ;
|
||||
6. dry-run sans écriture des lignes output ;
|
||||
7. table requise manquante -> erreur propre.
|
||||
|
||||
## Documentation
|
||||
|
||||
Mettre à jour :
|
||||
|
||||
```text
|
||||
README.md
|
||||
ROADMAP.md
|
||||
CHANGELOG.md
|
||||
validation_sql/SQL_VALIDATION_DB_MERGE_0_7_59.sql
|
||||
docs/reports/SQLITE_DB_TRANSACTION_MERGER_0_7_59.md
|
||||
```
|
||||
|
||||
## Résultat attendu
|
||||
|
||||
L’utilisateur peut consolider ses anciennes bases Raydium/Pump/Meteora dans une base output et relancer un replay local depuis les lignes brutes existantes, sans refaire les backfills RPC de signatures.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- file: docs/prompts/prompt_name_0_7_60_meteora_damm_next_dex.md -->
|
||||
<!-- file: docs/prompts/PROMPT_0_7_60_meteora_damm_next_dex.md -->
|
||||
|
||||
# Prompt — 0.7.60 Meteora DAMM v1 puis DAMM v2
|
||||
|
||||
@@ -34,14 +34,16 @@ Ne pas partager la classification de decoder d’une manière qui masque les dis
|
||||
|
||||
## Contexte
|
||||
|
||||
`0.7.57 meteora_dlmm` est clos. Les tranches intermédiaires planifiées sont :
|
||||
`0.7.57 meteora_dlmm` est clos.
|
||||
|
||||
Les tranches intermédiaires planifiées avant DAMM sont maintenant :
|
||||
|
||||
```text
|
||||
0.7.58 -> demo4_program_surface_discovery
|
||||
0.7.59 -> sqlite_db_transaction_merger
|
||||
0.7.58 -> sqlite_db_transaction_merger + validation anti-régression cross-DEX
|
||||
0.7.59 -> demo4_program_surface_discovery
|
||||
```
|
||||
|
||||
La prochaine tranche DEX doit reprendre la famille Meteora après DLMM/DBC, en commençant par DAMM v1.
|
||||
Raison du changement : la régression PumpSwap/PumpFees a montré qu’une base DEX isolée ne suffit pas. Les futurs décodeurs doivent être validés à la fois sur une base dédiée et sur une base consolidée `final.db` issue du merger.
|
||||
|
||||
Surfaces Meteora connues :
|
||||
|
||||
@@ -53,6 +55,27 @@ meteora_damm_v2 -> 0.7.61
|
||||
meteora_vault -> plus tard, surface séparée
|
||||
```
|
||||
|
||||
## Workflow obligatoire pour 0.7.60+
|
||||
|
||||
Pour chaque nouvelle tranche DEX :
|
||||
|
||||
1. Créer une base vide dédiée au DEX.
|
||||
2. Backfiller le corpus DAMM v1 dans cette base dédiée.
|
||||
3. Développer decoder/materializer sur cette base.
|
||||
4. Clore les gates SQL DAMM v1 localement.
|
||||
5. Fusionner la base dédiée dans une copie de `final.db` via le merger `0.7.58`.
|
||||
6. Rejouer la base fusionnée avec :
|
||||
|
||||
```text
|
||||
metadata=no
|
||||
skipDexDecode=no
|
||||
forceDexDecode=yes
|
||||
deferInstructionObservations=yes
|
||||
```
|
||||
|
||||
7. Lancer les validations globales anti-régression Pump/Raydium/Meteora.
|
||||
8. Ne considérer la tranche close que si la base dédiée et la base fusionnée sont propres.
|
||||
|
||||
## Cible 0.7.60 — meteora_damm_v1
|
||||
|
||||
Program id depuis la roadmap :
|
||||
@@ -88,6 +111,7 @@ Règles :
|
||||
- Utiliser `k_sol_fee_event_amounts` pour les legs fee fiables.
|
||||
- Utiliser la récupération inner SPL transfer seulement avec allowlist explicite et tests.
|
||||
- Conserver toutes les entrées IDL/upstream non observées dans la coverage avec `0/0` et proof status clair.
|
||||
- Un decoder ne doit pas aborter toute une transaction multi-protocoles pour un payload secondaire tronqué/incompatible si l’entrée peut être ignorée proprement.
|
||||
|
||||
## Sources à vérifier
|
||||
|
||||
@@ -105,6 +129,7 @@ Pinax/substreams-solana-idls
|
||||
0xfnzero sol-parser-sdk / solana-streamer
|
||||
ancien code local et rapports historiques du projet
|
||||
samples Solscan / backfills Demo3
|
||||
surfaces visibles dans Demo4 si 0.7.59 est disponible
|
||||
```
|
||||
|
||||
Ne pas considérer les anciens résultats `0.7.36` ou `0.7.46` comme clôture finale. Revalider depuis le schéma courant et les règles actuelles de matérialisation.
|
||||
@@ -118,16 +143,12 @@ Ne pas considérer les anciens résultats `0.7.36` ou `0.7.46` comme clôture fi
|
||||
5. Décoder toutes les instructions et tous les events Anchor disponibles.
|
||||
6. Matérialiser swaps, liquidity, lifecycle, fee, reward/admin/orderbook si applicable.
|
||||
7. Ajouter des tests synthétiques pour chaque instruction/event IDL, y compris non observé.
|
||||
8. Ajouter le SQL de validation.
|
||||
9. Rejouer sur base fraîche avec :
|
||||
|
||||
```text
|
||||
skipDexDecode=no
|
||||
forceDexDecode=yes
|
||||
deferInstructionObservations=yes
|
||||
```
|
||||
|
||||
10. Clore seulement si les gates SQL sont propres.
|
||||
8. Ajouter le SQL de validation DAMM v1.
|
||||
9. Rejouer sur base fraîche dédiée avec `forceDexDecode=yes`.
|
||||
10. Fusionner la base dédiée vers `final.next.db` via `kb_db_merge`.
|
||||
11. Rejouer `final.next.db`.
|
||||
12. Lancer les gates cross-DEX.
|
||||
13. Clore seulement si les gates locales et globales sont propres.
|
||||
|
||||
## Gates SQL obligatoires
|
||||
|
||||
@@ -147,6 +168,16 @@ coverage logical duplicates -> vide
|
||||
watchlist sans backlog meteora_damm_v1
|
||||
```
|
||||
|
||||
Ajouter les gates anti-régression globales après merge :
|
||||
|
||||
```text
|
||||
successful non-materialized Pump/Raydium/Meteora expliqué ou vide
|
||||
pure instruction_audit inattendu -> vide
|
||||
decoder errors cross-surface -> vide
|
||||
failed tx materialized -> vide
|
||||
trade/candle non-swap -> vide
|
||||
```
|
||||
|
||||
## Cible 0.7.61 — meteora_damm_v2
|
||||
|
||||
Program id depuis la roadmap :
|
||||
@@ -167,6 +198,8 @@ event mapping
|
||||
synthetic tests
|
||||
SQL validation
|
||||
rapport
|
||||
base dédiée
|
||||
validation final.db merge
|
||||
```
|
||||
|
||||
## Livrables attendus pour 0.7.60
|
||||
@@ -180,6 +213,7 @@ kb_lib/src/dex_event_classification.rs
|
||||
kb_lib/src/instruction_observation_index.rs
|
||||
kb_lib/src/non_trade_event_materialization.rs
|
||||
validation_sql/SQL_VALIDATION_METEORA_DAMM_V1_0_7_60.sql
|
||||
validation_sql/SQL_VALIDATION_CROSS_DEX_REGRESSION_AFTER_DAMM_V1_0_7_60.sql
|
||||
docs/reports/METEORA_DAMM_V1_EVENT_COVERAGE_REPORT.md
|
||||
```
|
||||
|
||||
|
||||
@@ -34,14 +34,16 @@ Ne pas partager la classification de decoder d’une manière qui masque les dis
|
||||
|
||||
## Contexte
|
||||
|
||||
`0.7.57 meteora_dlmm` est clos. Les tranches intermédiaires planifiées sont :
|
||||
`0.7.57 meteora_dlmm` est clos.
|
||||
|
||||
Les tranches intermédiaires planifiées avant DAMM sont maintenant :
|
||||
|
||||
```text
|
||||
0.7.58 -> demo4_program_surface_discovery
|
||||
0.7.59 -> sqlite_db_transaction_merger
|
||||
0.7.58 -> sqlite_db_transaction_merger + validation anti-régression cross-DEX
|
||||
0.7.59 -> demo4_program_surface_discovery
|
||||
```
|
||||
|
||||
La prochaine tranche DEX doit reprendre la famille Meteora après DLMM/DBC, en commençant par DAMM v1.
|
||||
Raison du changement : la régression PumpSwap/PumpFees a montré qu’une base DEX isolée ne suffit pas. Les futurs décodeurs doivent être validés à la fois sur une base dédiée et sur une base consolidée `final.db` issue du merger.
|
||||
|
||||
Surfaces Meteora connues :
|
||||
|
||||
@@ -53,6 +55,27 @@ meteora_damm_v2 -> 0.7.61
|
||||
meteora_vault -> plus tard, surface séparée
|
||||
```
|
||||
|
||||
## Workflow obligatoire pour 0.7.60+
|
||||
|
||||
Pour chaque nouvelle tranche DEX :
|
||||
|
||||
1. Créer une base vide dédiée au DEX.
|
||||
2. Backfiller le corpus DAMM v1 dans cette base dédiée.
|
||||
3. Développer decoder/materializer sur cette base.
|
||||
4. Clore les gates SQL DAMM v1 localement.
|
||||
5. Fusionner la base dédiée dans une copie de `final.db` via le merger `0.7.58`.
|
||||
6. Rejouer la base fusionnée avec :
|
||||
|
||||
```text
|
||||
metadata=no
|
||||
skipDexDecode=no
|
||||
forceDexDecode=yes
|
||||
deferInstructionObservations=yes
|
||||
```
|
||||
|
||||
7. Lancer les validations globales anti-régression Pump/Raydium/Meteora.
|
||||
8. Ne considérer la tranche close que si la base dédiée et la base fusionnée sont propres.
|
||||
|
||||
## Cible 0.7.60 — meteora_damm_v1
|
||||
|
||||
Program id depuis la roadmap :
|
||||
@@ -88,6 +111,7 @@ Règles :
|
||||
- Utiliser `k_sol_fee_event_amounts` pour les legs fee fiables.
|
||||
- Utiliser la récupération inner SPL transfer seulement avec allowlist explicite et tests.
|
||||
- Conserver toutes les entrées IDL/upstream non observées dans la coverage avec `0/0` et proof status clair.
|
||||
- Un decoder ne doit pas aborter toute une transaction multi-protocoles pour un payload secondaire tronqué/incompatible si l’entrée peut être ignorée proprement.
|
||||
|
||||
## Sources à vérifier
|
||||
|
||||
@@ -105,6 +129,7 @@ Pinax/substreams-solana-idls
|
||||
0xfnzero sol-parser-sdk / solana-streamer
|
||||
ancien code local et rapports historiques du projet
|
||||
samples Solscan / backfills Demo3
|
||||
surfaces visibles dans Demo4 si 0.7.59 est disponible
|
||||
```
|
||||
|
||||
Ne pas considérer les anciens résultats `0.7.36` ou `0.7.46` comme clôture finale. Revalider depuis le schéma courant et les règles actuelles de matérialisation.
|
||||
@@ -118,16 +143,12 @@ Ne pas considérer les anciens résultats `0.7.36` ou `0.7.46` comme clôture fi
|
||||
5. Décoder toutes les instructions et tous les events Anchor disponibles.
|
||||
6. Matérialiser swaps, liquidity, lifecycle, fee, reward/admin/orderbook si applicable.
|
||||
7. Ajouter des tests synthétiques pour chaque instruction/event IDL, y compris non observé.
|
||||
8. Ajouter le SQL de validation.
|
||||
9. Rejouer sur base fraîche avec :
|
||||
|
||||
```text
|
||||
skipDexDecode=no
|
||||
forceDexDecode=yes
|
||||
deferInstructionObservations=yes
|
||||
```
|
||||
|
||||
10. Clore seulement si les gates SQL sont propres.
|
||||
8. Ajouter le SQL de validation DAMM v1.
|
||||
9. Rejouer sur base fraîche dédiée avec `forceDexDecode=yes`.
|
||||
10. Fusionner la base dédiée vers `final.next.db` via `kb_db_merge`.
|
||||
11. Rejouer `final.next.db`.
|
||||
12. Lancer les gates cross-DEX.
|
||||
13. Clore seulement si les gates locales et globales sont propres.
|
||||
|
||||
## Gates SQL obligatoires
|
||||
|
||||
@@ -147,6 +168,16 @@ coverage logical duplicates -> vide
|
||||
watchlist sans backlog meteora_damm_v1
|
||||
```
|
||||
|
||||
Ajouter les gates anti-régression globales après merge :
|
||||
|
||||
```text
|
||||
successful non-materialized Pump/Raydium/Meteora expliqué ou vide
|
||||
pure instruction_audit inattendu -> vide
|
||||
decoder errors cross-surface -> vide
|
||||
failed tx materialized -> vide
|
||||
trade/candle non-swap -> vide
|
||||
```
|
||||
|
||||
## Cible 0.7.61 — meteora_damm_v2
|
||||
|
||||
Program id depuis la roadmap :
|
||||
@@ -167,6 +198,8 @@ event mapping
|
||||
synthetic tests
|
||||
SQL validation
|
||||
rapport
|
||||
base dédiée
|
||||
validation final.db merge
|
||||
```
|
||||
|
||||
## Livrables attendus pour 0.7.60
|
||||
@@ -180,6 +213,7 @@ kb_lib/src/dex_event_classification.rs
|
||||
kb_lib/src/instruction_observation_index.rs
|
||||
kb_lib/src/non_trade_event_materialization.rs
|
||||
validation_sql/SQL_VALIDATION_METEORA_DAMM_V1_0_7_60.sql
|
||||
validation_sql/SQL_VALIDATION_CROSS_DEX_REGRESSION_AFTER_DAMM_V1_0_7_60.sql
|
||||
docs/reports/METEORA_DAMM_V1_EVENT_COVERAGE_REPORT.md
|
||||
```
|
||||
|
||||
|
||||
@@ -1484,7 +1484,16 @@ fn parse_pump_fees_instruction_data(
|
||||
let decoded_fields_result = decode_borsh_fields(&decoded[8..], spec.fields);
|
||||
let decoded_fields = match decoded_fields_result {
|
||||
Ok(decoded_fields) => decoded_fields,
|
||||
Err(error) => return Err(error),
|
||||
Err(error) => {
|
||||
tracing::debug!(
|
||||
decoder = "pump_fees",
|
||||
discriminator_hex = bytes_to_hex(discriminator.as_slice()),
|
||||
payload_size = decoded.len().saturating_sub(8),
|
||||
error = %error,
|
||||
"ignoring malformed pump_fees instruction payload"
|
||||
);
|
||||
return Ok(None);
|
||||
},
|
||||
};
|
||||
return Ok(Some(PumpFeesInstructionData {
|
||||
spec,
|
||||
@@ -1544,7 +1553,16 @@ fn decode_pump_fees_anchor_event_from_base64(
|
||||
let fields_result = decode_borsh_fields(&payload[8..], spec.fields);
|
||||
let fields_json = match fields_result {
|
||||
Ok(fields_json) => fields_json,
|
||||
Err(error) => return Err(error),
|
||||
Err(error) => {
|
||||
tracing::debug!(
|
||||
decoder = "pump_fees",
|
||||
discriminator_hex = bytes_to_hex(discriminator.as_slice()),
|
||||
payload_size = payload.len().saturating_sub(8),
|
||||
error = %error,
|
||||
"ignoring malformed pump_fees anchor event payload"
|
||||
);
|
||||
return Ok(None);
|
||||
},
|
||||
};
|
||||
return Ok(Some(PumpFeesAnchorEventData {
|
||||
spec,
|
||||
@@ -2422,6 +2440,37 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_truncated_known_instruction_payload() {
|
||||
let mut data = std::vec::Vec::new();
|
||||
data.extend_from_slice(&super::PUMP_FEES_GET_FEES_DISCRIMINATOR);
|
||||
let encoded = bs58::encode(data).into_string();
|
||||
let data_json = serde_json::json!(encoded).to_string();
|
||||
let decoded_result = super::parse_pump_fees_instruction_data(Some(&data_json));
|
||||
match decoded_result {
|
||||
Ok(None) => {},
|
||||
Ok(Some(decoded)) => panic!("unexpected decoded truncated instruction: {}", decoded.spec.name),
|
||||
Err(error) => panic!("truncated instruction should be ignored: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_truncated_known_anchor_event_payload() {
|
||||
let data = pump_fees_anchor_event_prefix(
|
||||
&super::PUMP_FEES_SOCIAL_FEE_PDA_CLAIMED_DISCRIMINATOR,
|
||||
);
|
||||
let encoded = {
|
||||
use base64::Engine as _;
|
||||
base64::engine::general_purpose::STANDARD.encode(data)
|
||||
};
|
||||
let decoded_result = super::decode_pump_fees_anchor_event_from_base64(encoded.as_str());
|
||||
match decoded_result {
|
||||
Ok(None) => {},
|
||||
Ok(Some(decoded)) => panic!("unexpected decoded truncated anchor event: {}", decoded.spec.name),
|
||||
Err(error) => panic!("truncated anchor event should be ignored: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
fn pump_fees_anchor_event_prefix(event_discriminator: &[u8; 8]) -> std::vec::Vec<u8> {
|
||||
let mut data = std::vec::Vec::new();
|
||||
data.extend_from_slice(&super::PUMP_FEES_ANCHOR_SELF_CPI_LOG_DISCRIMINATOR);
|
||||
|
||||
@@ -1865,10 +1865,31 @@ fn build_anchor_event_payload(
|
||||
}
|
||||
|
||||
fn is_materializable_pump_swap_anchor_event(event_name: &str) -> bool {
|
||||
if event_name == "claim_token_incentives_event" {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return matches!(
|
||||
event_name,
|
||||
"admin_set_coin_creator_event"
|
||||
| "admin_update_token_incentives_event"
|
||||
| "buy_event"
|
||||
| "claim_cashback_event"
|
||||
| "claim_token_incentives_event"
|
||||
| "close_user_volume_accumulator_event"
|
||||
| "collect_coin_creator_fee_event"
|
||||
| "create_config_event"
|
||||
| "create_pool_event"
|
||||
| "deposit_event"
|
||||
| "disable_event"
|
||||
| "extend_account_event"
|
||||
| "init_user_volume_accumulator_event"
|
||||
| "migrate_pool_coin_creator_event"
|
||||
| "reserved_fee_recipients_event"
|
||||
| "sell_event"
|
||||
| "set_bonding_curve_coin_creator_event"
|
||||
| "set_metaplex_coin_creator_event"
|
||||
| "sync_user_volume_accumulator_event"
|
||||
| "update_admin_event"
|
||||
| "update_fee_config_event"
|
||||
| "withdraw_event"
|
||||
);
|
||||
}
|
||||
|
||||
fn add_anchor_event_aliases(
|
||||
@@ -3622,7 +3643,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pump_swap_sync_user_volume_accumulator_anchor_event_is_decoded_audit_only_when_present() {
|
||||
fn pump_swap_sync_user_volume_accumulator_anchor_event_is_materializable_reward_when_present() {
|
||||
let decoder = crate::PumpSwapDecoder::new();
|
||||
let encoded = make_pump_swap_anchor_event_base64_from_fields(
|
||||
super::PUMP_SWAP_SYNC_USER_VOLUME_ACCUMULATOR_EVENT_DISCRIMINATOR,
|
||||
@@ -3646,14 +3667,10 @@ mod tests {
|
||||
found_anchor_event = true;
|
||||
assert_eq!(
|
||||
event.payload_json.get("anchorEventAuditOnly"),
|
||||
Some(&serde_json::Value::Bool(true))
|
||||
);
|
||||
assert_eq!(
|
||||
event.payload_json.get("skipCatalogReason"),
|
||||
Some(&serde_json::Value::String(
|
||||
"pump_swap_anchor_event_audit_only".to_string()
|
||||
))
|
||||
Some(&serde_json::Value::Bool(false))
|
||||
);
|
||||
assert_eq!(event.payload_json.get("skipCatalogReason"), None);
|
||||
assert_eq!(event.payload_json.get("skipRewardReason"), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3746,7 +3763,7 @@ mod tests {
|
||||
found_buy_anchor_event = true;
|
||||
assert_eq!(
|
||||
event.payload_json.get("anchorEventAuditOnly"),
|
||||
Some(&serde_json::Value::Bool(true))
|
||||
Some(&serde_json::Value::Bool(false))
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -440,6 +440,48 @@ fn is_meteora_dlmm_pool_lifecycle_event_kind(event_kind: &str) -> bool {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
fn is_pump_swap_anchor_swap_log_event_kind(event_kind: &str) -> bool {
|
||||
return matches!(event_kind, "pump_swap.buy_event" | "pump_swap.sell_event");
|
||||
}
|
||||
|
||||
fn is_pump_swap_admin_event_kind(event_kind: &str) -> bool {
|
||||
if !event_kind.starts_with("pump_swap.") {
|
||||
return false;
|
||||
}
|
||||
if is_pump_swap_anchor_swap_log_event_kind(event_kind) {
|
||||
return false;
|
||||
}
|
||||
return matches!(
|
||||
event_kind,
|
||||
"pump_swap.admin_set_coin_creator"
|
||||
| "pump_swap.admin_set_coin_creator_event"
|
||||
| "pump_swap.admin_update_token_incentives"
|
||||
| "pump_swap.admin_update_token_incentives_event"
|
||||
| "pump_swap.create_config"
|
||||
| "pump_swap.create_config_event"
|
||||
| "pump_swap.disable"
|
||||
| "pump_swap.disable_event"
|
||||
| "pump_swap.extend_account"
|
||||
| "pump_swap.extend_account_event"
|
||||
| "pump_swap.migrate_pool_coin_creator"
|
||||
| "pump_swap.migrate_pool_coin_creator_event"
|
||||
| "pump_swap.reserved_fee_recipients_event"
|
||||
| "pump_swap.set_bonding_curve_coin_creator_event"
|
||||
| "pump_swap.set_coin_creator"
|
||||
| "pump_swap.set_metaplex_coin_creator_event"
|
||||
| "pump_swap.set_reserved_fee_recipient"
|
||||
| "pump_swap.set_reserved_fee_recipients"
|
||||
| "pump_swap.toggle_cashback_enabled"
|
||||
| "pump_swap.toggle_mayhem_mode"
|
||||
| "pump_swap.update_admin"
|
||||
| "pump_swap.update_admin_event"
|
||||
| "pump_swap.update_buyback_config"
|
||||
| "pump_swap.update_fee_config"
|
||||
| "pump_swap.update_fee_config_event"
|
||||
);
|
||||
}
|
||||
|
||||
fn is_meteora_dlmm_admin_event_kind(event_kind: &str) -> bool {
|
||||
if !event_kind.starts_with("meteora_dlmm.") {
|
||||
return false;
|
||||
@@ -807,6 +849,9 @@ pub fn is_dex_orderbook_event_kind(event_kind: &str) -> bool {
|
||||
|
||||
/// Returns true for pool, pair, launch, mint, burn or migration lifecycle events.
|
||||
pub fn is_dex_pool_lifecycle_event_kind(event_kind: &str) -> bool {
|
||||
if is_pump_swap_anchor_swap_log_event_kind(event_kind) {
|
||||
return true;
|
||||
}
|
||||
if is_meteora_dlmm_pool_lifecycle_event_kind(event_kind) {
|
||||
return true;
|
||||
}
|
||||
@@ -1053,6 +1098,9 @@ pub fn is_dex_admin_event_kind(event_kind: &str) -> bool {
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if is_pump_swap_admin_event_kind(event_kind) {
|
||||
return true;
|
||||
}
|
||||
if event_kind.starts_with("pump_fees.")
|
||||
&& (event_kind.contains("authority")
|
||||
|| event_kind.contains("admin")
|
||||
@@ -1839,4 +1887,23 @@ mod tests {
|
||||
crate::UPSTREAM_REGISTRY_INSTRUCTION_MATCH_EVENT_KIND
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_pump_swap_anchor_swap_logs_as_lifecycle_not_trade() {
|
||||
assert!(super::is_dex_pool_lifecycle_event_kind("pump_swap.buy_event"));
|
||||
assert!(super::is_dex_pool_lifecycle_event_kind("pump_swap.sell_event"));
|
||||
assert!(!super::is_dex_trade_event_kind("pump_swap.buy_event"));
|
||||
assert!(!super::is_dex_trade_event_kind("pump_swap.sell_event"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_pump_swap_admin_instructions_and_events_explicitly() {
|
||||
assert!(super::is_dex_admin_event_kind("pump_swap.update_fee_config"));
|
||||
assert!(super::is_dex_admin_event_kind("pump_swap.update_fee_config_event"));
|
||||
assert!(super::is_dex_admin_event_kind("pump_swap.set_coin_creator"));
|
||||
assert!(super::is_dex_admin_event_kind("pump_swap.extend_account_event"));
|
||||
assert!(!super::is_dex_admin_event_kind("pump_swap.buy_event"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1920,10 +1920,10 @@ fn infer_pump_swap_expected_db_target(
|
||||
if entry_name == "buy" || entry_name == "sell" || entry_name == "buy_exact_quote_in" {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_TRADE_EVENTS.to_string());
|
||||
}
|
||||
if entry_name.starts_with("observed_unknown_") {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string());
|
||||
if entry_name == "buy_event" || entry_name == "sell_event" {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_POOL_LIFECYCLE_EVENTS.to_string());
|
||||
}
|
||||
if entry_name.ends_with("_event") && entry_name != "claim_token_incentives_event" {
|
||||
if entry_name.starts_with("observed_unknown_") {
|
||||
return Some(crate::DexEventCoverageEntryDto::DB_TARGET_DECODED_EVENTS_ONLY.to_string());
|
||||
}
|
||||
if entry_name == "deposit"
|
||||
@@ -1998,7 +1998,7 @@ fn infer_pump_swap_event_family(
|
||||
return Some("swap".to_string());
|
||||
}
|
||||
if entry_name == "buy_event" || entry_name == "sell_event" {
|
||||
return Some("swap_event_audit".to_string());
|
||||
return Some("swap_log".to_string());
|
||||
}
|
||||
if entry_name == "deposit" || entry_name == "deposit_event" {
|
||||
return Some("liquidity_add".to_string());
|
||||
@@ -3962,4 +3962,41 @@ mod tests {
|
||||
};
|
||||
assert!(!rows.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pump_swap_anchor_events_have_materialized_targets() {
|
||||
assert_eq!(
|
||||
super::infer_pump_swap_expected_db_target("buy_event", crate::ENTRY_KIND_EVENT),
|
||||
Some(crate::DexEventCoverageEntryDto::DB_TARGET_POOL_LIFECYCLE_EVENTS.to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_pump_swap_expected_db_target("sell_event", crate::ENTRY_KIND_EVENT),
|
||||
Some(crate::DexEventCoverageEntryDto::DB_TARGET_POOL_LIFECYCLE_EVENTS.to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_pump_swap_expected_db_target(
|
||||
"collect_coin_creator_fee_event",
|
||||
crate::ENTRY_KIND_EVENT,
|
||||
),
|
||||
Some(crate::DexEventCoverageEntryDto::DB_TARGET_FEE_EVENTS.to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_pump_swap_expected_db_target("update_fee_config_event", crate::ENTRY_KIND_EVENT),
|
||||
Some(crate::DexEventCoverageEntryDto::DB_TARGET_POOL_ADMIN_EVENTS.to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pump_swap_anchor_swap_events_are_swap_logs() {
|
||||
assert_eq!(
|
||||
super::infer_pump_swap_event_family("buy_event", crate::ENTRY_KIND_EVENT),
|
||||
Some("swap_log".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
super::infer_pump_swap_event_family("sell_event", crate::ENTRY_KIND_EVENT),
|
||||
Some("swap_log".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -99,6 +99,71 @@ fn should_attempt_meteora_dbc_explicit_skip_materialization(
|
||||
return false;
|
||||
}
|
||||
|
||||
fn should_attempt_pump_swap_explicit_skip_materialization(
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
_payload: &serde_json::Value,
|
||||
) -> bool {
|
||||
if decoded_event.protocol_name != "pump_swap" {
|
||||
return false;
|
||||
}
|
||||
return is_pump_swap_known_non_trade_materialization_candidate(
|
||||
decoded_event.event_kind.as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
fn is_pump_swap_known_non_trade_materialization_candidate(event_kind: &str) -> bool {
|
||||
return matches!(
|
||||
event_kind,
|
||||
"pump_swap.admin_set_coin_creator"
|
||||
| "pump_swap.admin_set_coin_creator_event"
|
||||
| "pump_swap.admin_update_token_incentives"
|
||||
| "pump_swap.admin_update_token_incentives_event"
|
||||
| "pump_swap.buy_event"
|
||||
| "pump_swap.claim_cashback"
|
||||
| "pump_swap.claim_cashback_event"
|
||||
| "pump_swap.claim_token_incentives"
|
||||
| "pump_swap.claim_token_incentives_event"
|
||||
| "pump_swap.close_user_volume_accumulator"
|
||||
| "pump_swap.close_user_volume_accumulator_event"
|
||||
| "pump_swap.collect_coin_creator_fee"
|
||||
| "pump_swap.collect_coin_creator_fee_event"
|
||||
| "pump_swap.create_config"
|
||||
| "pump_swap.create_config_event"
|
||||
| "pump_swap.create_pool"
|
||||
| "pump_swap.create_pool_event"
|
||||
| "pump_swap.deposit"
|
||||
| "pump_swap.deposit_event"
|
||||
| "pump_swap.disable"
|
||||
| "pump_swap.disable_event"
|
||||
| "pump_swap.extend_account"
|
||||
| "pump_swap.extend_account_event"
|
||||
| "pump_swap.init_user_volume_accumulator"
|
||||
| "pump_swap.init_user_volume_accumulator_event"
|
||||
| "pump_swap.migrate_pool_coin_creator"
|
||||
| "pump_swap.migrate_pool_coin_creator_event"
|
||||
| "pump_swap.reserved_fee_recipients_event"
|
||||
| "pump_swap.sell_event"
|
||||
| "pump_swap.set_bonding_curve_coin_creator_event"
|
||||
| "pump_swap.set_coin_creator"
|
||||
| "pump_swap.set_metaplex_coin_creator_event"
|
||||
| "pump_swap.set_reserved_fee_recipient"
|
||||
| "pump_swap.set_reserved_fee_recipients"
|
||||
| "pump_swap.sync_user_volume_accumulator"
|
||||
| "pump_swap.sync_user_volume_accumulator_event"
|
||||
| "pump_swap.toggle_cashback_enabled"
|
||||
| "pump_swap.toggle_mayhem_mode"
|
||||
| "pump_swap.transfer_creator_fees_to_pump"
|
||||
| "pump_swap.transfer_creator_fees_to_pump_v2"
|
||||
| "pump_swap.update_admin"
|
||||
| "pump_swap.update_admin_event"
|
||||
| "pump_swap.update_buyback_config"
|
||||
| "pump_swap.update_fee_config"
|
||||
| "pump_swap.update_fee_config_event"
|
||||
| "pump_swap.withdraw"
|
||||
| "pump_swap.withdraw_event"
|
||||
);
|
||||
}
|
||||
|
||||
fn is_meteora_dbc_instruction_fee_materialization_candidate(event_kind: &str) -> bool {
|
||||
return matches!(
|
||||
event_kind,
|
||||
@@ -131,6 +196,37 @@ enum FeeAmountRecoveryPolicy {
|
||||
InnerSplTransfer,
|
||||
}
|
||||
|
||||
fn is_pump_swap_forced_pool_admin_materialization_candidate(event_kind: &str) -> bool {
|
||||
return matches!(
|
||||
event_kind,
|
||||
"pump_swap.admin_set_coin_creator"
|
||||
| "pump_swap.admin_set_coin_creator_event"
|
||||
| "pump_swap.admin_update_token_incentives"
|
||||
| "pump_swap.admin_update_token_incentives_event"
|
||||
| "pump_swap.create_config"
|
||||
| "pump_swap.create_config_event"
|
||||
| "pump_swap.disable"
|
||||
| "pump_swap.disable_event"
|
||||
| "pump_swap.extend_account"
|
||||
| "pump_swap.extend_account_event"
|
||||
| "pump_swap.migrate_pool_coin_creator"
|
||||
| "pump_swap.migrate_pool_coin_creator_event"
|
||||
| "pump_swap.reserved_fee_recipients_event"
|
||||
| "pump_swap.set_bonding_curve_coin_creator_event"
|
||||
| "pump_swap.set_coin_creator"
|
||||
| "pump_swap.set_metaplex_coin_creator_event"
|
||||
| "pump_swap.set_reserved_fee_recipient"
|
||||
| "pump_swap.set_reserved_fee_recipients"
|
||||
| "pump_swap.toggle_cashback_enabled"
|
||||
| "pump_swap.toggle_mayhem_mode"
|
||||
| "pump_swap.update_admin"
|
||||
| "pump_swap.update_admin_event"
|
||||
| "pump_swap.update_buyback_config"
|
||||
| "pump_swap.update_fee_config"
|
||||
| "pump_swap.update_fee_config_event"
|
||||
);
|
||||
}
|
||||
|
||||
fn fee_amount_recovery_policy_for_event_kind(event_kind: &str) -> FeeAmountRecoveryPolicy {
|
||||
return match event_kind {
|
||||
"pump_fees.crank_donation_fee_pda"
|
||||
@@ -271,7 +367,30 @@ impl NonTradeEventMaterializationService {
|
||||
continue;
|
||||
},
|
||||
};
|
||||
if is_anchor_event_audit_only(&payload) {
|
||||
if decoded_event.protocol_name == "pump_swap"
|
||||
&& is_pump_swap_forced_pool_admin_materialization_candidate(
|
||||
decoded_event.event_kind.as_str(),
|
||||
)
|
||||
{
|
||||
let materialized = self
|
||||
.materialize_pool_admin_event(
|
||||
&transaction,
|
||||
transaction_id,
|
||||
decoded_event,
|
||||
&payload,
|
||||
)
|
||||
.await;
|
||||
match materialized {
|
||||
Ok(was_materialized) => {
|
||||
if was_materialized {
|
||||
result.pool_admin_event_count += 1;
|
||||
}
|
||||
},
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if is_anchor_event_audit_only(decoded_event, &payload) {
|
||||
continue;
|
||||
}
|
||||
if should_skip_non_trade_event_due_to_explicit_reason(decoded_event, &payload)
|
||||
@@ -279,6 +398,7 @@ impl NonTradeEventMaterializationService {
|
||||
decoded_event,
|
||||
&payload,
|
||||
)
|
||||
&& !should_attempt_pump_swap_explicit_skip_materialization(decoded_event, &payload)
|
||||
{
|
||||
tracing::debug!(
|
||||
event_kind = %decoded_event.event_kind,
|
||||
@@ -297,6 +417,29 @@ impl NonTradeEventMaterializationService {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if decoded_event.protocol_name == "pump_swap"
|
||||
&& is_pump_swap_forced_pool_admin_materialization_candidate(
|
||||
decoded_event.event_kind.as_str(),
|
||||
)
|
||||
{
|
||||
let materialized = self
|
||||
.materialize_pool_admin_event(
|
||||
&transaction,
|
||||
transaction_id,
|
||||
decoded_event,
|
||||
&payload,
|
||||
)
|
||||
.await;
|
||||
match materialized {
|
||||
Ok(was_materialized) => {
|
||||
if was_materialized {
|
||||
result.pool_admin_event_count += 1;
|
||||
}
|
||||
},
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if crate::is_dex_pool_lifecycle_event_kind(decoded_event.event_kind.as_str()) {
|
||||
let cleanup_result =
|
||||
self.delete_stale_pool_admin_event_for_lifecycle(decoded_event).await;
|
||||
@@ -3481,10 +3624,21 @@ fn is_pump_fun_payload(payload: &serde_json::Value) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn is_anchor_event_audit_only(payload: &serde_json::Value) -> bool {
|
||||
fn is_anchor_event_audit_only(
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
payload: &serde_json::Value,
|
||||
) -> bool {
|
||||
if is_pump_fun_payload(payload) {
|
||||
return false;
|
||||
}
|
||||
if decoded_event.protocol_name == "pump_swap"
|
||||
&& is_pump_swap_known_non_trade_materialization_candidate(decoded_event.event_kind.as_str())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if is_pump_swap_materializable_anchor_payload(payload) {
|
||||
return false;
|
||||
}
|
||||
if let Some(object) = payload.as_object() {
|
||||
let flag = object.get("anchorEventAuditOnly");
|
||||
if let Some(flag) = flag {
|
||||
@@ -3502,6 +3656,26 @@ fn is_anchor_event_audit_only(payload: &serde_json::Value) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn is_pump_swap_materializable_anchor_payload(payload: &serde_json::Value) -> bool {
|
||||
if !is_pump_swap_payload(payload) {
|
||||
return false;
|
||||
}
|
||||
let event_kind = extract_first_string(payload, &["eventKind", "event_kind"]);
|
||||
let event_kind = match event_kind {
|
||||
Some(event_kind) => event_kind,
|
||||
None => return false,
|
||||
};
|
||||
return is_pump_swap_known_non_trade_materialization_candidate(event_kind.as_str());
|
||||
}
|
||||
|
||||
fn is_pump_swap_payload(payload: &serde_json::Value) -> bool {
|
||||
let decoder = extract_first_string(payload, &["decoder", "protocolName", "protocol_name"]);
|
||||
match decoder.as_deref() {
|
||||
Some("pump_swap") => return true,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
|
||||
fn transaction_has_effective_error(transaction: &crate::ChainTransactionDto) -> bool {
|
||||
let err_json = match transaction.err_json.as_ref() {
|
||||
Some(err_json) => err_json.trim(),
|
||||
@@ -3627,6 +3801,81 @@ fn extract_first_number_as_string(
|
||||
return None;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod pump_swap_cleanup_tests {
|
||||
fn make_decoded_event(event_kind: &str) -> crate::DexDecodedEventDto {
|
||||
return crate::DexDecodedEventDto::new(
|
||||
1,
|
||||
Some(2),
|
||||
"pump_swap".to_string(),
|
||||
crate::PUMP_SWAP_PROGRAM_ID.to_string(),
|
||||
event_kind.to_string(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
"{}".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pump_swap_known_non_trade_candidates_bypass_legacy_trade_skip_reason() {
|
||||
let decoded_event = make_decoded_event("pump_swap.update_fee_config");
|
||||
let payload = serde_json::json!({
|
||||
"decoder": "pump_swap",
|
||||
"eventKind": "pump_swap.update_fee_config",
|
||||
"skipAdminReason": "legacy_pump_swap_admin_skip",
|
||||
"skipTradeReason": "pump_swap_non_trade_or_incomplete_trade_instruction",
|
||||
"skipCandleReason": "pump_swap_non_trade_or_incomplete_trade_instruction"
|
||||
});
|
||||
assert!(super::should_skip_non_trade_event_due_to_explicit_reason(
|
||||
&decoded_event,
|
||||
&payload,
|
||||
));
|
||||
assert!(super::should_attempt_pump_swap_explicit_skip_materialization(
|
||||
&decoded_event,
|
||||
&payload,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pump_swap_forced_admin_candidates_include_fee_config_event() {
|
||||
assert!(super::is_pump_swap_forced_pool_admin_materialization_candidate(
|
||||
"pump_swap.update_fee_config_event"
|
||||
));
|
||||
assert!(super::is_pump_swap_forced_pool_admin_materialization_candidate(
|
||||
"pump_swap.migrate_pool_coin_creator_event"
|
||||
));
|
||||
assert!(!super::is_pump_swap_forced_pool_admin_materialization_candidate(
|
||||
"pump_swap.buy_event"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pump_swap_materializable_anchor_payload_is_not_audit_only() {
|
||||
let payload = serde_json::json!({
|
||||
"decoder": "pump_swap",
|
||||
"eventKind": "pump_swap.buy_event",
|
||||
"anchorEventAuditOnly": true
|
||||
});
|
||||
let decoded_event = crate::DexDecodedEventDto::new(
|
||||
1,
|
||||
Some(1),
|
||||
"pump_swap".to_string(),
|
||||
crate::PUMP_SWAP_PROGRAM_ID.to_string(),
|
||||
"pump_swap.buy_event".to_string(),
|
||||
Some("pool".to_string()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
payload.to_string(),
|
||||
);
|
||||
assert!(!super::is_anchor_event_audit_only(&decoded_event, &payload));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
||||
@@ -122,7 +122,27 @@ pub(crate) async fn load_trade_aggregation_decoded_event_context(
|
||||
};
|
||||
let pool = match pool_option {
|
||||
Some(pool) => pool,
|
||||
None => return Ok(None),
|
||||
None => {
|
||||
let materialized =
|
||||
crate::trade_aggregation_context::try_materialize_pump_swap_trade_pool(
|
||||
database,
|
||||
decoded_event,
|
||||
)
|
||||
.await;
|
||||
match materialized {
|
||||
Ok(true) => {
|
||||
let refreshed_result =
|
||||
crate::query_pools_get_by_address(database, pool_address.as_str()).await;
|
||||
match refreshed_result {
|
||||
Ok(Some(pool)) => pool,
|
||||
Ok(None) => return Ok(None),
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
},
|
||||
Ok(false) => return Ok(None),
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
},
|
||||
};
|
||||
let pool_id = match pool.id {
|
||||
Some(pool_id) => pool_id,
|
||||
@@ -140,7 +160,26 @@ pub(crate) async fn load_trade_aggregation_decoded_event_context(
|
||||
};
|
||||
let pair = match pair_option {
|
||||
Some(pair) => pair,
|
||||
None => return Ok(None),
|
||||
None => {
|
||||
let materialized =
|
||||
crate::trade_aggregation_context::try_materialize_pump_swap_trade_pool(
|
||||
database,
|
||||
decoded_event,
|
||||
)
|
||||
.await;
|
||||
match materialized {
|
||||
Ok(true) => {
|
||||
let refreshed_pair_result = crate::query_pairs_get_by_pool_id(database, pool_id).await;
|
||||
match refreshed_pair_result {
|
||||
Ok(Some(pair)) => pair,
|
||||
Ok(None) => return Ok(None),
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
},
|
||||
Ok(false) => return Ok(None),
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
},
|
||||
};
|
||||
let pair_id = match pair.id {
|
||||
Some(pair_id) => pair_id,
|
||||
@@ -195,6 +234,107 @@ pub(crate) async fn load_trade_aggregation_decoded_event_context(
|
||||
}));
|
||||
}
|
||||
|
||||
async fn try_materialize_pump_swap_trade_pool(
|
||||
database: &crate::Database,
|
||||
decoded_event: &crate::DexDecodedEventDto,
|
||||
) -> Result<bool, crate::Error> {
|
||||
if decoded_event.protocol_name != "pump_swap" {
|
||||
return Ok(false);
|
||||
}
|
||||
if !crate::is_dex_trade_event_kind(decoded_event.event_kind.as_str()) {
|
||||
return Ok(false);
|
||||
}
|
||||
if decoded_event.pool_account.is_none()
|
||||
|| decoded_event.token_a_mint.is_none()
|
||||
|| decoded_event.token_b_mint.is_none()
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
let dex_result = crate::query_dexs_get_by_code(database, decoded_event.protocol_name.as_str()).await;
|
||||
let dex = match dex_result {
|
||||
Ok(Some(dex)) => dex,
|
||||
Ok(None) => return Ok(false),
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let dex_id = match dex.id {
|
||||
Some(dex_id) => dex_id,
|
||||
None => return Ok(false),
|
||||
};
|
||||
let payload_result = serde_json::from_str::<serde_json::Value>(decoded_event.payload_json.as_str());
|
||||
let payload = match payload_result {
|
||||
Ok(payload) => payload,
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
let token_a_vault_address = crate::trade_aggregation_context::extract_payload_string(
|
||||
&payload,
|
||||
&["poolBaseTokenAccount", "pool_base_token_account", "baseVault", "base_vault"],
|
||||
);
|
||||
let token_b_vault_address = crate::trade_aggregation_context::extract_payload_string(
|
||||
&payload,
|
||||
&["poolQuoteTokenAccount", "pool_quote_token_account", "quoteVault", "quote_vault"],
|
||||
);
|
||||
let materialization_input_result =
|
||||
crate::dex_pool_materialization::DexPoolMaterializationInput::from_decoded_event(
|
||||
decoded_event,
|
||||
dex_id,
|
||||
crate::PoolKind::Amm,
|
||||
crate::PoolStatus::Active,
|
||||
crate::dex_pool_materialization::DexPoolTokenOrder::AlreadyBaseQuote,
|
||||
token_a_vault_address,
|
||||
token_b_vault_address,
|
||||
None,
|
||||
);
|
||||
let materialization_input = match materialization_input_result {
|
||||
Ok(materialization_input) => materialization_input,
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
let materialization_result =
|
||||
crate::dex_pool_materialization::materialize_dex_pool(database, &materialization_input).await;
|
||||
match materialization_result {
|
||||
Ok(_) => return Ok(true),
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_payload_string(
|
||||
payload: &serde_json::Value,
|
||||
candidate_keys: &[&str],
|
||||
) -> std::option::Option<std::string::String> {
|
||||
if let Some(object) = payload.as_object() {
|
||||
for candidate_key in candidate_keys {
|
||||
if let Some(value) = object.get(*candidate_key) {
|
||||
if let Some(text) = value.as_str() {
|
||||
let trimmed = text.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for nested_value in object.values() {
|
||||
let nested = crate::trade_aggregation_context::extract_payload_string(
|
||||
nested_value,
|
||||
candidate_keys,
|
||||
);
|
||||
if nested.is_some() {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(array) = payload.as_array() {
|
||||
for nested_value in array {
|
||||
let nested = crate::trade_aggregation_context::extract_payload_string(
|
||||
nested_value,
|
||||
candidate_keys,
|
||||
);
|
||||
if nested.is_some() {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn find_pool_token_vault_address_by_token_id(
|
||||
pool_tokens: &[crate::PoolTokenDto],
|
||||
token_id: i64,
|
||||
|
||||
@@ -81,6 +81,21 @@ pub(crate) async fn resolve_trade_amounts(
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if input.decoded_event.event_kind.starts_with("pump_swap.")
|
||||
&& (base_amount_raw.is_none() || quote_amount_raw.is_none())
|
||||
{
|
||||
let resolution_result =
|
||||
crate::trade_amount_resolution::apply_pump_swap_db_instruction_transfer_amount_fallback(
|
||||
input,
|
||||
&mut base_amount_raw,
|
||||
&mut quote_amount_raw,
|
||||
&mut resolved_trade_side,
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = resolution_result {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if input.decoded_event.event_kind.starts_with("pump_fun.")
|
||||
&& (base_amount_raw.is_none()
|
||||
|| quote_amount_raw.is_none()
|
||||
@@ -622,9 +637,240 @@ async fn apply_pump_swap_amount_fallbacks(
|
||||
);
|
||||
}
|
||||
}
|
||||
if price_quote_per_base.is_none() && base_amount_raw.is_some() && quote_amount_raw.is_some() {
|
||||
*price_quote_per_base =
|
||||
crate::trade_metric_update::compute_price_quote_per_base_from_raw_amounts_with_decimals(
|
||||
base_amount_raw.as_ref().map(std::string::String::as_str),
|
||||
quote_amount_raw.as_ref().map(std::string::String::as_str),
|
||||
input.base_token_decimals,
|
||||
input.quote_token_decimals,
|
||||
);
|
||||
tracing::debug!(
|
||||
event_kind = %input.decoded_event.event_kind,
|
||||
pool_account = ?input.decoded_event.pool_account,
|
||||
decoded_event_id = ?input.decoded_event.id,
|
||||
base_amount_raw = ?base_amount_raw,
|
||||
quote_amount_raw = ?quote_amount_raw,
|
||||
price_quote_per_base = ?price_quote_per_base,
|
||||
"pump_swap trade price computed from resolved raw amounts"
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
async fn apply_pump_swap_db_instruction_transfer_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>,
|
||||
resolved_trade_side: &mut std::option::Option<crate::SwapTradeSide>,
|
||||
) -> Result<(), crate::Error> {
|
||||
if base_amount_raw.is_some() && quote_amount_raw.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
let decoded_instruction_result = crate::trade_amount_resolution::load_decoded_instruction(
|
||||
input.database,
|
||||
input.decoded_event,
|
||||
)
|
||||
.await;
|
||||
let decoded_instruction = match decoded_instruction_result {
|
||||
Ok(Some(decoded_instruction)) => decoded_instruction,
|
||||
Ok(None) => return Ok(()),
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
|
||||
input.database,
|
||||
input.decoded_event.transaction_id,
|
||||
)
|
||||
.await;
|
||||
let instructions = match instructions_result {
|
||||
Ok(instructions) => instructions,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let payload_user_base_token_account =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["userBaseTokenAccount", "user_base_token_account"],
|
||||
);
|
||||
let payload_user_quote_token_account =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["userQuoteTokenAccount", "user_quote_token_account"],
|
||||
);
|
||||
let payload_pool_base_token_account =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["poolBaseTokenAccount", "pool_base_token_account", "baseVault", "base_vault"],
|
||||
);
|
||||
let payload_pool_quote_token_account =
|
||||
crate::trade_amount_resolution::extract_string_by_candidate_keys(
|
||||
input.payload,
|
||||
&["poolQuoteTokenAccount", "pool_quote_token_account", "quoteVault", "quote_vault"],
|
||||
);
|
||||
let pool_base_token_account = match input.base_vault_address {
|
||||
Some(base_vault_address) => Some(base_vault_address),
|
||||
None => payload_pool_base_token_account.as_deref(),
|
||||
};
|
||||
let pool_quote_token_account = match input.quote_vault_address {
|
||||
Some(quote_vault_address) => Some(quote_vault_address),
|
||||
None => payload_pool_quote_token_account.as_deref(),
|
||||
};
|
||||
let user_base_token_account = payload_user_base_token_account.as_deref();
|
||||
let user_quote_token_account = payload_user_quote_token_account.as_deref();
|
||||
let is_buy = input.decoded_event.event_kind.ends_with(".buy")
|
||||
|| input.decoded_event.event_kind.ends_with(".buy_exact_quote_in");
|
||||
let is_sell = input.decoded_event.event_kind.ends_with(".sell");
|
||||
if !is_buy && !is_sell {
|
||||
return Ok(());
|
||||
}
|
||||
let mut base_transfer_direction = None;
|
||||
let mut quote_transfer_direction = None;
|
||||
for instruction in &instructions {
|
||||
if !crate::trade_amount_resolution::instruction_is_inside_same_pump_swap_instruction_window(
|
||||
&decoded_instruction,
|
||||
instruction,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let parsed_transfer_result =
|
||||
crate::trade_amount_resolution::parse_transfer_checked_instruction(instruction);
|
||||
let parsed_transfer = match parsed_transfer_result {
|
||||
Ok(Some(parsed_transfer)) => parsed_transfer,
|
||||
Ok(None) => continue,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
if is_buy {
|
||||
if base_amount_raw.is_none()
|
||||
&& crate::trade_amount_resolution::string_option_equals(
|
||||
input.base_token_mint,
|
||||
parsed_transfer.mint.as_str(),
|
||||
)
|
||||
&& crate::trade_amount_resolution::account_option_equals(
|
||||
pool_base_token_account,
|
||||
parsed_transfer.source.as_str(),
|
||||
)
|
||||
&& crate::trade_amount_resolution::account_option_equals(
|
||||
user_base_token_account,
|
||||
parsed_transfer.destination.as_str(),
|
||||
)
|
||||
{
|
||||
*base_amount_raw = Some(parsed_transfer.amount_raw.clone());
|
||||
base_transfer_direction =
|
||||
Some(crate::trade_amount_resolution::VaultTransferDirection::OutOfVault);
|
||||
continue;
|
||||
}
|
||||
if quote_amount_raw.is_none()
|
||||
&& crate::trade_amount_resolution::string_option_equals(
|
||||
input.quote_token_mint,
|
||||
parsed_transfer.mint.as_str(),
|
||||
)
|
||||
&& crate::trade_amount_resolution::account_option_equals(
|
||||
user_quote_token_account,
|
||||
parsed_transfer.source.as_str(),
|
||||
)
|
||||
&& crate::trade_amount_resolution::account_option_equals(
|
||||
pool_quote_token_account,
|
||||
parsed_transfer.destination.as_str(),
|
||||
)
|
||||
{
|
||||
*quote_amount_raw = Some(parsed_transfer.amount_raw.clone());
|
||||
quote_transfer_direction =
|
||||
Some(crate::trade_amount_resolution::VaultTransferDirection::IntoVault);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if is_sell {
|
||||
if base_amount_raw.is_none()
|
||||
&& crate::trade_amount_resolution::string_option_equals(
|
||||
input.base_token_mint,
|
||||
parsed_transfer.mint.as_str(),
|
||||
)
|
||||
&& crate::trade_amount_resolution::account_option_equals(
|
||||
user_base_token_account,
|
||||
parsed_transfer.source.as_str(),
|
||||
)
|
||||
&& crate::trade_amount_resolution::account_option_equals(
|
||||
pool_base_token_account,
|
||||
parsed_transfer.destination.as_str(),
|
||||
)
|
||||
{
|
||||
*base_amount_raw = Some(parsed_transfer.amount_raw.clone());
|
||||
base_transfer_direction =
|
||||
Some(crate::trade_amount_resolution::VaultTransferDirection::IntoVault);
|
||||
continue;
|
||||
}
|
||||
if quote_amount_raw.is_none()
|
||||
&& crate::trade_amount_resolution::string_option_equals(
|
||||
input.quote_token_mint,
|
||||
parsed_transfer.mint.as_str(),
|
||||
)
|
||||
&& crate::trade_amount_resolution::account_option_equals(
|
||||
pool_quote_token_account,
|
||||
parsed_transfer.source.as_str(),
|
||||
)
|
||||
&& crate::trade_amount_resolution::account_option_equals(
|
||||
user_quote_token_account,
|
||||
parsed_transfer.destination.as_str(),
|
||||
)
|
||||
{
|
||||
*quote_amount_raw = Some(parsed_transfer.amount_raw.clone());
|
||||
quote_transfer_direction =
|
||||
Some(crate::trade_amount_resolution::VaultTransferDirection::OutOfVault);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if resolved_trade_side.is_none() {
|
||||
*resolved_trade_side =
|
||||
crate::trade_amount_resolution::infer_trade_side_from_transfer_directions(
|
||||
base_transfer_direction,
|
||||
quote_transfer_direction,
|
||||
);
|
||||
}
|
||||
if base_amount_raw.is_some() || quote_amount_raw.is_some() {
|
||||
tracing::debug!(
|
||||
event_kind = %input.decoded_event.event_kind,
|
||||
decoded_event_id = ?input.decoded_event.id,
|
||||
transaction_signature = %input.transaction.signature,
|
||||
base_amount_raw = ?base_amount_raw,
|
||||
quote_amount_raw = ?quote_amount_raw,
|
||||
resolved_trade_side = ?resolved_trade_side,
|
||||
"pump_swap trade amounts recovered from persisted instruction transfer window"
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fn instruction_is_inside_same_pump_swap_instruction_window(
|
||||
decoded_instruction: &crate::ChainInstructionDto,
|
||||
candidate_instruction: &crate::ChainInstructionDto,
|
||||
) -> bool {
|
||||
if candidate_instruction.transaction_id != decoded_instruction.transaction_id {
|
||||
return false;
|
||||
}
|
||||
if candidate_instruction.instruction_index != decoded_instruction.instruction_index {
|
||||
return false;
|
||||
}
|
||||
let candidate_inner_instruction_index = match candidate_instruction.inner_instruction_index {
|
||||
Some(candidate_inner_instruction_index) => candidate_inner_instruction_index,
|
||||
None => return false,
|
||||
};
|
||||
if let Some(decoded_inner_instruction_index) = decoded_instruction.inner_instruction_index {
|
||||
if candidate_inner_instruction_index <= decoded_inner_instruction_index {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn account_option_equals(left: std::option::Option<&str>, right: &str) -> bool {
|
||||
let left = match left {
|
||||
Some(left) => left,
|
||||
None => return false,
|
||||
};
|
||||
return crate::trade_amount_resolution::account_equals(left, right);
|
||||
}
|
||||
|
||||
fn apply_raydium_launchpad_amount_fallback(
|
||||
input: &crate::trade_amount_resolution::TradeAmountResolutionInput<'_>,
|
||||
base_amount_raw: &mut std::option::Option<std::string::String>,
|
||||
|
||||
@@ -240,11 +240,7 @@ pub(crate) fn extract_trade_amounts_from_instruction_token_transfers(
|
||||
Some(destination) => destination,
|
||||
None => continue,
|
||||
};
|
||||
let amount_option =
|
||||
crate::trade_solana_amounts::extract_scalar_as_string_by_candidate_keys(
|
||||
info,
|
||||
&["amount"],
|
||||
);
|
||||
let amount_option = crate::trade_solana_amounts::extract_spl_transfer_amount_raw(info);
|
||||
let amount = match amount_option {
|
||||
Some(amount) => amount,
|
||||
None => continue,
|
||||
@@ -418,6 +414,26 @@ pub(crate) fn compute_price_quote_per_base_with_decimals(
|
||||
return inferred.2;
|
||||
}
|
||||
|
||||
fn extract_spl_transfer_amount_raw(
|
||||
info: &serde_json::Value,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
let amount = crate::trade_solana_amounts::extract_scalar_as_string_by_candidate_keys(
|
||||
info,
|
||||
&["amount"],
|
||||
);
|
||||
if amount.is_some() {
|
||||
return amount;
|
||||
}
|
||||
let token_amount = match info.get("tokenAmount") {
|
||||
Some(token_amount) => token_amount,
|
||||
None => return None,
|
||||
};
|
||||
return crate::trade_solana_amounts::extract_scalar_as_string_by_candidate_keys(
|
||||
token_amount,
|
||||
&["amount"],
|
||||
);
|
||||
}
|
||||
|
||||
fn is_spl_token_transfer_instruction(instruction: &serde_json::Value) -> bool {
|
||||
let program_id_option = instruction.get("programId").and_then(|value| return value.as_str());
|
||||
if let Some(program_id) = program_id_option {
|
||||
@@ -854,6 +870,69 @@ mod tests {
|
||||
assert_eq!(amounts.2, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instruction_transfer_amounts_read_transfer_checked_token_amount() {
|
||||
let meta_json = serde_json::json!({
|
||||
"innerInstructions": [
|
||||
{
|
||||
"index": 7,
|
||||
"instructions": [
|
||||
{
|
||||
"programId": crate::SPL_TOKEN_PROGRAM_ID,
|
||||
"parsed": {
|
||||
"type": "transferChecked",
|
||||
"info": {
|
||||
"source": "UserQuote111",
|
||||
"destination": "QuoteVault111",
|
||||
"mint": "QuoteMint111",
|
||||
"tokenAmount": {
|
||||
"amount": "199900249",
|
||||
"decimals": 9,
|
||||
"uiAmountString": "0.199900249"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"programId": crate::SPL_TOKEN_PROGRAM_ID,
|
||||
"parsed": {
|
||||
"type": "transferChecked",
|
||||
"info": {
|
||||
"source": "BaseVault111",
|
||||
"destination": "UserBase111",
|
||||
"mint": "BaseMint111",
|
||||
"tokenAmount": {
|
||||
"amount": "437138968699",
|
||||
"decimals": 6,
|
||||
"uiAmountString": "437138.968699"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
let meta_json_text = meta_json.to_string();
|
||||
let result = super::extract_trade_amounts_from_instruction_token_transfers(
|
||||
Some(meta_json_text.as_str()),
|
||||
Some(7),
|
||||
Some("QuoteVault111"),
|
||||
Some("BaseVault111"),
|
||||
Some("UserQuote111"),
|
||||
Some("UserBase111"),
|
||||
Some("BaseVault111"),
|
||||
Some("QuoteVault111"),
|
||||
);
|
||||
let amounts = match result {
|
||||
Ok(amounts) => amounts,
|
||||
Err(error) => panic!("transferChecked tokenAmount extraction should succeed: {}", error),
|
||||
};
|
||||
assert_eq!(amounts.0, Some("437138968699".to_string()));
|
||||
assert_eq!(amounts.1, Some("199900249".to_string()));
|
||||
assert_eq!(amounts.2, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pump_fun_amounts_extract_token_delta_and_native_delta() {
|
||||
let transaction_json = serde_json::json!({
|
||||
|
||||
Reference in New Issue
Block a user