0.7.40
This commit is contained in:
@@ -69,4 +69,5 @@
|
||||
0.7.36 - Consolidation de la famille Meteora : corpus mixte `meteora_damm_v1`, `meteora_damm_v2`, `meteora_dbc` et `meteora_dlmm`, correction des discriminants DAMM v2 / DBC, validation du profil `0.7.36_meteora_family_consolidation`, et reclassement explicite des swaps DAMM v2 / DBC sans payload montant/prix en `non_actionable_trade` afin d’éviter tout trade/candle artificiel.
|
||||
0.7.37 - Première tranche metadata/catalog : ajout du profil `0.7.37_token_metadata_catalog_enrichment`, exposition des compteurs metadata dans diagnostics/validation et raccordement UI Demo Pipeline 2 sans rendre les metadata manquantes bloquantes.
|
||||
0.7.38 - Priorisation des metadata manquantes : ajout du profil `0.7.38_token_metadata_gap_prioritization`, samples `tokenMetadataGapSamples`, priorités tradable/quote/catalog, raccordement UI Demo Pipeline 2 et maintien du caractère non bloquant des metadata incomplètes.
|
||||
0.7.39 - Réorientation DEX-first : ajout du rôle stratégique `surfaceRole` dans la matrice DEX (`dex_effective`, `aggregator_router`, `launch_surface`, `to_verify`), profil `0.7.39_dex_first_effective_swap_surfaces`, ajout de `metaDAO` et `Printr` comme entrées `to_verify` sans `program_id`, maintien des launch surfaces en état différé et des invariants sans faux trade/candle ni program id fictif.
|
||||
0.7.39 - Réorientation DEX-first : distinction explicite des rôles `dex_effective`, `aggregator_router`, `launch_surface` et `to_verify` dans la matrice DEX, suppression de l’alias ambigu `raydium`, ajout de `metaDAO` et `Printr` comme surfaces à vérifier sans `program_id`, profil `0.7.39_dex_first_effective_swap_surfaces`, validation locale avec `blockingIssueCount = 0`, `actionableMissingTradeEventCount = 0` et `missingTradeEventCount = 0`.
|
||||
0.7.40 - Ajout de Demo3 pour la découverte on-chain de corpus DEX par `dex_code` / `program_id` via `getSignaturesForAddress` + `getTransaction`, extraction générique des mints observés, deltas SPL Token, comptes pool/state candidats, vaults candidats et comptes programme, ajout du backfill par signature dans Demo Pipeline 2, validation pratique sur Raydium AMM v4 avec instructions internes `675kPX...` persistées, et report de Demo4 après la première consolidation Raydium AMM v4.
|
||||
|
||||
@@ -8,7 +8,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.39"
|
||||
version = "0.7.40"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
|
||||
|
||||
39
README.md
39
README.md
@@ -4,7 +4,7 @@
|
||||
|
||||
`khadhroony-bobobot` est un workspace Rust destiné à la détection, au décodage, à l’analyse et, à terme, au trading semi-automatisé de tokens Solana.
|
||||
|
||||
Le README précédent décrivait surtout l’état `0.3.1`. Ce fichier reflète l’état de clôture `0.7.38-B` et la réorientation de travail `0.7.39 DEX-first` : le socle transport HTTP/WS, la résolution transactionnelle, le modèle SQLite, plusieurs connecteurs DEX, les candles, les signaux analytiques, la validation locale, la matrice DEX commune et les diagnostics de metadata prioritaires existent déjà. La prochaine phase donne la priorité aux DEX effectifs sur lesquels les swaps et événements de marché sont observables, avant de revenir aux launch surfaces.
|
||||
Le README précédent décrivait surtout l’état `0.3.1`. Ce fichier reflète l’état de clôture `0.7.40` et la réorientation de travail `0.7.41 Raydium AMM v4` : le socle transport HTTP/WS, la résolution transactionnelle, le modèle SQLite, plusieurs connecteurs DEX, les candles, les signaux analytiques, la validation locale, la matrice DEX commune et les diagnostics de metadata prioritaires existent déjà. La prochaine phase exploite les corpus on-chain obtenus via Demo3 et Demo Pipeline 2 pour consolider les DEX effectifs, en commençant par `raydium_amm_v4`, avant de revenir aux launch surfaces.
|
||||
|
||||
## 1. Objectif
|
||||
|
||||
@@ -114,7 +114,7 @@ La distinction de travail à partir de `0.7.39` est la suivante :
|
||||
|
||||
### 4.1. Matrice de travail
|
||||
|
||||
Depuis `0.7.29`, la matrice de support DEX est portée par `kb_lib/src/dex_support_matrix.rs`. Elle centralise le code interne, la famille, la version, le type technique de surface, le rôle stratégique de surface (`dex_effective`, `aggregator_router`, `launch_surface`, `to_verify`), les program ids vérifiés localement, le statut de support, les capacités actuelles et les raisons de skip.
|
||||
Depuis `0.7.29`, la matrice de support DEX est portée par `kb_lib/src/dex_support_matrix.rs`. Elle centralise le code interne, la famille, la version, le type de surface, les program ids vérifiés localement, le statut de support, les capacités actuelles et les raisons de skip.
|
||||
|
||||
Depuis `0.7.30`, les événements décodés reçoivent aussi une classification plus fine : `eventLifecycleKind`, `eventActionability` et `nonTradeUseful`. Cette classification sert aux diagnostics et prépare la matérialisation future des événements non-trade sans alimenter directement les trades/candles.
|
||||
|
||||
@@ -230,30 +230,27 @@ Les tests peuvent rester plus souples lorsque cela clarifie le test.
|
||||
|
||||
## 8. Priorité immédiate
|
||||
|
||||
La phase `0.7.38_token_metadata_gap_prioritization` est considérée comme close. La clôture `0.7.38-B` ajoute un registre local minimal de tokens connus : `WSOL`, `USDC`, `USDT`, `JUP`, `RAY`, `BONK`. `USDC` et `USDT` restent les seuls ajouts stable-quote ; `JUP`, `RAY` et `BONK` enrichissent seulement l’affichage metadata sans changer la classification de quote.
|
||||
Les phases `0.7.38`, `0.7.39` et `0.7.40` sont considérées comme closes lorsque les tests et validations locales passent.
|
||||
|
||||
La prochaine étape est maintenant `0.7.39_dex_first_effective_swap_surfaces`. Elle remplace la priorité précédemment donnée aux launch surfaces. L’objectif est de vérifier et consolider d’abord les vrais DEX de swap et leurs événements observables.
|
||||
État acquis :
|
||||
|
||||
Préconditions validées avant de reprendre le codage :
|
||||
- `0.7.38_token_metadata_gap_prioritization` : metadata manquantes priorisées et non bloquantes ;
|
||||
- `0.7.39_dex_first_effective_swap_surfaces` : matrice DEX-first, suppression de l’alias `raydium`, ajout de `metaDAO` et `Printr` en `to_verify`, invariants locaux maintenus ;
|
||||
- `0.7.40` : Demo3 découvre on-chain des signatures, mints, deltas et comptes candidats par DEX/program id, et Demo Pipeline 2 peut backfiller une signature précise.
|
||||
|
||||
1. les invariants locaux restent sains : `blockingIssueCount = 0`, `actionableMissingTradeEventCount = 0`, `missingTradeEventCount = 0` ;
|
||||
2. les profils `0.7.36_meteora_family_consolidation`, `0.7.37_token_metadata_catalog_enrichment` et `0.7.38_token_metadata_gap_prioritization` existent et passent sur le corpus local fourni ;
|
||||
3. les metadata manquantes restent non bloquantes ;
|
||||
4. les `tokenMetadataGapSamples` exposent une liste de travail priorisée ;
|
||||
5. le registre local minimal contient `SOL`, `WSOL`, `USDC`, `USDT`, `JUP`, `RAY` et `BONK` ;
|
||||
6. le backfill metadata peut traiter les tokens déjà présents en base et rafraîchir les `pair_symbol` sans recréer les objets de marché.
|
||||
La prochaine étape est maintenant `0.7.41_raydium_amm_v4_swap_decoder_v1`.
|
||||
|
||||
Objectifs `0.7.39+` :
|
||||
Objectifs immédiats :
|
||||
|
||||
- inventorier et vérifier les DEX principaux sur lesquels des swaps peuvent réellement se produire ;
|
||||
- rechercher ou confirmer les `program_id` inconnus sans en inventer ;
|
||||
- ajouter `metaDAO` et `Printr` comme DEX à vérifier, notamment via DEX Screener et corpus local ;
|
||||
- couvrir, par DEX, les swaps, liquidités, lifecycle, fees, rewards, admin/config, burns/mints utiles et autres événements non-trade prouvés ;
|
||||
- ajouter `Demo3` pour rechercher les pools/paires/signatures par DEX ou `program_id` ;
|
||||
- ajouter `Demo4` pour interroger DEX Screener et comparer les résultats avec la base locale ;
|
||||
- préparer des démos spécialisées pour les launch surfaces seulement après stabilisation des DEX effectifs ;
|
||||
- ajouter une démo temps réel type `Demo10` avec start/stop du client WebSocket, souscriptions aux programmes DEX et écriture en base via le pipeline existant ;
|
||||
- passer ensuite à la création de wallets, aux fonctions wallet et aux transferts de fonds avant toute logique de swap/trading.
|
||||
- utiliser le corpus Raydium AMM v4 obtenu via Demo3 et backfill signature ;
|
||||
- décoder les inner instructions `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8`, y compris lorsqu’elles sont appelées par Jupiter ou un autre routeur top-level ;
|
||||
- extraire les comptes pool/state, authority, vaults et comptes utilisateur lorsque le layout observé est compatible ;
|
||||
- dériver les montants à partir des deltas SPL Token ou transferts instruction-scoped ;
|
||||
- produire `tradeCandidate=true` seulement si les mints et montants sont exploitables ;
|
||||
- conserver les routes ambiguës, failed transactions et swaps sans montants fiables comme non actionnables ;
|
||||
- ne créer aucun trade, metric ou candle sans payload exploitable.
|
||||
|
||||
`Demo4` est volontairement reportée à une version ultérieure. Pour la suite immédiate, Demo3 et Demo Pipeline 2 suffisent à produire le corpus nécessaire à la consolidation Raydium AMM v4.
|
||||
|
||||
Les launch surfaces restent importantes, mais elles sont reportées après la consolidation des DEX effectifs. Elles ne doivent pas générer de faux trades/candles ni de `program_id` fictif.
|
||||
|
||||
|
||||
146
ROADMAP.md
146
ROADMAP.md
@@ -787,7 +787,7 @@ Contraintes :
|
||||
Réalisé :
|
||||
|
||||
- ajouter `kb_lib/src/dex_support_matrix.rs` comme source commune de metadata DEX/surfaces ;
|
||||
- exposer pour chaque entrée : code interne, famille, version, type technique de surface, rôle stratégique de surface, program id connu ou à vérifier, support actuel, statut, confiance, raisons de skip et activation catalogue ;
|
||||
- exposer pour chaque entrée : code interne, famille, version, type de surface, program id connu ou à vérifier, support actuel, statut, confiance, raisons de skip et activation catalogue ;
|
||||
- raccorder `dex_catalog`, `transaction_classification` et `protocol_candidate_recording` à cette matrice ;
|
||||
- ajouter le profil `0.7.29_multi_dex_matrix_baseline` ;
|
||||
- exposer la matrice dans le rapport de validation local ;
|
||||
@@ -949,30 +949,69 @@ Objectif : remplacer la priorité précédemment donnée aux launch surfaces par
|
||||
|
||||
Réalisé :
|
||||
|
||||
- modifier la matrice DEX pour distinguer explicitement les rôles `dex_effective`, `aggregator_router`, `launch_surface`, `to_verify` ;
|
||||
- vérifier les DEX de swap principaux déjà connus : `pump_swap`, `raydium_cpmm`, `raydium_clmm`, `raydium_amm_v4`, `raydium_stable_swap`, `meteora_dlmm`, `meteora_damm_v1`, `meteora_damm_v2`, `meteora_dbc`, `orca_whirlpools`, `fluxbeam`, `dexlab` ;
|
||||
- ajouter `metaDAO` et `Printr` comme entrées `to_verify` dans la matrice, sans `program_id` tant qu’ils ne sont pas prouvés ;
|
||||
- rechercher ou confirmer les `program_id` inconnus depuis les corpus locaux, les protocol candidates, DEX Screener, les explorateurs et les transactions résolues ;
|
||||
- ne promouvoir aucune entrée vers `partial` ou `supported` sans corpus transactionnel vérifiable ;
|
||||
- conserver les invariants `0.7.36` à `0.7.38` : aucun faux trade, aucune fausse candle, aucun événement non price-action transformé en trade/candle.
|
||||
- modification de la matrice DEX pour distinguer explicitement les rôles de surface : `dex_effective`, `aggregator_router`, `launch_surface`, `to_verify` ;
|
||||
- suppression de l’alias ambigu `raydium` comme code DEX autonome ; `raydium` reste uniquement une famille, avec `raydium_amm_v4` comme surface legacy explicite ;
|
||||
- ajout de `metaDAO` et `Printr` comme entrées `to_verify` sans `program_id` inventé ;
|
||||
- conservation des DEX de swap principaux déjà connus dans la matrice : `pump_swap`, `raydium_cpmm`, `raydium_clmm`, `raydium_amm_v4`, `raydium_stable_swap`, `meteora_dlmm`, `meteora_damm_v1`, `meteora_damm_v2`, `meteora_dbc`, `orca_whirlpools`, `fluxbeam`, `dexlab` ;
|
||||
- maintien des launch surfaces comme surfaces reportées et non prioritaires ;
|
||||
- ajout du profil `0.7.39_dex_first_effective_swap_surfaces` ;
|
||||
- validation locale confirmée avec `validationPassed = true`, `blockingIssueCount = 0`, `actionableMissingTradeEventCount = 0` et `missingTradeEventCount = 0` ;
|
||||
- confirmation par corpus local initial que Raydium CLMM est observé, tandis que Raydium AMM v4 et Stable Swap ne sont pas encore exploitables sans constitution de corpus dédiée.
|
||||
|
||||
Résultat attendu : une matrice DEX réorientée vers les surfaces de swap effectives, avec les launch surfaces explicitement reportées.
|
||||
Décision : `0.7.39` est clos. La suite immédiate ne doit pas commencer par un décodeur Raydium AMM v4 sans corpus. Il faut d’abord ajouter les outils de découverte on-chain et de backfill ciblé afin d’obtenir des signatures, pools/state accounts, token mints et instructions exploitables.
|
||||
|
||||
### 6.072. Version `0.7.40` — Raydium effectif : AMM v4, Stable Swap, CPMM, CLMM
|
||||
Objectif : consolider la famille Raydium côté DEX effectifs avant les launch surfaces Raydium.
|
||||
### 6.072. Version `0.7.40` — Demo3 on-chain discovery et backfill par signature
|
||||
Objectif : ajouter les outils de constitution de corpus nécessaires avant de consolider les décodeurs DEX incomplets.
|
||||
|
||||
Réalisé :
|
||||
|
||||
- ajout de `Demo3` dans `kb_demo_app` pour rechercher on-chain à partir d’un `dex_code` et/ou d’un `program_id` ;
|
||||
- utilisation de la chaîne `getSignaturesForAddress(program_id)` puis `getTransaction(signature)` pour récupérer des transactions récentes liées à un programme DEX ;
|
||||
- extraction générique, indépendante des décodeurs DEX existants, de preuves on-chain : `observedTokenMints`, `tokenBalanceDeltas`, `candidatePoolAccounts`, `candidateTokenVaultAccounts` et `candidateProgramAccounts` ;
|
||||
- distinction explicite entre `verifiedPoolAddress` et comptes candidats : un compte candidat n’est pas promu en pool vérifié sans décodage/layout/corpus fiable ;
|
||||
- conservation de `metaDAO` et `Printr` comme surfaces à vérifier sans `program_id` ;
|
||||
- ajout du backfill par signature dans Demo Pipeline 2, en complément du backfill par token mint et pool address ;
|
||||
- réutilisation du pipeline existant pour le backfill signature : résolution transactionnelle, projection `k_sol_chain_transactions` / `k_sol_chain_instructions`, décodage DEX existant, détection, matérialisation non-trade, trades, candles et classification ;
|
||||
- validation pratique sur `raydium_amm_v4` : les signatures et inner instructions associées au programme `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8` sont maintenant persistées et inspectables ;
|
||||
- observation de patterns Raydium AMM v4 récurrents dans les instructions projetées : `accounts_json[1]` comme candidat pool/state, `accounts_json[2]` comme autorité Raydium AMM, `accounts_json[3]` et `accounts_json[4]` comme vaults candidats, avec les comptes utilisateur en fin d’instruction ;
|
||||
- maintien des invariants : aucune transaction failed ne produit `trade_events`, metrics ou candles ; aucun candidat sans montants exploitables ne devient trade/candle ; aucun `program_id` n’est inventé.
|
||||
|
||||
Décision : `0.7.40` est clos. `Demo3` et Demo Pipeline 2 suffisent pour constituer le corpus nécessaire à la suite immédiate. `Demo4` est décalée à une version ultérieure, car la priorité est maintenant d’utiliser le corpus on-chain local pour consolider `raydium_amm_v4`.
|
||||
|
||||
### 6.073. Version `0.7.41` — Raydium AMM v4 swap decoder v1
|
||||
Objectif : ajouter un premier décodeur fiable pour les swaps Raydium AMM v4 observés dans le corpus constitué avec Demo3 et Demo Pipeline 2.
|
||||
|
||||
À faire :
|
||||
|
||||
- vérifier `raydium_cpmm` et `raydium_clmm` comme références déjà supportées ;
|
||||
- rechercher des pools réellement rattachés au programme AMM v4 `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8` ;
|
||||
- vérifier l’usage réel de `raydium_stable_swap` et du programme `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h` ;
|
||||
- distinguer clairement `raydium_amm_v4`, `raydium_cpmm`, `raydium_clmm`, `raydium_router`, `raydium_stable_swap`, `raydium_launchlab` et `raydium_launchpad` ;
|
||||
- identifier les instructions swap, initialize/create pool, liquidité, fees/admin réellement observées ;
|
||||
- ajouter des requêtes diagnostics par `program_id`, `accounts_json`, `data_json`, signature et pool address ;
|
||||
- documenter les limites si le corpus Raydium legacy reste faible.
|
||||
- créer ou compléter le décodeur `raydium_amm_v4` pour les instructions de swap AMM v4 ;
|
||||
- traiter explicitement les inner instructions dont `program_id = 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8`, notamment lorsqu’elles sont appelées via Jupiter ou un autre routeur top-level ;
|
||||
- conserver dans le payload décodé les informations de routage utiles : `routeSource`, `topLevelProgram`, `innerInstruction`, `instructionIndex`, `innerInstructionIndex` ;
|
||||
- extraire les comptes selon le layout observé lorsque le format est compatible : token program, pool/state candidat, authority, vault A, vault B, comptes intermédiaires, comptes utilisateur ;
|
||||
- utiliser les deltas SPL Token et/ou les transferts inner instruction-scoped pour dériver les montants `base_amount_raw` et `quote_amount_raw` ;
|
||||
- marquer `eventActionability = trade_candidate` seulement si les montants et les mints sont exploitables ;
|
||||
- classer en `non_actionable_trade` les swaps ou routes dont les montants sont absents, ambigus, multi-hop non isolables ou issus de transactions failed ;
|
||||
- matérialiser `trade_events`, metrics et candles uniquement pour les transactions OK avec payload de montants exploitable ;
|
||||
- matérialiser ou mettre à jour pools/paires/listings uniquement lorsque les comptes permettent un rattachement fiable ;
|
||||
- ajouter des tests de non-régression sur les signatures et patterns Raydium AMM v4 observés localement ;
|
||||
- maintenir `blockingIssueCount = 0`, `actionableMissingTradeEventCount = 0`, `missingTradeEventCount = 0` après replay/validation.
|
||||
|
||||
### 6.073. Version `0.7.41` — Meteora effectif : DLMM, DAMM v1/v2, DBC
|
||||
Objectif : compléter la famille Meteora en couvrant les événements réellement utiles au DEX effectif.
|
||||
Corpus de travail initial : comptes candidats tels que `48yaEzz1JSHoghWYg3RGE31srN66ubfPgm4Wc5556YTG`, `SJmR8rJgzzCi4sPjGnrNsqY4akQb3jn5nsxZBhyEifC`, `EMFHrMra2GepQK9KtkQ9C65zxqdDCpQM2uKySPyNEFDA`, `58oQChx4yWmvKdwLLZzBi4ChoCc2fqCUWBkwMihLYQo2`, et les signatures associées issues de Demo3.
|
||||
|
||||
### 6.074. Version `0.7.42` — Raydium effectif : famille Raydium consolidée
|
||||
Objectif : consolider la famille Raydium après le premier décodeur AMM v4.
|
||||
|
||||
À faire :
|
||||
|
||||
- vérifier `raydium_cpmm` et `raydium_clmm` comme références déjà supportées et éviter toute régression ;
|
||||
- étendre ou stabiliser `raydium_amm_v4` après le décodeur v1 : swaps directs, routes Jupiter, pools/state accounts récurrents, vaults et mints ;
|
||||
- vérifier l’usage réel de `raydium_stable_swap` et du programme `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h` uniquement si un corpus local exploitable existe ;
|
||||
- distinguer clairement `raydium_amm_v4`, `raydium_cpmm`, `raydium_clmm`, `raydium_router`, `raydium_stable_swap`, `raydium_launchlab` et `raydium_launchpad` ;
|
||||
- identifier les instructions initialize/create pool, liquidité, fees/admin réellement observées ;
|
||||
- ajouter ou renforcer les diagnostics par `program_id`, `accounts_json`, `data_json`, signature et pool address ;
|
||||
- ne pas ajouter de trade/candle Stable Swap ou Router sans payload de montants exploitable.
|
||||
|
||||
### 6.075. Version `0.7.43` — Meteora effectif : DLMM, DAMM v1/v2, DBC
|
||||
Objectif : compléter la famille Meteora en couvrant les événements réellement utiles au DEX effectif, avec corpus enrichi par Demo3 si nécessaire.
|
||||
|
||||
À faire :
|
||||
|
||||
@@ -983,19 +1022,19 @@ Objectif : compléter la famille Meteora en couvrant les événements réellemen
|
||||
- clarifier la part `launch/bonding_curve` et la part `dex_effective` de `meteora_dbc` ;
|
||||
- éviter tout trade/candle artificiel lorsque les montants ou prix ne sont pas prouvés.
|
||||
|
||||
### 6.074. Version `0.7.42` — Autres DEX effectifs : Orca, FluxBeam, DexLab, metaDAO, Printr
|
||||
Objectif : consolider les DEX non-Raydium/Meteora et intégrer les DEX récemment observés comme candidats vérifiables.
|
||||
### 6.076. Version `0.7.44` — Autres DEX effectifs : Orca, FluxBeam, DexLab, metaDAO, Printr
|
||||
Objectif : consolider les DEX non-Raydium/Meteora et intégrer les DEX récemment observés comme candidats vérifiables.
|
||||
|
||||
À faire :
|
||||
|
||||
- constituer des corpus locaux pour `orca_whirlpools`, `fluxbeam` et `dexlab` ;
|
||||
- vérifier les `program_id`, comptes, préfixes `data_json` et familles d’instructions utiles ;
|
||||
- stabiliser les événements `create_pool`, `swap`, liquidité, positions et admin lorsque prouvés ;
|
||||
- ajouter `metaDAO` et `Printr` en `to_verify` avec recherche explicite de program ids, signatures, comptes et pools ;
|
||||
- vérifier `metaDAO` et `Printr` par corpus on-chain local avant toute promotion ;
|
||||
- ne pas confondre source externe de découverte et preuve on-chain ;
|
||||
- marquer explicitement les variantes partiellement supportées ou heuristiques.
|
||||
|
||||
### 6.075. Version `0.7.43` — Couverture événementielle DEX : swap, liquidité, fees, rewards, admin, burns
|
||||
### 6.077. Version `0.7.45` — Couverture événementielle DEX : swap, liquidité, fees, rewards, admin, burns
|
||||
Objectif : s’assurer que chaque DEX effectif expose les événements utiles au scoring et au risque sans polluer les trades/candles.
|
||||
|
||||
À faire :
|
||||
@@ -1009,31 +1048,19 @@ Objectif : s’assurer que chaque DEX effectif expose les événements utiles au
|
||||
- ajouter des compteurs et samples diagnostics par DEX et par type d’événement ;
|
||||
- conserver l’invariant : aucun fee/reward/admin/liquidity/lifecycle/burn non price-action ne produit de trade, metric ou candle.
|
||||
|
||||
### 6.076. Version `0.7.44` — `kb_demo_app` Demo3 : recherche de paires/pools par DEX ou program id
|
||||
Objectif : ajouter une fenêtre ou vue de démonstration dédiée à la constitution de corpus par DEX.
|
||||
|
||||
À faire :
|
||||
|
||||
- ajouter une `Demo3` dans `kb_demo_app` ;
|
||||
- permettre la saisie d’un `program_id`, d’un `dex_code`, d’un `pool_address`, d’un `pair_id` ou d’un token mint ;
|
||||
- rechercher dans la base locale les transactions, instructions, decoded events, pools, paires, listings et candidates associés ;
|
||||
- fournir des presets pour les programmes à vérifier, par exemple `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8`, `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h`, `cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG` ;
|
||||
- afficher les signatures candidates à backfill/replay ;
|
||||
- garder `kb_demo_app` comme façade UI : toute requête métier doit rester dans `kb_lib`.
|
||||
|
||||
### 6.077. Version `0.7.45` — `kb_demo_app` Demo4 : DEX Screener et sources externes de découverte
|
||||
Objectif : utiliser des sources externes comme aides à la découverte de corpus sans les traiter comme vérité métier.
|
||||
### 6.078. Version `0.7.46` — `kb_demo_app` Demo4 : DEX Screener et sources externes de découverte
|
||||
Objectif : utiliser des sources externes comme aides à la découverte de corpus sans les traiter comme vérité métier, après la première consolidation Raydium AMM v4.
|
||||
|
||||
À faire :
|
||||
|
||||
- ajouter une `Demo4` pour interroger DEX Screener ou une source équivalente ;
|
||||
- rechercher des paires par token mint, chain, DEX name, pool address ou program id lorsque disponible ;
|
||||
- comparer les résultats externes avec les objets locaux : tokens, pools, pairs, listings, decoded events ;
|
||||
- comparer les résultats externes avec les objets locaux : tokens, pools, pairs, listings, decoded events et protocol candidates ;
|
||||
- afficher les écarts : paire externe absente localement, pool local sans source externe, DEX label ambigu, program id manquant ;
|
||||
- permettre de copier les signatures/adresses candidates pour backfill ;
|
||||
- ne jamais promouvoir automatiquement un DEX, un `program_id` ou une paire sur la seule base d’une réponse externe.
|
||||
|
||||
### 6.078. Version `0.7.46` — Démos spécialisées launch surfaces après DEX effectifs
|
||||
### 6.079. Version `0.7.47` — Démos spécialisées launch surfaces après DEX effectifs
|
||||
Objectif : préparer des vues spécialisées pour les launch surfaces, mais seulement après stabilisation des DEX effectifs.
|
||||
|
||||
À faire plus tard :
|
||||
@@ -1044,7 +1071,7 @@ Objectif : préparer des vues spécialisées pour les launch surfaces, mais seul
|
||||
- exposer les origins dans les diagnostics et l’UI d’inspection ;
|
||||
- maintenir l’interdiction de faux program ids, faux trades et fausses candles.
|
||||
|
||||
### 6.079. Version `0.7.47` — `kb_demo_app` Demo10 : watcher WebSocket live DEX
|
||||
### 6.080. Version `0.7.48` — `kb_demo_app` Demo10 : watcher WebSocket live DEX
|
||||
Objectif : valider le passage du replay/backfill vers l’observation temps réel contrôlée.
|
||||
|
||||
À faire :
|
||||
@@ -1058,7 +1085,7 @@ Objectif : valider le passage du replay/backfill vers l’observation temps rée
|
||||
- afficher les compteurs live, erreurs, subscriptions actives et derniers objets persistés ;
|
||||
- prévoir un arrêt propre avec unsubscribe avant close.
|
||||
|
||||
### 6.080. Version `0.7.48` — Validation DEX v1 consolidée
|
||||
### 6.081. Version `0.7.49` — Validation DEX v1 consolidée
|
||||
Objectif : rejouer tous les DEX effectifs supportés et valider les invariants du pipeline complet avant de revenir aux launch surfaces ou à l’analyse `0.8.x`.
|
||||
|
||||
À faire :
|
||||
@@ -1071,7 +1098,7 @@ Objectif : rejouer tous les DEX effectifs supportés et valider les invariants d
|
||||
- conserver une matrice de support par DEX, variante, instruction et type d’événement ;
|
||||
- verrouiller les invariants avant d’ouvrir l’analyse `0.8.x`.
|
||||
|
||||
### 6.081. Version `0.8.x` — Analyse et filtrage
|
||||
### 6.082. Version `0.8.x` — Analyse et filtrage
|
||||
Objectif : transformer les événements bruts en signaux exploitables.
|
||||
|
||||
À faire :
|
||||
@@ -1086,7 +1113,7 @@ Objectif : transformer les événements bruts en signaux exploitables.
|
||||
- outils de sélection manuelle de points ABC et projection d’un point D selon des règles temps/prix explicites ;
|
||||
- séparation stricte entre signaux analytiques observés, projections hypothétiques et décisions de trading.
|
||||
|
||||
### 6.082. Version `1.x.y` — Wallets, comptes et transferts
|
||||
### 6.083. Version `1.x.y` — Wallets, comptes et transferts
|
||||
Objectif : préparer la couche d’action sans encore brancher l’achat/vente automatique.
|
||||
|
||||
À faire :
|
||||
@@ -1243,29 +1270,30 @@ Le projet doit maintenir au minimum :
|
||||
|
||||
## 12. Priorité immédiate
|
||||
|
||||
La priorité immédiate après `0.7.38-B` est `0.7.39_dex_first_effective_swap_surfaces`. La phase launch surfaces est volontairement reportée : elle sera reprise après consolidation des DEX effectifs et des outils de constitution de corpus.
|
||||
La priorité immédiate après `0.7.40` est `0.7.41_raydium_amm_v4_swap_decoder_v1`. `Demo3` et le backfill par signature donnent maintenant assez de corpus on-chain pour travailler sur Raydium AMM v4 sans inventer de layout ni promouvoir de faux pool.
|
||||
|
||||
Préconditions validées avant `0.7.39` :
|
||||
Préconditions validées avant `0.7.41` :
|
||||
|
||||
1. validation `0.7.36` acquise : Meteora consolidé, transactions failed traçables mais non actionnables, swaps sans amounts classés `non_actionable_trade`, aucun diagnostic bloquant masqué ;
|
||||
2. validation `0.7.37` acquise : compteurs metadata/catalog exposés, backfill metadata idempotent, `pair_symbol` rafraîchissables, metadata manquantes non bloquantes ;
|
||||
3. validation `0.7.38` acquise : `tokenMetadataGapSamples` priorisés, Demo Pipeline 2 raccordé, `validationPassed = true`, `blockingIssueCount = 0`, registre local `WSOL`/`USDC`/`USDT`/`JUP`/`RAY`/`BONK` disponible ;
|
||||
4. registre local minimal disponible : `SOL`, `WSOL`, `USDC`, `USDT` ;
|
||||
5. les diagnostics locaux restent l’outil de vérité pour décider si un DEX peut passer de `planned` ou `to_verify` à `partial` ou `supported`.
|
||||
4. validation `0.7.39` acquise : matrice DEX-first, suppression de l’alias `raydium`, `metaDAO` et `Printr` en `to_verify`, aucun `program_id` fictif ;
|
||||
5. validation `0.7.40` acquise : `Demo3` découvre on-chain des signatures, mints, deltas et comptes candidats ; Demo Pipeline 2 peut backfiller une signature précise ; les instructions Raydium AMM v4 sont persistées en base ;
|
||||
6. aucune transaction failed ne doit alimenter `trade_events`, metrics ou candles ;
|
||||
7. aucun decoded event AMM v4 ne doit être promu `trade_candidate` sans montants exploitables.
|
||||
|
||||
Ordre de travail recommandé pour `0.7.39+` :
|
||||
Ordre de travail recommandé pour `0.7.41+` :
|
||||
|
||||
1. scanner `dex_support_matrix.rs`, `dex.rs`, `dex/*.rs`, `dex_decode.rs`, `dex_detect.rs`, `trade_aggregation.rs`, `local_pipeline_validation.rs`, `local_pipeline_diagnostics.rs`, `transaction_classification.rs` et `protocol_candidate_recording.rs` ;
|
||||
2. produire un état des DEX effectifs déjà couverts, partiels, à vérifier ou absents ;
|
||||
3. ajouter `metaDAO` et `Printr` comme DEX `to_verify` sans `program_id` inventé ;
|
||||
4. vérifier les DEX principaux de swap : PumpSwap, Raydium CPMM/CLMM/AMM v4/Stable Swap, Meteora DLMM/DAMM/DBC, Orca Whirlpools, FluxBeam, DexLab ;
|
||||
5. rechercher des corpus par `program_id`, pair/pool address, signature et token mint ;
|
||||
6. ajouter `Demo3` pour rechercher localement les paires/pools/signatures par DEX ou `program_id` ;
|
||||
7. ajouter `Demo4` pour interroger DEX Screener ou sources externes et comparer avec la base locale sans promotion automatique ;
|
||||
8. compléter la couverture par DEX des swaps, liquidités, lifecycle, fees, rewards, admin/config, burns/mints utiles ;
|
||||
9. ajouter ensuite `Demo10` pour le watcher WebSocket live DEX avec start/stop, subscribe/unsubscribe et écriture en base via le pipeline existant ;
|
||||
10. reprendre seulement après cela les launch surfaces : Raydium LaunchLab/Launchpad, LetsBonk/Bonk.fun, Bags, Moonshot/Moonit, Boop.fun, Believe, Heaven ;
|
||||
11. passer ensuite à la couche wallet : création de wallet/keypair, inspection et transfert de fonds vers un autre account.
|
||||
1. implémenter `raydium_amm_v4.swap` v1 à partir du corpus local : instructions internes `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8`, comptes, `data_json`, deltas SPL Token et routes Jupiter ;
|
||||
2. distinguer `routeSource` / routeur top-level et DEX effectif : une transaction Jupiter peut contenir un leg Raydium AMM v4, mais le decoded event métier doit rester attribué au DEX effectif ;
|
||||
3. matérialiser trades/candles uniquement pour transactions OK et montants fiables ;
|
||||
4. conserver les swaps ambigus ou multi-hop non isolables en `non_actionable_trade` ;
|
||||
5. consolider ensuite la famille Raydium (`cpmm`, `clmm`, `amm_v4`, `stable_swap`, router et launch surfaces reportées) ;
|
||||
6. reprendre Meteora, Orca, FluxBeam, DexLab, metaDAO et Printr avec la même approche corpus-first ;
|
||||
7. décaler `Demo4` après la première consolidation Raydium AMM v4, car la découverte on-chain locale suffit pour la suite immédiate ;
|
||||
8. ajouter ensuite `Demo10` pour le watcher WebSocket live DEX avec start/stop, subscribe/unsubscribe et écriture en base via le pipeline existant ;
|
||||
9. reprendre seulement après cela les launch surfaces spécialisées ;
|
||||
10. passer ensuite à la couche wallet : création de wallet/keypair, inspection et transfert de fonds vers un autre account.
|
||||
|
||||
Garde-fous constants :
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"demo_ws",
|
||||
"demo_http",
|
||||
"demo_ws_manager",
|
||||
"demo3old",
|
||||
"demo3",
|
||||
"demo_pipeline",
|
||||
"demo_pipeline2"
|
||||
],
|
||||
|
||||
214
kb_demo_app/frontend/demo3.html
Normal file
214
kb_demo_app/frontend/demo3.html
Normal file
@@ -0,0 +1,214 @@
|
||||
<!-- file: kb_demo_app/frontend/demo3.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Demo3 On-chain DEX Discovery</title>
|
||||
<link rel="stylesheet" href="sass/main.scss" />
|
||||
</head>
|
||||
|
||||
<body class="bg-body-tertiary">
|
||||
<header class="app-header">
|
||||
<nav class="navbar navbar-expand-lg h-100 py-0 bg-light text-dark">
|
||||
<div class="container my-0">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img alt="Logo" src="imgs/logo.png" class="app-logo" />
|
||||
<span class="ps-2 fs-4 fw-bold text-primary font-logo">Khadhroony-BoBoBot</span>
|
||||
</a>
|
||||
<span class="badge text-bg-primary">Demo3</span>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="app-main">
|
||||
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row g-4">
|
||||
<div class="col-12 col-xxl-4">
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h1 class="h4 mb-0">On-chain DEX discovery</h1>
|
||||
<span id="demo3StatusBadge" class="badge text-bg-secondary">idle</span>
|
||||
</div>
|
||||
<p class="text-body-secondary small mb-3">
|
||||
Recherche directement sur Solana via <code>getSignaturesForAddress</code> + <code>getTransaction</code>.
|
||||
Le résultat sert à trouver une signature, un pool ou un mint à backfiller ensuite dans Demo Pipeline 2.
|
||||
</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="demo3PresetSelect" class="form-label">Preset DEX</label>
|
||||
<select id="demo3PresetSelect" class="form-select"></select>
|
||||
<div id="demo3PresetHelp" class="form-text">Choisis un DEX ou saisis un program id manuellement.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="demo3DexCodeInput" class="form-label">DEX code</label>
|
||||
<input id="demo3DexCodeInput" type="text" class="form-control font-monospace" spellcheck="false" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="demo3ProgramIdInput" class="form-label">Program id</label>
|
||||
<input id="demo3ProgramIdInput" type="text" class="form-control font-monospace" spellcheck="false" />
|
||||
</div>
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-6">
|
||||
<label for="demo3HttpRoleInput" class="form-label">HTTP role</label>
|
||||
<input id="demo3HttpRoleInput" type="text" class="form-control" value="history_backfill" />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="demo3SignatureLimitInput" class="form-label">Signature limit</label>
|
||||
<input id="demo3SignatureLimitInput" type="number" min="1" max="1000" class="form-control" value="50" />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="demo3TransactionLimitInput" class="form-label">Tx fetch limit</label>
|
||||
<input id="demo3TransactionLimitInput" type="number" min="1" max="250" class="form-control" value="25" />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="demo3CandidateLimitInput" class="form-label">Candidate limit</label>
|
||||
<input id="demo3CandidateLimitInput" type="number" min="1" max="100" class="form-control" value="25" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||
<button id="demo3DiscoverButton" type="button" class="btn btn-primary">Discover on-chain</button>
|
||||
<button id="demo3LocalSearchButton" type="button" class="btn btn-outline-primary">Search local DB</button>
|
||||
<button id="demo3ClearFiltersButton" type="button" class="btn btn-outline-secondary">Clear</button>
|
||||
<button id="demo3CopyJsonButton" type="button" class="btn btn-outline-secondary">Copy JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Filtres locaux optionnels</h2>
|
||||
<div class="mb-3">
|
||||
<label for="demo3PairIdInput" class="form-label">Pair id local</label>
|
||||
<input id="demo3PairIdInput" type="number" class="form-control" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="demo3PoolAddressInput" class="form-label">Pool address</label>
|
||||
<input id="demo3PoolAddressInput" type="text" class="form-control font-monospace" spellcheck="false" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="demo3TokenMintInput" class="form-label">Token mint</label>
|
||||
<input id="demo3TokenMintInput" type="text" class="form-control font-monospace" spellcheck="false" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="demo3SignatureInput" class="form-label">Signature</label>
|
||||
<input id="demo3SignatureInput" type="text" class="form-control font-monospace" spellcheck="false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Résumé</h2>
|
||||
<div class="row g-2 small">
|
||||
<div class="col-6"><strong>Signatures:</strong> <span id="demo3SummarySignatureCount">0</span></div>
|
||||
<div class="col-6"><strong>Tx fetched:</strong> <span id="demo3SummaryFetchedTxCount">0</span></div>
|
||||
<div class="col-6"><strong>Missing tx:</strong> <span id="demo3SummaryMissingTxCount">0</span></div>
|
||||
<div class="col-6"><strong>Failed tx:</strong> <span id="demo3SummaryFailedTxCount">0</span></div>
|
||||
<div class="col-6"><strong>Candidates:</strong> <span id="demo3SummaryCandidateCount">0</span></div>
|
||||
<div class="col-6"><strong>Local pairs:</strong> <span id="demo3SummaryLocalPairCount">0</span></div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="small text-body-secondary">
|
||||
<strong>Target:</strong>
|
||||
<span id="demo3TargetText" class="font-monospace">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Logs</h2>
|
||||
<textarea id="demo3LogTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea>
|
||||
<button id="demo3ClearLogButton" type="button" class="btn btn-sm btn-outline-secondary mt-2">Clear log</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xxl-8">
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Candidats on-chain</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Signature</th>
|
||||
<th>Slot</th>
|
||||
<th>Kind</th>
|
||||
<th>Confidence</th>
|
||||
<th>Verified pool</th>
|
||||
<th>Token A</th>
|
||||
<th>Token B</th>
|
||||
<th>Observed mints</th>
|
||||
<th>Token deltas</th>
|
||||
<th>Candidate accounts</th>
|
||||
<th>Hint</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="demo3OnchainCandidateTableBody">
|
||||
<tr><td colspan="11" class="text-body-secondary">No on-chain candidate.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Recherche locale DB</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>DEX</th>
|
||||
<th>Pool</th>
|
||||
<th>Pair id</th>
|
||||
<th>Symbol</th>
|
||||
<th>Base</th>
|
||||
<th>Quote</th>
|
||||
<th>Trades</th>
|
||||
<th>Candles</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="demo3LocalPoolPairTableBody">
|
||||
<tr><td colspan="8" class="text-body-secondary">No local pool/pair sample.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Raw result JSON</h2>
|
||||
<textarea id="demo3JsonTextarea" class="form-control font-monospace" rows="22" readonly spellcheck="false"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="app-footer bg-dark text-light">
|
||||
<div class="container h-100 d-flex align-items-center">
|
||||
<div class="row flex-grow-1 align-items-center">
|
||||
<div class="col-12 text-center text-small my-1 my-md-0">
|
||||
© 2026 SASEDEV — Demo3 On-chain DEX Discovery
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="ts/demo3.ts" defer></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
240
kb_demo_app/frontend/demo3old.html
Normal file
240
kb_demo_app/frontend/demo3old.html
Normal file
@@ -0,0 +1,240 @@
|
||||
<!-- file: kb_demo_app/frontend/demo3old.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Demo3old Local DEX Corpus</title>
|
||||
<link rel="stylesheet" href="sass/main.scss" />
|
||||
</head>
|
||||
|
||||
<body class="bg-body-tertiary">
|
||||
<header class="app-header">
|
||||
<nav class="navbar navbar-expand-lg h-100 py-0 bg-light text-dark">
|
||||
<div class="container my-0">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img alt="Logo" src="imgs/logo.png" class="app-logo" />
|
||||
<span class="ps-2 fs-4 fw-bold text-primary font-logo">Khadhroony-BoBoBot</span>
|
||||
</a>
|
||||
<div class="ms-auto">
|
||||
<span id="demo3oldStatusBadge" class="badge text-bg-secondary">Ready</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="app-main">
|
||||
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row g-4">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<h1 class="h4 mb-3">Demo3old — Local DEX Corpus Search</h1>
|
||||
<p class="text-body-secondary mb-0">
|
||||
Recherche locale de corpus par DEX, program id, pool, pair, token mint ou signature. Cette vue ne promeut aucun program id : elle sert uniquement à trouver des candidats à backfill et validation.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xxl-4">
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Filtres</h2>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="demo3oldPresetSelect" class="form-label">Preset</label>
|
||||
<select id="demo3oldPresetSelect" class="form-select"></select>
|
||||
<div id="demo3oldPresetHelp" class="form-text">
|
||||
Choisis un preset ou saisis les filtres manuellement.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="demo3oldDexCodeInput" class="form-label">DEX code</label>
|
||||
<input id="demo3oldDexCodeInput" type="text" class="form-control font-monospace" spellcheck="false" placeholder="raydium_amm_v4" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="demo3oldProgramIdInput" class="form-label">Program id</label>
|
||||
<input id="demo3oldProgramIdInput" type="text" class="form-control font-monospace" spellcheck="false" placeholder="675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" />
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-lg-6">
|
||||
<label for="demo3oldPairIdInput" class="form-label">Pair id</label>
|
||||
<input id="demo3oldPairIdInput" type="number" min="1" step="1" class="form-control" />
|
||||
</div>
|
||||
<div class="col-12 col-lg-6">
|
||||
<label for="demo3oldLimitInput" class="form-label">Limit</label>
|
||||
<input id="demo3oldLimitInput" type="number" min="1" max="200" step="1" class="form-control" value="50" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 mb-3">
|
||||
<label for="demo3oldPoolAddressInput" class="form-label">Pool address</label>
|
||||
<input id="demo3oldPoolAddressInput" type="text" class="form-control font-monospace" spellcheck="false" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="demo3oldTokenMintInput" class="form-label">Token mint</label>
|
||||
<input id="demo3oldTokenMintInput" type="text" class="form-control font-monospace" spellcheck="false" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="demo3oldSignatureInput" class="form-label">Signature</label>
|
||||
<input id="demo3oldSignatureInput" type="text" class="form-control font-monospace" spellcheck="false" />
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button id="demo3oldSearchButton" type="button" class="btn btn-primary">Search local corpus</button>
|
||||
<button id="demo3oldClearFiltersButton" type="button" class="btn btn-outline-secondary">Clear filters</button>
|
||||
<button id="demo3oldCopyJsonButton" type="button" class="btn btn-outline-secondary">Copy JSON</button>
|
||||
<button id="demo3oldClearLogButton" type="button" class="btn btn-outline-secondary">Clear log</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Résumé</h2>
|
||||
<div class="row g-2 small">
|
||||
<div class="col-6"><strong>Transactions:</strong> <span id="demo3oldSummaryTransactionCount">0</span></div>
|
||||
<div class="col-6"><strong>Instructions:</strong> <span id="demo3oldSummaryInstructionCount">0</span></div>
|
||||
<div class="col-6"><strong>Decoded:</strong> <span id="demo3oldSummaryDecodedEventCount">0</span></div>
|
||||
<div class="col-6"><strong>Pools:</strong> <span id="demo3oldSummaryPoolCount">0</span></div>
|
||||
<div class="col-6"><strong>Pairs:</strong> <span id="demo3oldSummaryPairCount">0</span></div>
|
||||
<div class="col-6"><strong>Trades:</strong> <span id="demo3oldSummaryTradeEventCount">0</span></div>
|
||||
<div class="col-6"><strong>Candles:</strong> <span id="demo3oldSummaryCandleCount">0</span></div>
|
||||
<div class="col-6"><strong>Candidates:</strong> <span id="demo3oldSummaryProtocolCandidateCount">0</span></div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="small text-body-secondary">
|
||||
<strong>Database:</strong>
|
||||
<span id="demo3oldDatabaseUrlText" class="font-monospace">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Logs</h2>
|
||||
<textarea id="demo3oldLogTextarea" class="form-control font-monospace" rows="12" readonly spellcheck="false"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xxl-8">
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Transactions candidates</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Signature</th>
|
||||
<th>Slot</th>
|
||||
<th>Failed</th>
|
||||
<th>Ix</th>
|
||||
<th>Decoded</th>
|
||||
<th>Trades</th>
|
||||
<th>Programs</th>
|
||||
<th>Tx id</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="demo3oldTransactionTableBody">
|
||||
<tr>
|
||||
<td colspan="8" class="text-body-secondary">No transaction sample.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Pools / pairs candidats</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>DEX</th>
|
||||
<th>Pool</th>
|
||||
<th>Pair id</th>
|
||||
<th>Symbol</th>
|
||||
<th>Base</th>
|
||||
<th>Quote</th>
|
||||
<th>Decoded</th>
|
||||
<th>Trades</th>
|
||||
<th>Candles</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="demo3oldPoolPairTableBody">
|
||||
<tr>
|
||||
<td colspan="9" class="text-body-secondary">No pool/pair sample.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Decoded events candidats</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Signature</th>
|
||||
<th>Slot</th>
|
||||
<th>Protocol</th>
|
||||
<th>Kind</th>
|
||||
<th>Program</th>
|
||||
<th>Pool</th>
|
||||
<th>Category</th>
|
||||
<th>Actionability</th>
|
||||
<th>Trade</th>
|
||||
<th>Candle</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="demo3oldDecodedEventTableBody">
|
||||
<tr>
|
||||
<td colspan="11" class="text-body-secondary">No decoded event sample.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Raw result JSON</h2>
|
||||
<textarea id="demo3oldJsonTextarea" class="form-control font-monospace" rows="18" readonly spellcheck="false"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="app-footer bg-dark text-light">
|
||||
<div class="container h-100 d-flex align-items-center">
|
||||
<div class="row flex-grow-1 align-items-center">
|
||||
<div class="col-12 col-md-6 text-center text-small my-1 my-md-0">
|
||||
© 2026 SASEDEV — Demo3old Local DEX Corpus
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="ts/demo3old.ts" defer></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -109,6 +109,19 @@
|
||||
Backfill pool
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr class="my-4" />
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="demoPipeline2SignatureInput" class="form-label">Signature</label>
|
||||
<input id="demoPipeline2SignatureInput" type="text" class="form-control font-monospace" spellcheck="false" placeholder="Transaction signature from Demo3" />
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button id="demoPipeline2BackfillSignatureButton" type="button" class="btn btn-outline-primary">
|
||||
Backfill signature
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -166,6 +179,7 @@
|
||||
<div class="mb-3">
|
||||
<label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label>
|
||||
<select id="demoPipeline2ValidationProfileSelect" class="form-select">
|
||||
<option value="0.7.40_raydium_effective_surfaces" selected>0.7.340 — Raydium effective surfaces</option>
|
||||
<option value="0.7.39_dex_first_effective_swap_surfaces" selected>0.7.39 — DEX-first effective swap surfaces</option>
|
||||
<option value="0.7.38_token_metadata_gap_prioritization">0.7.38 — token metadata gap prioritization</option>
|
||||
<option value="0.7.37_token_metadata_catalog_enrichment">0.7.37 — token metadata/catalog enrichment</option>
|
||||
@@ -193,7 +207,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="accordion-item border-0 shadow-sm mb-3">
|
||||
<h2 class="accordion-header" id="demoPipeline2DiscriminatorDiagnosticsHeading">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2DiscriminatorDiagnosticsCollapse" aria-expanded="false" aria-controls="demoPipeline2DiscriminatorDiagnosticsCollapse">
|
||||
@@ -208,24 +222,12 @@
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="demoPipeline2DiscriminatorProgramIdInput" class="form-label">Program id</label>
|
||||
<input
|
||||
id="demoPipeline2DiscriminatorProgramIdInput"
|
||||
type="text"
|
||||
class="form-control font-monospace"
|
||||
value="LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"
|
||||
/>
|
||||
<input id="demoPipeline2DiscriminatorProgramIdInput" type="text" class="form-control font-monospace" value="LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="demoPipeline2DiscriminatorLimitInput" class="form-label">Instruction row limit</label>
|
||||
<input
|
||||
id="demoPipeline2DiscriminatorLimitInput"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
class="form-control"
|
||||
value="200"
|
||||
/>
|
||||
<input id="demoPipeline2DiscriminatorLimitInput" type="number" min="1" step="1" class="form-control" value="200" />
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
@@ -352,7 +354,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="accordion-item border-0 shadow-sm mb-3">
|
||||
<h2 class="accordion-header" id="demoPipeline2DiscriminatorSummaryHeading">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2DiscriminatorSummaryCollapse" aria-expanded="false" aria-controls="demoPipeline2DiscriminatorSummaryCollapse">
|
||||
@@ -361,13 +363,7 @@
|
||||
</h2>
|
||||
<div id="demoPipeline2DiscriminatorSummaryCollapse" class="accordion-collapse collapse" aria-labelledby="demoPipeline2DiscriminatorSummaryHeading" data-bs-parent="#demoPipeline2ContentAccordion">
|
||||
<div class="accordion-body">
|
||||
<textarea
|
||||
id="demoPipeline2DiscriminatorSummariesTextarea"
|
||||
class="form-control font-monospace"
|
||||
rows="18"
|
||||
readonly
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
<textarea id="demoPipeline2DiscriminatorSummariesTextarea" class="form-control font-monospace" rows="18" readonly spellcheck="false"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
<button id="openDemoWsManagerButton" type="button" class="btn btn-outline-primary">
|
||||
Demo Ws Manager
|
||||
</button>
|
||||
<button id="openDemo3Button" type="button" class="btn btn-outline-primary">
|
||||
Demo3 Corpus
|
||||
</button>
|
||||
<button id="openDemo3oldButton" type="button" class="btn btn-outline-primary">
|
||||
Demo3old Corpus
|
||||
</button>
|
||||
<button id="openDemoPipelineButton" type="button" class="btn btn-outline-primary">
|
||||
Ouvrir Demo Pipeline
|
||||
</button>
|
||||
@@ -67,6 +73,10 @@
|
||||
La démonstration légère de pilotage multi-clients est disponible dans la fenêtre
|
||||
<strong>Demo Ws Manager</strong>.
|
||||
</p>
|
||||
<p class="text-body-secondary mb-3">
|
||||
La recherche locale de corpus DEX est disponible dans la fenêtre
|
||||
<strong>Demo3 Local DEX Corpus</strong>.
|
||||
</p>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button id="openDemoWsButtonSecondary" type="button" class="btn btn-primary">
|
||||
@@ -78,6 +88,12 @@
|
||||
<button id="openDemoWsManagerButtonSecondary" type="button" class="btn btn-primary">
|
||||
Ouvrir Demo Ws Manager
|
||||
</button>
|
||||
<button id="openDemo3ButtonSecondary" type="button" class="btn btn-primary">
|
||||
Ouvrir Demo3 Corpus
|
||||
</button>
|
||||
<button id="openDemo3oldButtonSecondary" type="button" class="btn btn-primary">
|
||||
Ouvrir Demo3old Corpus
|
||||
</button>
|
||||
<button id="openDemoPipelineButtonSecondary" type="button" class="btn btn-primary">
|
||||
Ouvrir Demo Pipeline
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Matching decoded event sample for corpus discovery.
|
||||
*/
|
||||
export type Demo3LocalDexCorpusDecodedEventSample = {
|
||||
/**
|
||||
* Decoded event id.
|
||||
*/
|
||||
decodedEventId: number,
|
||||
/**
|
||||
* Transaction id.
|
||||
*/
|
||||
transactionId: number,
|
||||
/**
|
||||
* Transaction signature.
|
||||
*/
|
||||
signature: string,
|
||||
/**
|
||||
* Optional Solana slot.
|
||||
*/
|
||||
slot: number | null,
|
||||
/**
|
||||
* Protocol name stored on the decoded event.
|
||||
*/
|
||||
protocolName: string,
|
||||
/**
|
||||
* Program id stored on the decoded event.
|
||||
*/
|
||||
programId: string,
|
||||
/**
|
||||
* Event kind.
|
||||
*/
|
||||
eventKind: string,
|
||||
/**
|
||||
* Optional pool account.
|
||||
*/
|
||||
poolAccount: string | null,
|
||||
/**
|
||||
* Optional token A mint.
|
||||
*/
|
||||
tokenAMint: string | null,
|
||||
/**
|
||||
* Optional token B mint.
|
||||
*/
|
||||
tokenBMint: string | null,
|
||||
/**
|
||||
* Decoded event category extracted from payload JSON.
|
||||
*/
|
||||
eventCategory: string | null,
|
||||
/**
|
||||
* Decoded event lifecycle kind extracted from payload JSON.
|
||||
*/
|
||||
eventLifecycleKind: string | null,
|
||||
/**
|
||||
* Decoded event actionability extracted from payload JSON.
|
||||
*/
|
||||
eventActionability: string | null,
|
||||
/**
|
||||
* Whether the decoded event is a trade candidate.
|
||||
*/
|
||||
tradeCandidate: boolean,
|
||||
/**
|
||||
* Whether the decoded event is a candle candidate.
|
||||
*/
|
||||
candleCandidate: boolean, };
|
||||
@@ -0,0 +1,58 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Matching pool/pair sample for corpus discovery.
|
||||
*/
|
||||
export type Demo3LocalDexCorpusPoolPairSample = {
|
||||
/**
|
||||
* Optional pool id.
|
||||
*/
|
||||
poolId: number | null,
|
||||
/**
|
||||
* Optional pool address.
|
||||
*/
|
||||
poolAddress: string | null,
|
||||
/**
|
||||
* Optional pair id.
|
||||
*/
|
||||
pairId: number | null,
|
||||
/**
|
||||
* Optional DEX code.
|
||||
*/
|
||||
dexCode: string | null,
|
||||
/**
|
||||
* Optional pair symbol.
|
||||
*/
|
||||
pairSymbol: string | null,
|
||||
/**
|
||||
* Optional base token mint.
|
||||
*/
|
||||
baseMint: string | null,
|
||||
/**
|
||||
* Optional base token symbol.
|
||||
*/
|
||||
baseSymbol: string | null,
|
||||
/**
|
||||
* Optional quote token mint.
|
||||
*/
|
||||
quoteMint: string | null,
|
||||
/**
|
||||
* Optional quote token symbol.
|
||||
*/
|
||||
quoteSymbol: string | null,
|
||||
/**
|
||||
* Number of decoded events attached to the pool.
|
||||
*/
|
||||
decodedEventCount: number,
|
||||
/**
|
||||
* Number of trade events attached to the pair.
|
||||
*/
|
||||
tradeEventCount: number,
|
||||
/**
|
||||
* Number of candle rows attached to the pair.
|
||||
*/
|
||||
pairCandleCount: number,
|
||||
/**
|
||||
* Latest known founding or activity signature for the pool/pair.
|
||||
*/
|
||||
latestSignature: string | null, };
|
||||
@@ -0,0 +1,19 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Demo3LocalDexCorpusSearchResult } from "./Demo3LocalDexCorpusSearchResult";
|
||||
|
||||
/**
|
||||
* Response payload returned by Demo3 local DEX corpus search.
|
||||
*/
|
||||
export type Demo3LocalDexCorpusSearchPayload = {
|
||||
/**
|
||||
* Open database URL.
|
||||
*/
|
||||
databaseUrl: string,
|
||||
/**
|
||||
* Pretty JSON representation of the search result.
|
||||
*/
|
||||
resultJson: string,
|
||||
/**
|
||||
* Structured local DEX corpus search result.
|
||||
*/
|
||||
result: Demo3LocalDexCorpusSearchResult, };
|
||||
@@ -0,0 +1,34 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Request payload for a local DEX corpus search.
|
||||
*/
|
||||
export type Demo3LocalDexCorpusSearchRequest = {
|
||||
/**
|
||||
* Optional DEX code or decoded protocol name.
|
||||
*/
|
||||
dexCode: string | null,
|
||||
/**
|
||||
* Optional Solana program id.
|
||||
*/
|
||||
programId: string | null,
|
||||
/**
|
||||
* Optional local pair id.
|
||||
*/
|
||||
pairId: number | null,
|
||||
/**
|
||||
* Optional pool account/address.
|
||||
*/
|
||||
poolAddress: string | null,
|
||||
/**
|
||||
* Optional token mint to match as base, quote or decoded mint.
|
||||
*/
|
||||
tokenMint: string | null,
|
||||
/**
|
||||
* Optional transaction signature.
|
||||
*/
|
||||
signature: string | null,
|
||||
/**
|
||||
* Maximum number of rows to return per sample category.
|
||||
*/
|
||||
limit: number, };
|
||||
@@ -0,0 +1,31 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Demo3LocalDexCorpusDecodedEventSample } from "./Demo3LocalDexCorpusDecodedEventSample";
|
||||
import type { Demo3LocalDexCorpusPoolPairSample } from "./Demo3LocalDexCorpusPoolPairSample";
|
||||
import type { Demo3LocalDexCorpusSearchRequest } from "./Demo3LocalDexCorpusSearchRequest";
|
||||
import type { Demo3LocalDexCorpusSearchSummary } from "./Demo3LocalDexCorpusSearchSummary";
|
||||
import type { Demo3LocalDexCorpusTransactionSample } from "./Demo3LocalDexCorpusTransactionSample";
|
||||
|
||||
/**
|
||||
* Structured local DEX corpus search result.
|
||||
*/
|
||||
export type Demo3LocalDexCorpusSearchResult = {
|
||||
/**
|
||||
* Normalized search request applied by the backend service.
|
||||
*/
|
||||
request: Demo3LocalDexCorpusSearchRequest,
|
||||
/**
|
||||
* Aggregate counts for the matching local data.
|
||||
*/
|
||||
summary: Demo3LocalDexCorpusSearchSummary,
|
||||
/**
|
||||
* Matching transaction samples.
|
||||
*/
|
||||
transactionSamples: Array<Demo3LocalDexCorpusTransactionSample>,
|
||||
/**
|
||||
* Matching pool/pair samples.
|
||||
*/
|
||||
poolPairSamples: Array<Demo3LocalDexCorpusPoolPairSample>,
|
||||
/**
|
||||
* Matching decoded event samples.
|
||||
*/
|
||||
decodedEventSamples: Array<Demo3LocalDexCorpusDecodedEventSample>, };
|
||||
@@ -0,0 +1,38 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Aggregate counts for one local DEX corpus search.
|
||||
*/
|
||||
export type Demo3LocalDexCorpusSearchSummary = {
|
||||
/**
|
||||
* Number of distinct matching transactions.
|
||||
*/
|
||||
transactionCount: number,
|
||||
/**
|
||||
* Number of distinct matching instructions.
|
||||
*/
|
||||
instructionCount: number,
|
||||
/**
|
||||
* Number of distinct matching decoded DEX events.
|
||||
*/
|
||||
decodedEventCount: number,
|
||||
/**
|
||||
* Number of distinct matching pools.
|
||||
*/
|
||||
poolCount: number,
|
||||
/**
|
||||
* Number of distinct matching pairs.
|
||||
*/
|
||||
pairCount: number,
|
||||
/**
|
||||
* Number of distinct matching trade events.
|
||||
*/
|
||||
tradeEventCount: number,
|
||||
/**
|
||||
* Number of distinct matching candle rows.
|
||||
*/
|
||||
pairCandleCount: number,
|
||||
/**
|
||||
* Number of distinct matching protocol candidate rows.
|
||||
*/
|
||||
protocolCandidateCount: number, };
|
||||
@@ -0,0 +1,38 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Matching transaction sample for corpus discovery.
|
||||
*/
|
||||
export type Demo3LocalDexCorpusTransactionSample = {
|
||||
/**
|
||||
* Transaction id.
|
||||
*/
|
||||
transactionId: number,
|
||||
/**
|
||||
* Transaction signature.
|
||||
*/
|
||||
signature: string,
|
||||
/**
|
||||
* Optional Solana slot.
|
||||
*/
|
||||
slot: number | null,
|
||||
/**
|
||||
* Whether the transaction has a non-null error payload.
|
||||
*/
|
||||
failed: boolean,
|
||||
/**
|
||||
* Number of persisted instructions for the transaction.
|
||||
*/
|
||||
instructionCount: number,
|
||||
/**
|
||||
* Number of persisted decoded DEX events for the transaction.
|
||||
*/
|
||||
decodedEventCount: number,
|
||||
/**
|
||||
* Number of persisted trade events for the transaction.
|
||||
*/
|
||||
tradeEventCount: number,
|
||||
/**
|
||||
* Comma-separated distinct program ids seen in the transaction.
|
||||
*/
|
||||
programIdsCsv: string, };
|
||||
@@ -0,0 +1,34 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Candidate account inferred from generic on-chain transaction evidence.
|
||||
*/
|
||||
export type Demo3OnchainDexCandidateAccount = {
|
||||
/**
|
||||
* Account address.
|
||||
*/
|
||||
address: string,
|
||||
/**
|
||||
* Account index in the transaction message when known.
|
||||
*/
|
||||
accountIndex: number | null,
|
||||
/**
|
||||
* Whether the account is writable in the transaction message when known.
|
||||
*/
|
||||
writable: boolean | null,
|
||||
/**
|
||||
* Whether the account is a signer in the transaction message when known.
|
||||
*/
|
||||
signer: boolean | null,
|
||||
/**
|
||||
* Generic role inferred by Demo3.
|
||||
*/
|
||||
inferredRole: string,
|
||||
/**
|
||||
* Confidence of the generic account inference.
|
||||
*/
|
||||
confidence: string,
|
||||
/**
|
||||
* Short reason explaining why the account is listed.
|
||||
*/
|
||||
reason: string, };
|
||||
@@ -0,0 +1,19 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Demo3OnchainDexDiscoveryResult } from "./Demo3OnchainDexDiscoveryResult";
|
||||
|
||||
/**
|
||||
* Response payload returned by Demo3 on-chain DEX discovery.
|
||||
*/
|
||||
export type Demo3OnchainDexDiscoveryPayload = {
|
||||
/**
|
||||
* HTTP role used by the request.
|
||||
*/
|
||||
httpRole: string,
|
||||
/**
|
||||
* Pretty JSON representation of the discovery result.
|
||||
*/
|
||||
resultJson: string,
|
||||
/**
|
||||
* Structured discovery result.
|
||||
*/
|
||||
result: Demo3OnchainDexDiscoveryResult, };
|
||||
@@ -0,0 +1,30 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Request payload for on-chain DEX pair/pool discovery.
|
||||
*/
|
||||
export type Demo3OnchainDexDiscoveryRequest = {
|
||||
/**
|
||||
* Optional DEX code from the support matrix.
|
||||
*/
|
||||
dexCode: string | null,
|
||||
/**
|
||||
* Optional Solana program id. When absent, dex_code must resolve to a verified program id.
|
||||
*/
|
||||
programId: string | null,
|
||||
/**
|
||||
* HTTP role used to query Solana RPC.
|
||||
*/
|
||||
httpRole: string,
|
||||
/**
|
||||
* Maximum number of signatures to inspect.
|
||||
*/
|
||||
signatureLimit: number,
|
||||
/**
|
||||
* Maximum number of transactions to fetch from the signature list.
|
||||
*/
|
||||
transactionLimit: number,
|
||||
/**
|
||||
* Maximum number of candidate rows to return.
|
||||
*/
|
||||
candidateLimit: number, };
|
||||
@@ -0,0 +1,44 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Demo3OnchainDexDiscoveryRequest } from "./Demo3OnchainDexDiscoveryRequest";
|
||||
import type { Demo3OnchainDexPairCandidate } from "./Demo3OnchainDexPairCandidate";
|
||||
|
||||
/**
|
||||
* Structured on-chain DEX discovery result.
|
||||
*/
|
||||
export type Demo3OnchainDexDiscoveryResult = {
|
||||
/**
|
||||
* Normalized request used by kb_lib.
|
||||
*/
|
||||
request: Demo3OnchainDexDiscoveryRequest,
|
||||
/**
|
||||
* DEX code resolved from the support matrix when available.
|
||||
*/
|
||||
resolvedDexCode: string | null,
|
||||
/**
|
||||
* Program id scanned with getSignaturesForAddress.
|
||||
*/
|
||||
resolvedProgramId: string,
|
||||
/**
|
||||
* Number of signatures returned by Solana RPC.
|
||||
*/
|
||||
fetchedSignatureCount: number,
|
||||
/**
|
||||
* Number of fetched transactions.
|
||||
*/
|
||||
fetchedTransactionCount: number,
|
||||
/**
|
||||
* Number of getTransaction calls returning null.
|
||||
*/
|
||||
missingTransactionCount: number,
|
||||
/**
|
||||
* Number of failed transactions encountered.
|
||||
*/
|
||||
failedTransactionCount: number,
|
||||
/**
|
||||
* Number of candidate rows returned.
|
||||
*/
|
||||
candidateCount: number,
|
||||
/**
|
||||
* Candidate on-chain rows.
|
||||
*/
|
||||
candidates: Array<Demo3OnchainDexPairCandidate>, };
|
||||
100
kb_demo_app/frontend/ts/bindings/Demo3OnchainDexPairCandidate.ts
Normal file
100
kb_demo_app/frontend/ts/bindings/Demo3OnchainDexPairCandidate.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Demo3OnchainDexCandidateAccount } from "./Demo3OnchainDexCandidateAccount";
|
||||
import type { Demo3OnchainDexTokenBalanceDelta } from "./Demo3OnchainDexTokenBalanceDelta";
|
||||
|
||||
/**
|
||||
* Candidate on-chain transaction/instruction for a DEX program id.
|
||||
*/
|
||||
export type Demo3OnchainDexPairCandidate = {
|
||||
/**
|
||||
* Transaction signature.
|
||||
*/
|
||||
signature: string,
|
||||
/**
|
||||
* Slot when available.
|
||||
*/
|
||||
slot: number | null,
|
||||
/**
|
||||
* Block time when available.
|
||||
*/
|
||||
blockTime: number | null,
|
||||
/**
|
||||
* Whether the transaction failed.
|
||||
*/
|
||||
failed: boolean,
|
||||
/**
|
||||
* Program id matched by the candidate.
|
||||
*/
|
||||
programId: string,
|
||||
/**
|
||||
* DEX code when known.
|
||||
*/
|
||||
dexCode: string | null,
|
||||
/**
|
||||
* Candidate kind inferred from data/logs.
|
||||
*/
|
||||
candidateKind: string,
|
||||
/**
|
||||
* Extraction confidence.
|
||||
*/
|
||||
confidence: string,
|
||||
/**
|
||||
* Top-level instruction index.
|
||||
*/
|
||||
instructionIndex: number | null,
|
||||
/**
|
||||
* Inner instruction index.
|
||||
*/
|
||||
innerInstructionIndex: number | null,
|
||||
/**
|
||||
* Instruction name inferred from data/logs.
|
||||
*/
|
||||
instructionName: string | null,
|
||||
/**
|
||||
* Candidate pool address.
|
||||
*/
|
||||
poolAddress: string | null,
|
||||
/**
|
||||
* Candidate token A mint.
|
||||
*/
|
||||
tokenAMint: string | null,
|
||||
/**
|
||||
* Candidate token B mint.
|
||||
*/
|
||||
tokenBMint: string | null,
|
||||
/**
|
||||
* Verified pool address when a DEX decoder or stable layout proves it.
|
||||
*/
|
||||
verifiedPoolAddress: string | null,
|
||||
/**
|
||||
* Token mints observed generically from transaction token balances.
|
||||
*/
|
||||
observedTokenMints: Array<string>,
|
||||
/**
|
||||
* Token balance deltas observed through transaction metadata.
|
||||
*/
|
||||
tokenBalanceDeltas: Array<Demo3OnchainDexTokenBalanceDelta>,
|
||||
/**
|
||||
* Program-owned or writable accounts that may be pool/config/state accounts.
|
||||
*/
|
||||
candidatePoolAccounts: Array<Demo3OnchainDexCandidateAccount>,
|
||||
/**
|
||||
* Token accounts that may be pool vaults.
|
||||
*/
|
||||
candidateTokenVaultAccounts: Array<Demo3OnchainDexCandidateAccount>,
|
||||
/**
|
||||
* Other candidate accounts attached to the matched instruction.
|
||||
*/
|
||||
candidateProgramAccounts: Array<Demo3OnchainDexCandidateAccount>,
|
||||
/**
|
||||
* Short account sample.
|
||||
*/
|
||||
accountSamples: Array<string>,
|
||||
/**
|
||||
* Short log sample.
|
||||
*/
|
||||
logSamples: Array<string>,
|
||||
/**
|
||||
* Suggested next action.
|
||||
*/
|
||||
backfillHint: string, };
|
||||
@@ -0,0 +1,38 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Token-balance delta observed in one on-chain candidate transaction.
|
||||
*/
|
||||
export type Demo3OnchainDexTokenBalanceDelta = {
|
||||
/**
|
||||
* Token account index in the transaction message when available.
|
||||
*/
|
||||
accountIndex: number | null,
|
||||
/**
|
||||
* Token account address resolved from the transaction account keys.
|
||||
*/
|
||||
accountAddress: string | null,
|
||||
/**
|
||||
* SPL Token or Token-2022 mint address.
|
||||
*/
|
||||
mint: string,
|
||||
/**
|
||||
* Token account owner when Solana RPC exposes it.
|
||||
*/
|
||||
owner: string | null,
|
||||
/**
|
||||
* Token program id when Solana RPC exposes it.
|
||||
*/
|
||||
tokenProgram: string | null,
|
||||
/**
|
||||
* Raw token amount before the transaction.
|
||||
*/
|
||||
preAmountRaw: string | null,
|
||||
/**
|
||||
* Raw token amount after the transaction.
|
||||
*/
|
||||
postAmountRaw: string | null,
|
||||
/**
|
||||
* Signed raw delta when calculable.
|
||||
*/
|
||||
deltaRaw: string | null, };
|
||||
@@ -0,0 +1,66 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Matching decoded event sample for corpus discovery.
|
||||
*/
|
||||
export type Demo3oldLocalDexCorpusDecodedEventSample = {
|
||||
/**
|
||||
* Decoded event id.
|
||||
*/
|
||||
decodedEventId: number,
|
||||
/**
|
||||
* Transaction id.
|
||||
*/
|
||||
transactionId: number,
|
||||
/**
|
||||
* Transaction signature.
|
||||
*/
|
||||
signature: string,
|
||||
/**
|
||||
* Optional Solana slot.
|
||||
*/
|
||||
slot: number | null,
|
||||
/**
|
||||
* Protocol name stored on the decoded event.
|
||||
*/
|
||||
protocolName: string,
|
||||
/**
|
||||
* Program id stored on the decoded event.
|
||||
*/
|
||||
programId: string,
|
||||
/**
|
||||
* Event kind.
|
||||
*/
|
||||
eventKind: string,
|
||||
/**
|
||||
* Optional pool account.
|
||||
*/
|
||||
poolAccount: string | null,
|
||||
/**
|
||||
* Optional token A mint.
|
||||
*/
|
||||
tokenAMint: string | null,
|
||||
/**
|
||||
* Optional token B mint.
|
||||
*/
|
||||
tokenBMint: string | null,
|
||||
/**
|
||||
* Decoded event category extracted from payload JSON.
|
||||
*/
|
||||
eventCategory: string | null,
|
||||
/**
|
||||
* Decoded event lifecycle kind extracted from payload JSON.
|
||||
*/
|
||||
eventLifecycleKind: string | null,
|
||||
/**
|
||||
* Decoded event actionability extracted from payload JSON.
|
||||
*/
|
||||
eventActionability: string | null,
|
||||
/**
|
||||
* Whether the decoded event is a trade candidate.
|
||||
*/
|
||||
tradeCandidate: boolean,
|
||||
/**
|
||||
* Whether the decoded event is a candle candidate.
|
||||
*/
|
||||
candleCandidate: boolean, };
|
||||
@@ -0,0 +1,58 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Matching pool/pair sample for corpus discovery.
|
||||
*/
|
||||
export type Demo3oldLocalDexCorpusPoolPairSample = {
|
||||
/**
|
||||
* Optional pool id.
|
||||
*/
|
||||
poolId: number | null,
|
||||
/**
|
||||
* Optional pool address.
|
||||
*/
|
||||
poolAddress: string | null,
|
||||
/**
|
||||
* Optional pair id.
|
||||
*/
|
||||
pairId: number | null,
|
||||
/**
|
||||
* Optional DEX code.
|
||||
*/
|
||||
dexCode: string | null,
|
||||
/**
|
||||
* Optional pair symbol.
|
||||
*/
|
||||
pairSymbol: string | null,
|
||||
/**
|
||||
* Optional base token mint.
|
||||
*/
|
||||
baseMint: string | null,
|
||||
/**
|
||||
* Optional base token symbol.
|
||||
*/
|
||||
baseSymbol: string | null,
|
||||
/**
|
||||
* Optional quote token mint.
|
||||
*/
|
||||
quoteMint: string | null,
|
||||
/**
|
||||
* Optional quote token symbol.
|
||||
*/
|
||||
quoteSymbol: string | null,
|
||||
/**
|
||||
* Number of decoded events attached to the pool.
|
||||
*/
|
||||
decodedEventCount: number,
|
||||
/**
|
||||
* Number of trade events attached to the pair.
|
||||
*/
|
||||
tradeEventCount: number,
|
||||
/**
|
||||
* Number of candle rows attached to the pair.
|
||||
*/
|
||||
pairCandleCount: number,
|
||||
/**
|
||||
* Latest known founding or activity signature for the pool/pair.
|
||||
*/
|
||||
latestSignature: string | null, };
|
||||
@@ -0,0 +1,19 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Demo3oldLocalDexCorpusSearchResult } from "./Demo3oldLocalDexCorpusSearchResult";
|
||||
|
||||
/**
|
||||
* Response payload returned by Demo3old local DEX corpus search.
|
||||
*/
|
||||
export type Demo3oldLocalDexCorpusSearchPayload = {
|
||||
/**
|
||||
* Open database URL.
|
||||
*/
|
||||
databaseUrl: string,
|
||||
/**
|
||||
* Pretty JSON representation of the search result.
|
||||
*/
|
||||
resultJson: string,
|
||||
/**
|
||||
* Structured local DEX corpus search result.
|
||||
*/
|
||||
result: Demo3oldLocalDexCorpusSearchResult, };
|
||||
@@ -0,0 +1,34 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Request payload for a local DEX corpus search.
|
||||
*/
|
||||
export type Demo3oldLocalDexCorpusSearchRequest = {
|
||||
/**
|
||||
* Optional DEX code or decoded protocol name.
|
||||
*/
|
||||
dexCode: string | null,
|
||||
/**
|
||||
* Optional Solana program id.
|
||||
*/
|
||||
programId: string | null,
|
||||
/**
|
||||
* Optional local pair id.
|
||||
*/
|
||||
pairId: number | null,
|
||||
/**
|
||||
* Optional pool account/address.
|
||||
*/
|
||||
poolAddress: string | null,
|
||||
/**
|
||||
* Optional token mint to match as base, quote or decoded mint.
|
||||
*/
|
||||
tokenMint: string | null,
|
||||
/**
|
||||
* Optional transaction signature.
|
||||
*/
|
||||
signature: string | null,
|
||||
/**
|
||||
* Maximum number of rows to return per sample category.
|
||||
*/
|
||||
limit: number, };
|
||||
@@ -0,0 +1,31 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Demo3oldLocalDexCorpusDecodedEventSample } from "./Demo3oldLocalDexCorpusDecodedEventSample";
|
||||
import type { Demo3oldLocalDexCorpusPoolPairSample } from "./Demo3oldLocalDexCorpusPoolPairSample";
|
||||
import type { Demo3oldLocalDexCorpusSearchRequest } from "./Demo3oldLocalDexCorpusSearchRequest";
|
||||
import type { Demo3oldLocalDexCorpusSearchSummary } from "./Demo3oldLocalDexCorpusSearchSummary";
|
||||
import type { Demo3oldLocalDexCorpusTransactionSample } from "./Demo3oldLocalDexCorpusTransactionSample";
|
||||
|
||||
/**
|
||||
* Structured local DEX corpus search result.
|
||||
*/
|
||||
export type Demo3oldLocalDexCorpusSearchResult = {
|
||||
/**
|
||||
* Normalized search request applied by the backend service.
|
||||
*/
|
||||
request: Demo3oldLocalDexCorpusSearchRequest,
|
||||
/**
|
||||
* Aggregate counts for the matching local data.
|
||||
*/
|
||||
summary: Demo3oldLocalDexCorpusSearchSummary,
|
||||
/**
|
||||
* Matching transaction samples.
|
||||
*/
|
||||
transactionSamples: Array<Demo3oldLocalDexCorpusTransactionSample>,
|
||||
/**
|
||||
* Matching pool/pair samples.
|
||||
*/
|
||||
poolPairSamples: Array<Demo3oldLocalDexCorpusPoolPairSample>,
|
||||
/**
|
||||
* Matching decoded event samples.
|
||||
*/
|
||||
decodedEventSamples: Array<Demo3oldLocalDexCorpusDecodedEventSample>, };
|
||||
@@ -0,0 +1,38 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Aggregate counts for one local DEX corpus search.
|
||||
*/
|
||||
export type Demo3oldLocalDexCorpusSearchSummary = {
|
||||
/**
|
||||
* Number of distinct matching transactions.
|
||||
*/
|
||||
transactionCount: number,
|
||||
/**
|
||||
* Number of distinct matching instructions.
|
||||
*/
|
||||
instructionCount: number,
|
||||
/**
|
||||
* Number of distinct matching decoded DEX events.
|
||||
*/
|
||||
decodedEventCount: number,
|
||||
/**
|
||||
* Number of distinct matching pools.
|
||||
*/
|
||||
poolCount: number,
|
||||
/**
|
||||
* Number of distinct matching pairs.
|
||||
*/
|
||||
pairCount: number,
|
||||
/**
|
||||
* Number of distinct matching trade events.
|
||||
*/
|
||||
tradeEventCount: number,
|
||||
/**
|
||||
* Number of distinct matching candle rows.
|
||||
*/
|
||||
pairCandleCount: number,
|
||||
/**
|
||||
* Number of distinct matching protocol candidate rows.
|
||||
*/
|
||||
protocolCandidateCount: number, };
|
||||
@@ -0,0 +1,38 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Matching transaction sample for corpus discovery.
|
||||
*/
|
||||
export type Demo3oldLocalDexCorpusTransactionSample = {
|
||||
/**
|
||||
* Transaction id.
|
||||
*/
|
||||
transactionId: number,
|
||||
/**
|
||||
* Transaction signature.
|
||||
*/
|
||||
signature: string,
|
||||
/**
|
||||
* Optional Solana slot.
|
||||
*/
|
||||
slot: number | null,
|
||||
/**
|
||||
* Whether the transaction has a non-null error payload.
|
||||
*/
|
||||
failed: boolean,
|
||||
/**
|
||||
* Number of persisted instructions for the transaction.
|
||||
*/
|
||||
instructionCount: number,
|
||||
/**
|
||||
* Number of persisted decoded DEX events for the transaction.
|
||||
*/
|
||||
decodedEventCount: number,
|
||||
/**
|
||||
* Number of persisted trade events for the transaction.
|
||||
*/
|
||||
tradeEventCount: number,
|
||||
/**
|
||||
* Comma-separated distinct program ids seen in the transaction.
|
||||
*/
|
||||
programIdsCsv: string, };
|
||||
@@ -10,7 +10,7 @@ export type DemoPipeline2BackfillPayload = {
|
||||
*/
|
||||
objectKey: string,
|
||||
/**
|
||||
* Mode: `tokenMint` or `poolAddress`.
|
||||
* Mode: `tokenMint`, `poolAddress` or `signature`.
|
||||
*/
|
||||
mode: string,
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Request payload for single-signature backfill.
|
||||
*/
|
||||
export type DemoPipeline2BackfillSignatureRequest = {
|
||||
/**
|
||||
* Transaction signature to resolve and replay.
|
||||
*/
|
||||
signature: string,
|
||||
/**
|
||||
* Optional HTTP role.
|
||||
*/
|
||||
httpRole: string | null, };
|
||||
365
kb_demo_app/frontend/ts/demo3.ts
Normal file
365
kb_demo_app/frontend/ts/demo3.ts
Normal file
@@ -0,0 +1,365 @@
|
||||
// file: kb_demo_app/frontend/ts/demo3.ts
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing";
|
||||
import type { Demo3LocalDexCorpusSearchRequest } from "./bindings/Demo3LocalDexCorpusSearchRequest.ts";
|
||||
import type { Demo3LocalDexCorpusSearchPayload } from "./bindings/Demo3LocalDexCorpusSearchPayload.ts";
|
||||
import type { Demo3OnchainDexDiscoveryRequest } from "./bindings/Demo3OnchainDexDiscoveryRequest.ts";
|
||||
import type { Demo3OnchainDexDiscoveryResult } from "./bindings/Demo3OnchainDexDiscoveryResult.ts";
|
||||
import type { Demo3OnchainDexPairCandidate } from "./bindings/Demo3OnchainDexPairCandidate.ts";
|
||||
import type { Demo3LocalDexCorpusSearchResult } from "./bindings/Demo3LocalDexCorpusSearchResult.ts";
|
||||
import type { Demo3OnchainDexDiscoveryPayload } from "./bindings/Demo3OnchainDexDiscoveryPayload.ts";
|
||||
|
||||
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
|
||||
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
||||
|
||||
|
||||
interface Demo3Preset {
|
||||
label: string;
|
||||
dexCode: string;
|
||||
programId: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const presets: Demo3Preset[] = [
|
||||
{ label: "PumpSwap", dexCode: "pump_swap", programId: "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", description: "DEX effectif PumpSwap." },
|
||||
{ label: "Raydium CPMM", dexCode: "raydium_cpmm", programId: "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C", description: "Raydium CPMM." },
|
||||
{ label: "Raydium CLMM", dexCode: "raydium_clmm", programId: "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK", description: "Raydium CLMM." },
|
||||
{ label: "Raydium AMM v4", dexCode: "raydium_amm_v4", programId: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", description: "Raydium AMM v4 legacy. À prouver par corpus avant décodage swap." },
|
||||
{ label: "Raydium Stable Swap", dexCode: "raydium_stable_swap", programId: "5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h", description: "Stable Swap Raydium à vérifier par corpus." },
|
||||
{ label: "Meteora DLMM", dexCode: "meteora_dlmm", programId: "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo", description: "Meteora DLMM." },
|
||||
{ label: "Meteora DAMM v1", dexCode: "meteora_damm_v1", programId: "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB", description: "Meteora DAMM v1." },
|
||||
{ label: "Meteora DAMM v2", dexCode: "meteora_damm_v2", programId: "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG", description: "Meteora DAMM v2." },
|
||||
{ label: "Meteora DBC", dexCode: "meteora_dbc", programId: "dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN", description: "Meteora DBC." },
|
||||
{ label: "Orca Whirlpools", dexCode: "orca_whirlpools", programId: "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc", description: "Orca Whirlpools CLMM." },
|
||||
{ label: "FluxBeam", dexCode: "fluxbeam", programId: "FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X", description: "FluxBeam." },
|
||||
{ label: "DexLab", dexCode: "dexlab", programId: "DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N", description: "DexLab Swap/Pool." },
|
||||
{ label: "metaDAO", dexCode: "metadao", programId: "", description: "DEX à vérifier. Aucun program id n'est inventé." },
|
||||
{ label: "Printr", dexCode: "printr", programId: "", description: "DEX à vérifier. Aucun program id n'est inventé." },
|
||||
];
|
||||
|
||||
let lastResultJson = "";
|
||||
|
||||
function byId<T extends HTMLElement>(id: string): T {
|
||||
const element = document.getElementById(id);
|
||||
if (element === null) {
|
||||
throw new Error(`missing element #${id}`);
|
||||
}
|
||||
return element as T;
|
||||
}
|
||||
|
||||
function valueOrNull(value: string): string | null {
|
||||
const trimmed = value.trim();
|
||||
return trimmed === "" ? null : trimmed;
|
||||
}
|
||||
|
||||
function numberValueOrNull(value: string): number | null {
|
||||
const trimmed = value.trim();
|
||||
if (trimmed === "") {
|
||||
return null;
|
||||
}
|
||||
const parsed = Number.parseInt(trimmed, 10);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
function intValue(id: string, fallback: number): number {
|
||||
const parsed = Number.parseInt(byId<HTMLInputElement>(id).value, 10);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
||||
}
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function shortText(value: string | null, maxLength: number): string {
|
||||
if (value === null) {
|
||||
return "-";
|
||||
}
|
||||
if (value.length <= maxLength) {
|
||||
return value;
|
||||
}
|
||||
return `${value.slice(0, maxLength)}…`;
|
||||
}
|
||||
|
||||
function shortList(values: string[], maxItems: number, itemLength: number): string {
|
||||
if (values.length === 0) {
|
||||
return "-";
|
||||
}
|
||||
return values.slice(0, maxItems).map((value) => shortText(value, itemLength)).join(", ");
|
||||
}
|
||||
|
||||
function candidateAccountList(accounts: Demo3OnchainDexPairCandidate["candidatePoolAccounts"], maxItems: number): string {
|
||||
if (accounts.length === 0) {
|
||||
return "-";
|
||||
}
|
||||
return accounts.slice(0, maxItems).map((account) => shortText(account.address, 14)).join(", ");
|
||||
}
|
||||
|
||||
function tokenDeltaList(candidate: Demo3OnchainDexPairCandidate): string {
|
||||
if (candidate.tokenBalanceDeltas.length === 0) {
|
||||
return "-";
|
||||
}
|
||||
return candidate.tokenBalanceDeltas.slice(0, 4).map((delta) => {
|
||||
const amount = delta.deltaRaw ?? "?";
|
||||
return `${shortText(delta.mint, 10)}:${amount}`;
|
||||
}).join(", ");
|
||||
}
|
||||
|
||||
function appendLogLine(line: string): void {
|
||||
const textarea = byId<HTMLTextAreaElement>("demo3LogTextarea");
|
||||
const timestamp = new Date().toLocaleTimeString("fr-CH", { hour12: false });
|
||||
const lines = textarea.value === "" ? [] : textarea.value.split("\n");
|
||||
lines.push(`[${timestamp}] ${line}`);
|
||||
textarea.value = lines.slice(-400).join("\n");
|
||||
textarea.scrollTop = textarea.scrollHeight;
|
||||
}
|
||||
|
||||
function setStatus(label: string, cssClass: string): void {
|
||||
const badge = byId<HTMLElement>("demo3StatusBadge");
|
||||
badge.className = `badge ${cssClass}`;
|
||||
badge.textContent = label;
|
||||
}
|
||||
|
||||
function populatePresetSelect(): void {
|
||||
const select = byId<HTMLSelectElement>("demo3PresetSelect");
|
||||
select.innerHTML = '<option value="">Custom / empty</option>';
|
||||
presets.forEach((preset, index) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = String(index);
|
||||
option.textContent = preset.label;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
function applyPreset(indexText: string): void {
|
||||
if (indexText === "") {
|
||||
return;
|
||||
}
|
||||
const index = Number.parseInt(indexText, 10);
|
||||
if (!Number.isFinite(index) || index < 0 || index >= presets.length) {
|
||||
return;
|
||||
}
|
||||
const preset = presets[index];
|
||||
byId<HTMLInputElement>("demo3DexCodeInput").value = preset.dexCode;
|
||||
byId<HTMLInputElement>("demo3ProgramIdInput").value = preset.programId;
|
||||
byId<HTMLElement>("demo3PresetHelp").textContent = preset.description;
|
||||
}
|
||||
|
||||
function readOnchainRequest(): Demo3OnchainDexDiscoveryRequest {
|
||||
return {
|
||||
dexCode: valueOrNull(byId<HTMLInputElement>("demo3DexCodeInput").value),
|
||||
programId: valueOrNull(byId<HTMLInputElement>("demo3ProgramIdInput").value),
|
||||
httpRole: byId<HTMLInputElement>("demo3HttpRoleInput").value.trim() || "history_backfill",
|
||||
signatureLimit: intValue("demo3SignatureLimitInput", 50),
|
||||
transactionLimit: intValue("demo3TransactionLimitInput", 25),
|
||||
candidateLimit: intValue("demo3CandidateLimitInput", 25),
|
||||
};
|
||||
}
|
||||
|
||||
function readLocalRequest(): Demo3LocalDexCorpusSearchRequest {
|
||||
return {
|
||||
dexCode: valueOrNull(byId<HTMLInputElement>("demo3DexCodeInput").value),
|
||||
programId: valueOrNull(byId<HTMLInputElement>("demo3ProgramIdInput").value),
|
||||
pairId: numberValueOrNull(byId<HTMLInputElement>("demo3PairIdInput").value),
|
||||
poolAddress: valueOrNull(byId<HTMLInputElement>("demo3PoolAddressInput").value),
|
||||
tokenMint: valueOrNull(byId<HTMLInputElement>("demo3TokenMintInput").value),
|
||||
signature: valueOrNull(byId<HTMLInputElement>("demo3SignatureInput").value),
|
||||
limit: intValue("demo3CandidateLimitInput", 25),
|
||||
};
|
||||
}
|
||||
|
||||
function clearFilters(): void {
|
||||
byId<HTMLInputElement>("demo3DexCodeInput").value = "";
|
||||
byId<HTMLInputElement>("demo3ProgramIdInput").value = "";
|
||||
byId<HTMLInputElement>("demo3PairIdInput").value = "";
|
||||
byId<HTMLInputElement>("demo3PoolAddressInput").value = "";
|
||||
byId<HTMLInputElement>("demo3TokenMintInput").value = "";
|
||||
byId<HTMLInputElement>("demo3SignatureInput").value = "";
|
||||
byId<HTMLSelectElement>("demo3PresetSelect").value = "";
|
||||
byId<HTMLElement>("demo3PresetHelp").textContent = "Choisis un DEX ou saisis un program id manuellement.";
|
||||
}
|
||||
|
||||
function renderOnchainResult(result: Demo3OnchainDexDiscoveryResult): void {
|
||||
byId<HTMLElement>("demo3SummarySignatureCount").textContent = String(result.fetchedSignatureCount);
|
||||
byId<HTMLElement>("demo3SummaryFetchedTxCount").textContent = String(result.fetchedTransactionCount);
|
||||
byId<HTMLElement>("demo3SummaryMissingTxCount").textContent = String(result.missingTransactionCount);
|
||||
byId<HTMLElement>("demo3SummaryFailedTxCount").textContent = String(result.failedTransactionCount);
|
||||
byId<HTMLElement>("demo3SummaryCandidateCount").textContent = String(result.candidateCount);
|
||||
byId<HTMLElement>("demo3TargetText").textContent = `${result.resolvedDexCode ?? "custom"} / ${result.resolvedProgramId}`;
|
||||
renderOnchainCandidates(result.candidates);
|
||||
}
|
||||
|
||||
function renderOnchainCandidates(candidates: Demo3OnchainDexPairCandidate[]): void {
|
||||
const body = byId<HTMLTableSectionElement>("demo3OnchainCandidateTableBody");
|
||||
if (candidates.length === 0) {
|
||||
body.innerHTML = '<tr><td colspan="11" class="text-body-secondary">No on-chain candidate.</td></tr>';
|
||||
return;
|
||||
}
|
||||
body.innerHTML = candidates.map((candidate) => {
|
||||
const verifiedPool = candidate.verifiedPoolAddress;
|
||||
const firstCandidatePool = candidate.candidatePoolAccounts.length > 0 ? candidate.candidatePoolAccounts[0].address : null;
|
||||
const poolForFilter = verifiedPool ?? candidate.poolAddress ?? firstCandidatePool;
|
||||
if (poolForFilter !== null) {
|
||||
byId<HTMLInputElement>("demo3PoolAddressInput").value = poolForFilter;
|
||||
}
|
||||
if (candidate.tokenAMint !== null && byId<HTMLInputElement>("demo3TokenMintInput").value.trim() === "") {
|
||||
byId<HTMLInputElement>("demo3TokenMintInput").value = candidate.tokenAMint;
|
||||
}
|
||||
if (candidate.tokenAMint === null && candidate.observedTokenMints.length > 0 && byId<HTMLInputElement>("demo3TokenMintInput").value.trim() === "") {
|
||||
byId<HTMLInputElement>("demo3TokenMintInput").value = candidate.observedTokenMints[0];
|
||||
}
|
||||
byId<HTMLInputElement>("demo3SignatureInput").value = candidate.signature;
|
||||
const accountTitle = candidate.candidatePoolAccounts.map((account) => `${account.address} (${account.reason})`).join("\n");
|
||||
const vaultTitle = candidate.candidateTokenVaultAccounts.map((account) => `${account.address} (${account.reason})`).join("\n");
|
||||
return `
|
||||
<tr>
|
||||
<td class="font-monospace" title="${escapeHtml(candidate.signature)}">${escapeHtml(shortText(candidate.signature, 18))}</td>
|
||||
<td>${candidate.slot ?? "-"}</td>
|
||||
<td><span class="badge text-bg-info">${escapeHtml(candidate.candidateKind)}</span></td>
|
||||
<td><span class="badge text-bg-${candidate.confidence === "high" ? "success" : candidate.confidence === "medium" ? "warning" : "secondary"}">${escapeHtml(candidate.confidence)}</span></td>
|
||||
<td class="font-monospace" title="${escapeHtml(verifiedPool ?? "")}">${escapeHtml(shortText(verifiedPool, 14))}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(candidate.tokenAMint ?? "")}">${escapeHtml(shortText(candidate.tokenAMint, 14))}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(candidate.tokenBMint ?? "")}">${escapeHtml(shortText(candidate.tokenBMint, 14))}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(candidate.observedTokenMints.join("\n"))}">${escapeHtml(shortList(candidate.observedTokenMints, 3, 10))}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(tokenDeltaList(candidate))}">${escapeHtml(tokenDeltaList(candidate))}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(accountTitle)}">${escapeHtml(candidateAccountList(candidate.candidatePoolAccounts, 3))}<br /><span class="text-body-secondary">vaults: ${escapeHtml(candidateAccountList(candidate.candidateTokenVaultAccounts, 2))}</span></td>
|
||||
<td class="small" title="${escapeHtml(vaultTitle)}">${escapeHtml(candidate.backfillHint)}</td>
|
||||
</tr>`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function renderLocalResult(result: Demo3LocalDexCorpusSearchResult): void {
|
||||
byId<HTMLElement>("demo3SummaryLocalPairCount").textContent = String(result.summary.pairCount);
|
||||
const body = byId<HTMLTableSectionElement>("demo3LocalPoolPairTableBody");
|
||||
if (result.poolPairSamples.length === 0) {
|
||||
body.innerHTML = '<tr><td colspan="8" class="text-body-secondary">No local pool/pair sample.</td></tr>';
|
||||
return;
|
||||
}
|
||||
body.innerHTML = result.poolPairSamples.map((sample) => `
|
||||
<tr>
|
||||
<td>${escapeHtml(sample.dexCode ?? "-")}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(sample.poolAddress ?? "")}">${escapeHtml(shortText(sample.poolAddress, 16))}</td>
|
||||
<td>${sample.pairId ?? "-"}</td>
|
||||
<td>${escapeHtml(sample.pairSymbol ?? "-")}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(sample.baseMint ?? "")}">${escapeHtml(sample.baseSymbol ?? shortText(sample.baseMint, 12))}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(sample.quoteMint ?? "")}">${escapeHtml(sample.quoteSymbol ?? shortText(sample.quoteMint, 12))}</td>
|
||||
<td>${sample.tradeEventCount}</td>
|
||||
<td>${sample.pairCandleCount}</td>
|
||||
</tr>`).join("");
|
||||
}
|
||||
|
||||
async function discoverOnchain(): Promise<void> {
|
||||
const request = readOnchainRequest();
|
||||
setStatus("running", "text-bg-warning");
|
||||
appendLogLine(`on-chain discovery dex='${request.dexCode ?? ""}' program='${request.programId ?? ""}' role='${request.httpRole}'`);
|
||||
try {
|
||||
const payload = await invoke<Demo3OnchainDexDiscoveryPayload>("demo3_discover_onchain_dex_pairs", { request });
|
||||
lastResultJson = payload.resultJson;
|
||||
byId<HTMLTextAreaElement>("demo3JsonTextarea").value = payload.resultJson;
|
||||
renderOnchainResult(payload.result);
|
||||
setStatus("ok", "text-bg-success");
|
||||
appendLogLine(`on-chain discovery completed: candidates='${payload.result.candidateCount}' signatures='${payload.result.fetchedSignatureCount}'`);
|
||||
} catch (error) {
|
||||
setStatus("error", "text-bg-danger");
|
||||
appendLogLine(`on-chain discovery failed: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function searchLocalDb(): Promise<void> {
|
||||
const request = readLocalRequest();
|
||||
setStatus("running", "text-bg-warning");
|
||||
appendLogLine("local DB search started");
|
||||
try {
|
||||
const payload = await invoke<Demo3LocalDexCorpusSearchPayload>("demo3_search_local_dex_corpus", { request });
|
||||
lastResultJson = payload.resultJson;
|
||||
byId<HTMLTextAreaElement>("demo3JsonTextarea").value = payload.resultJson;
|
||||
renderLocalResult(payload.result);
|
||||
setStatus("ok", "text-bg-success");
|
||||
appendLogLine(`local DB search completed: pairs='${payload.result.summary.pairCount}' tx='${payload.result.summary.transactionCount}'`);
|
||||
} catch (error) {
|
||||
setStatus("error", "text-bg-danger");
|
||||
appendLogLine(`local DB search failed: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function copyJson(): Promise<void> {
|
||||
if (lastResultJson === "") {
|
||||
appendLogLine("nothing to copy");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(lastResultJson);
|
||||
appendLogLine("JSON copied to clipboard");
|
||||
} catch (error) {
|
||||
appendLogLine(`copy failed: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function init(): void {
|
||||
takeoverConsole();
|
||||
void debug("demo3 on-chain discovery initialized");
|
||||
debug("demo3 window loaded");
|
||||
|
||||
const sidebarToggle = document.querySelector<HTMLButtonElement>('#sidebarToggle');
|
||||
if (sidebarToggle) {
|
||||
// restaurer l’état depuis localStorage
|
||||
if (localStorage.getItem('sidebar-toggle') === 'true') {
|
||||
document.body.classList.add('sidenav-toggled');
|
||||
}
|
||||
|
||||
sidebarToggle.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
document.body.classList.toggle('sidenav-toggled');
|
||||
localStorage.setItem('sidebar-toggle', document.body.classList.contains('sidenav-toggled') ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||
const toastElList = document.querySelectorAll('.toast');
|
||||
Array.from(toastElList).map(toastEl => new bootstrap.Toast(toastEl));
|
||||
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]');
|
||||
Array.from(popoverTriggerList).map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl));
|
||||
|
||||
const gobackto = location.pathname + location.search;
|
||||
|
||||
document.querySelectorAll<HTMLAnchorElement>('a[data-setlang]').forEach((a) => {
|
||||
const href = a.getAttribute("href");
|
||||
if (!href) return; // pas de href => on ignore
|
||||
|
||||
const url = new URL(href, location.origin);
|
||||
url.searchParams.set("gobackto", gobackto);
|
||||
|
||||
// conserve une URL relative (path + query)
|
||||
a.setAttribute("href", url.pathname + "?" + url.searchParams.toString());
|
||||
});
|
||||
|
||||
populatePresetSelect();
|
||||
byId<HTMLSelectElement>("demo3PresetSelect").addEventListener("change", (event) => {
|
||||
applyPreset((event.target as HTMLSelectElement).value);
|
||||
});
|
||||
byId<HTMLButtonElement>("demo3DiscoverButton").addEventListener("click", () => {
|
||||
void discoverOnchain();
|
||||
});
|
||||
byId<HTMLButtonElement>("demo3LocalSearchButton").addEventListener("click", () => {
|
||||
void searchLocalDb();
|
||||
});
|
||||
byId<HTMLButtonElement>("demo3ClearFiltersButton").addEventListener("click", clearFilters);
|
||||
byId<HTMLButtonElement>("demo3CopyJsonButton").addEventListener("click", () => {
|
||||
void copyJson();
|
||||
});
|
||||
byId<HTMLButtonElement>("demo3ClearLogButton").addEventListener("click", () => {
|
||||
byId<HTMLTextAreaElement>("demo3LogTextarea").value = "";
|
||||
});
|
||||
appendLogLine("ready");
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
435
kb_demo_app/frontend/ts/demo3old.ts
Normal file
435
kb_demo_app/frontend/ts/demo3old.ts
Normal file
@@ -0,0 +1,435 @@
|
||||
// file: kb_demo_app/frontend/ts/demo3old.ts
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing";
|
||||
import type { Demo3oldLocalDexCorpusSearchRequest } from "./bindings/Demo3oldLocalDexCorpusSearchRequest.ts";
|
||||
import type { Demo3oldLocalDexCorpusSearchPayload } from "./bindings/Demo3oldLocalDexCorpusSearchPayload.ts";
|
||||
import type { Demo3oldLocalDexCorpusSearchSummary } from "./bindings/Demo3oldLocalDexCorpusSearchSummary.ts";
|
||||
import type { Demo3oldLocalDexCorpusTransactionSample } from "./bindings/Demo3oldLocalDexCorpusTransactionSample.ts";
|
||||
import type { Demo3oldLocalDexCorpusPoolPairSample } from "./bindings/Demo3oldLocalDexCorpusPoolPairSample.ts";
|
||||
import type { Demo3oldLocalDexCorpusDecodedEventSample } from "./bindings/Demo3oldLocalDexCorpusDecodedEventSample.ts";
|
||||
|
||||
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
|
||||
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
||||
|
||||
|
||||
|
||||
interface Demo3oldPreset {
|
||||
label: string;
|
||||
dexCode: string;
|
||||
programId: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const presets: Demo3oldPreset[] = [
|
||||
{
|
||||
label: "PumpSwap",
|
||||
dexCode: "pump_swap",
|
||||
programId: "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA",
|
||||
description: "DEX effectif PumpSwap.",
|
||||
},
|
||||
{
|
||||
label: "Raydium CPMM",
|
||||
dexCode: "raydium_cpmm",
|
||||
programId: "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C",
|
||||
description: "Raydium CPMM, swap AMM moderne.",
|
||||
},
|
||||
{
|
||||
label: "Raydium CLMM",
|
||||
dexCode: "raydium_clmm",
|
||||
programId: "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK",
|
||||
description: "Raydium CLMM, actuellement observé dans le corpus local.",
|
||||
},
|
||||
{
|
||||
label: "Raydium AMM v4",
|
||||
dexCode: "raydium_amm_v4",
|
||||
programId: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
|
||||
description: "Raydium AMM v4 legacy. À rechercher par corpus avant décodage swap.",
|
||||
},
|
||||
{
|
||||
label: "Raydium Stable Swap",
|
||||
dexCode: "raydium_stable_swap",
|
||||
programId: "5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h",
|
||||
description: "Stable Swap Raydium à vérifier par transactions locales.",
|
||||
},
|
||||
{
|
||||
label: "Raydium Router",
|
||||
dexCode: "raydium_router",
|
||||
programId: "routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS",
|
||||
description: "Router Raydium. Ne doit pas être promu comme DEX direct sans preuve.",
|
||||
},
|
||||
{
|
||||
label: "Meteora DLMM",
|
||||
dexCode: "meteora_dlmm",
|
||||
programId: "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo",
|
||||
description: "Meteora DLMM.",
|
||||
},
|
||||
{
|
||||
label: "Meteora DAMM v1",
|
||||
dexCode: "meteora_damm_v1",
|
||||
programId: "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB",
|
||||
description: "Meteora DAMM v1, swaps non actionnables sans montants exploitables.",
|
||||
},
|
||||
{
|
||||
label: "Meteora DAMM v2",
|
||||
dexCode: "meteora_damm_v2",
|
||||
programId: "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG",
|
||||
description: "Meteora DAMM v2.",
|
||||
},
|
||||
{
|
||||
label: "Meteora DBC",
|
||||
dexCode: "meteora_dbc",
|
||||
programId: "dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN",
|
||||
description: "Meteora DBC, bonding curve / DEX effectif partiel.",
|
||||
},
|
||||
{
|
||||
label: "Orca Whirlpools",
|
||||
dexCode: "orca_whirlpools",
|
||||
programId: "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc",
|
||||
description: "Orca Whirlpools CLMM.",
|
||||
},
|
||||
{
|
||||
label: "FluxBeam",
|
||||
dexCode: "fluxbeam",
|
||||
programId: "FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X",
|
||||
description: "FluxBeam AMM à consolider par corpus.",
|
||||
},
|
||||
{
|
||||
label: "DexLab",
|
||||
dexCode: "dexlab",
|
||||
programId: "DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N",
|
||||
description: "DexLab Swap/Pool à consolider par corpus.",
|
||||
},
|
||||
{
|
||||
label: "metaDAO",
|
||||
dexCode: "metadao",
|
||||
programId: "",
|
||||
description: "DEX à vérifier. Aucun program id ne doit être inventé.",
|
||||
},
|
||||
{
|
||||
label: "Printr",
|
||||
dexCode: "printr",
|
||||
programId: "",
|
||||
description: "DEX à vérifier. Aucun program id ne doit être inventé.",
|
||||
},
|
||||
];
|
||||
|
||||
let lastResultJson = "";
|
||||
|
||||
function byId<T extends HTMLElement>(id: string): T {
|
||||
const element = document.getElementById(id);
|
||||
if (element === null) {
|
||||
throw new Error(`missing element #${id}`);
|
||||
}
|
||||
return element as T;
|
||||
}
|
||||
|
||||
function valueOrNull(value: string): string | null {
|
||||
const trimmed = value.trim();
|
||||
if (trimmed === "") {
|
||||
return null;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function numberValueOrNull(value: string): number | null {
|
||||
const trimmed = value.trim();
|
||||
if (trimmed === "") {
|
||||
return null;
|
||||
}
|
||||
const parsed = Number.parseInt(trimmed, 10);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
return null;
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function displayNullable(value: string | number | null): string {
|
||||
if (value === null) {
|
||||
return "-";
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function boolBadge(value: boolean): string {
|
||||
if (value) {
|
||||
return '<span class="badge text-bg-success">yes</span>';
|
||||
}
|
||||
return '<span class="badge text-bg-secondary">no</span>';
|
||||
}
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function shortText(value: string | null, maxLength: number): string {
|
||||
if (value === null) {
|
||||
return "-";
|
||||
}
|
||||
if (value.length <= maxLength) {
|
||||
return value;
|
||||
}
|
||||
return `${value.slice(0, maxLength)}…`;
|
||||
}
|
||||
|
||||
function appendLogLine(textarea: HTMLTextAreaElement, line: string): void {
|
||||
const now = new Date();
|
||||
const timestamp = now.toLocaleTimeString("fr-CH", { hour12: false });
|
||||
const lines = textarea.value === "" ? [] : textarea.value.split("\n");
|
||||
lines.push(`[${timestamp}] ${line}`);
|
||||
textarea.value = lines.slice(-400).join("\n");
|
||||
textarea.scrollTop = textarea.scrollHeight;
|
||||
}
|
||||
|
||||
function setStatus(statusBadge: HTMLElement, label: string, cssClass: string): void {
|
||||
statusBadge.className = `badge ${cssClass}`;
|
||||
statusBadge.textContent = label;
|
||||
}
|
||||
|
||||
function readRequest(): Demo3oldLocalDexCorpusSearchRequest {
|
||||
const limitInput = byId<HTMLInputElement>("demo3oldLimitInput");
|
||||
const parsedLimit = Number.parseInt(limitInput.value, 10);
|
||||
const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : 50;
|
||||
return {
|
||||
dexCode: valueOrNull(byId<HTMLInputElement>("demo3oldDexCodeInput").value),
|
||||
programId: valueOrNull(byId<HTMLInputElement>("demo3oldProgramIdInput").value),
|
||||
pairId: numberValueOrNull(byId<HTMLInputElement>("demo3oldPairIdInput").value),
|
||||
poolAddress: valueOrNull(byId<HTMLInputElement>("demo3oldPoolAddressInput").value),
|
||||
tokenMint: valueOrNull(byId<HTMLInputElement>("demo3oldTokenMintInput").value),
|
||||
signature: valueOrNull(byId<HTMLInputElement>("demo3oldSignatureInput").value),
|
||||
limit,
|
||||
};
|
||||
}
|
||||
|
||||
function populatePresetSelect(select: HTMLSelectElement): void {
|
||||
select.innerHTML = '<option value="">Custom / empty</option>';
|
||||
presets.forEach((preset, index) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = String(index);
|
||||
option.textContent = preset.label;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
function applyPreset(indexText: string): void {
|
||||
if (indexText === "") {
|
||||
return;
|
||||
}
|
||||
const index = Number.parseInt(indexText, 10);
|
||||
if (!Number.isFinite(index) || index < 0 || index >= presets.length) {
|
||||
return;
|
||||
}
|
||||
const preset = presets[index];
|
||||
byId<HTMLInputElement>("demo3oldDexCodeInput").value = preset.dexCode;
|
||||
byId<HTMLInputElement>("demo3oldProgramIdInput").value = preset.programId;
|
||||
byId<HTMLElement>("demo3oldPresetHelp").textContent = preset.description;
|
||||
}
|
||||
|
||||
function clearFilters(): void {
|
||||
byId<HTMLInputElement>("demo3oldDexCodeInput").value = "";
|
||||
byId<HTMLInputElement>("demo3oldProgramIdInput").value = "";
|
||||
byId<HTMLInputElement>("demo3oldPairIdInput").value = "";
|
||||
byId<HTMLInputElement>("demo3oldPoolAddressInput").value = "";
|
||||
byId<HTMLInputElement>("demo3oldTokenMintInput").value = "";
|
||||
byId<HTMLInputElement>("demo3oldSignatureInput").value = "";
|
||||
byId<HTMLSelectElement>("demo3oldPresetSelect").value = "";
|
||||
byId<HTMLElement>("demo3oldPresetHelp").textContent = "Choisis un preset ou saisis les filtres manuellement.";
|
||||
}
|
||||
|
||||
function renderSummary(summary: Demo3oldLocalDexCorpusSearchSummary): void {
|
||||
byId<HTMLElement>("demo3oldSummaryTransactionCount").textContent = String(summary.transactionCount);
|
||||
byId<HTMLElement>("demo3oldSummaryInstructionCount").textContent = String(summary.instructionCount);
|
||||
byId<HTMLElement>("demo3oldSummaryDecodedEventCount").textContent = String(summary.decodedEventCount);
|
||||
byId<HTMLElement>("demo3oldSummaryPoolCount").textContent = String(summary.poolCount);
|
||||
byId<HTMLElement>("demo3oldSummaryPairCount").textContent = String(summary.pairCount);
|
||||
byId<HTMLElement>("demo3oldSummaryTradeEventCount").textContent = String(summary.tradeEventCount);
|
||||
byId<HTMLElement>("demo3oldSummaryCandleCount").textContent = String(summary.pairCandleCount);
|
||||
byId<HTMLElement>("demo3oldSummaryProtocolCandidateCount").textContent = String(summary.protocolCandidateCount);
|
||||
}
|
||||
|
||||
function renderTransactionSamples(samples: Demo3oldLocalDexCorpusTransactionSample[]): void {
|
||||
const tbody = byId<HTMLTableSectionElement>("demo3oldTransactionTableBody");
|
||||
tbody.innerHTML = "";
|
||||
if (samples.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="8" class="text-body-secondary">No transaction sample.</td></tr>';
|
||||
return;
|
||||
}
|
||||
samples.forEach((sample) => {
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML = `
|
||||
<td class="font-monospace" title="${escapeHtml(sample.signature)}">${escapeHtml(shortText(sample.signature, 18))}</td>
|
||||
<td>${displayNullable(sample.slot)}</td>
|
||||
<td>${boolBadge(sample.failed)}</td>
|
||||
<td>${sample.instructionCount}</td>
|
||||
<td>${sample.decodedEventCount}</td>
|
||||
<td>${sample.tradeEventCount}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(sample.programIdsCsv)}">${escapeHtml(shortText(sample.programIdsCsv, 44))}</td>
|
||||
<td>${sample.transactionId}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function renderPoolPairSamples(samples: Demo3oldLocalDexCorpusPoolPairSample[]): void {
|
||||
const tbody = byId<HTMLTableSectionElement>("demo3oldPoolPairTableBody");
|
||||
tbody.innerHTML = "";
|
||||
if (samples.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="9" class="text-body-secondary">No pool/pair sample.</td></tr>';
|
||||
return;
|
||||
}
|
||||
samples.forEach((sample) => {
|
||||
const baseLabel = `${displayNullable(sample.baseSymbol)} / ${shortText(sample.baseMint, 12)}`;
|
||||
const quoteLabel = `${displayNullable(sample.quoteSymbol)} / ${shortText(sample.quoteMint, 12)}`;
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML = `
|
||||
<td>${displayNullable(sample.dexCode)}</td>
|
||||
<td title="${escapeHtml(displayNullable(sample.poolAddress))}" class="font-monospace">${escapeHtml(shortText(sample.poolAddress, 16))}</td>
|
||||
<td>${displayNullable(sample.pairId)}</td>
|
||||
<td>${escapeHtml(displayNullable(sample.pairSymbol))}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(displayNullable(sample.baseMint))}">${escapeHtml(baseLabel)}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(displayNullable(sample.quoteMint))}">${escapeHtml(quoteLabel)}</td>
|
||||
<td>${sample.decodedEventCount}</td>
|
||||
<td>${sample.tradeEventCount}</td>
|
||||
<td>${sample.pairCandleCount}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function renderDecodedEventSamples(samples: Demo3oldLocalDexCorpusDecodedEventSample[]): void {
|
||||
const tbody = byId<HTMLTableSectionElement>("demo3oldDecodedEventTableBody");
|
||||
tbody.innerHTML = "";
|
||||
if (samples.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="11" class="text-body-secondary">No decoded event sample.</td></tr>';
|
||||
return;
|
||||
}
|
||||
samples.forEach((sample) => {
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML = `
|
||||
<td>${sample.decodedEventId}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(sample.signature)}">${escapeHtml(shortText(sample.signature, 16))}</td>
|
||||
<td>${displayNullable(sample.slot)}</td>
|
||||
<td>${escapeHtml(sample.protocolName)}</td>
|
||||
<td>${escapeHtml(sample.eventKind)}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(sample.programId)}">${escapeHtml(shortText(sample.programId, 16))}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(displayNullable(sample.poolAccount))}">${escapeHtml(shortText(sample.poolAccount, 16))}</td>
|
||||
<td>${escapeHtml(displayNullable(sample.eventCategory))}</td>
|
||||
<td>${escapeHtml(displayNullable(sample.eventActionability))}</td>
|
||||
<td>${boolBadge(sample.tradeCandidate)}</td>
|
||||
<td>${boolBadge(sample.candleCandidate)}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function renderResult(payload: Demo3oldLocalDexCorpusSearchPayload): void {
|
||||
lastResultJson = payload.resultJson;
|
||||
byId<HTMLElement>("demo3oldDatabaseUrlText").textContent = payload.databaseUrl;
|
||||
byId<HTMLTextAreaElement>("demo3oldJsonTextarea").value = payload.resultJson;
|
||||
renderSummary(payload.result.summary);
|
||||
renderTransactionSamples(payload.result.transactionSamples);
|
||||
renderPoolPairSamples(payload.result.poolPairSamples);
|
||||
renderDecodedEventSamples(payload.result.decodedEventSamples);
|
||||
}
|
||||
|
||||
async function runSearch(): Promise<void> {
|
||||
const statusBadge = byId<HTMLElement>("demo3oldStatusBadge");
|
||||
const logTextarea = byId<HTMLTextAreaElement>("demo3oldLogTextarea");
|
||||
setStatus(statusBadge, "Searching", "text-bg-warning");
|
||||
appendLogLine(logTextarea, "launching local DEX corpus search");
|
||||
try {
|
||||
const request = readRequest();
|
||||
const payload = await invoke<Demo3oldLocalDexCorpusSearchPayload>("demo3old_search_local_dex_corpus", { request });
|
||||
renderResult(payload);
|
||||
appendLogLine(
|
||||
logTextarea,
|
||||
`search completed: tx='${payload.result.summary.transactionCount}', pools='${payload.result.summary.poolCount}', pairs='${payload.result.summary.pairCount}', decoded='${payload.result.summary.decodedEventCount}'`,
|
||||
);
|
||||
setStatus(statusBadge, "Ready", "text-bg-success");
|
||||
} catch (error) {
|
||||
appendLogLine(logTextarea, `search failed: ${String(error)}`);
|
||||
setStatus(statusBadge, "Error", "text-bg-danger");
|
||||
}
|
||||
}
|
||||
|
||||
async function copyJson(): Promise<void> {
|
||||
const logTextarea = byId<HTMLTextAreaElement>("demo3oldLogTextarea");
|
||||
if (lastResultJson === "") {
|
||||
appendLogLine(logTextarea, "no JSON result to copy");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(lastResultJson);
|
||||
appendLogLine(logTextarea, "JSON result copied");
|
||||
} catch (error) {
|
||||
appendLogLine(logTextarea, `copy failed: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
void takeoverConsole();
|
||||
debug("demo3old window loaded");
|
||||
|
||||
const sidebarToggle = document.querySelector<HTMLButtonElement>('#sidebarToggle');
|
||||
if (sidebarToggle) {
|
||||
// restaurer l’état depuis localStorage
|
||||
if (localStorage.getItem('sidebar-toggle') === 'true') {
|
||||
document.body.classList.add('sidenav-toggled');
|
||||
}
|
||||
|
||||
sidebarToggle.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
document.body.classList.toggle('sidenav-toggled');
|
||||
localStorage.setItem('sidebar-toggle', document.body.classList.contains('sidenav-toggled') ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||
const toastElList = document.querySelectorAll('.toast');
|
||||
Array.from(toastElList).map(toastEl => new bootstrap.Toast(toastEl));
|
||||
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]');
|
||||
Array.from(popoverTriggerList).map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl));
|
||||
|
||||
const gobackto = location.pathname + location.search;
|
||||
|
||||
document.querySelectorAll<HTMLAnchorElement>('a[data-setlang]').forEach((a) => {
|
||||
const href = a.getAttribute("href");
|
||||
if (!href) return; // pas de href => on ignore
|
||||
|
||||
const url = new URL(href, location.origin);
|
||||
url.searchParams.set("gobackto", gobackto);
|
||||
|
||||
// conserve une URL relative (path + query)
|
||||
a.setAttribute("href", url.pathname + "?" + url.searchParams.toString());
|
||||
});
|
||||
|
||||
const presetSelect = byId<HTMLSelectElement>("demo3oldPresetSelect");
|
||||
populatePresetSelect(presetSelect);
|
||||
presetSelect.addEventListener("change", () => {
|
||||
applyPreset(presetSelect.value);
|
||||
});
|
||||
|
||||
byId<HTMLButtonElement>("demo3oldSearchButton").addEventListener("click", () => {
|
||||
void runSearch();
|
||||
});
|
||||
byId<HTMLButtonElement>("demo3oldClearFiltersButton").addEventListener("click", () => {
|
||||
clearFilters();
|
||||
});
|
||||
byId<HTMLButtonElement>("demo3oldCopyJsonButton").addEventListener("click", () => {
|
||||
void copyJson();
|
||||
});
|
||||
byId<HTMLButtonElement>("demo3oldClearLogButton").addEventListener("click", () => {
|
||||
byId<HTMLTextAreaElement>("demo3oldLogTextarea").value = "";
|
||||
});
|
||||
|
||||
applyPreset("3");
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing";
|
||||
import type { DemoPipeline2CatalogPayload } from "./bindings/DemoPipeline2CatalogPayload.ts";
|
||||
import type { DemoPipeline2BackfillTokenRequest } from "./bindings/DemoPipeline2BackfillTokenRequest.ts";
|
||||
import type { DemoPipeline2BackfillPoolRequest } from "./bindings/DemoPipeline2BackfillPoolRequest.ts";
|
||||
import type { DemoPipeline2BackfillSignatureRequest } from "./bindings/DemoPipeline2BackfillSignatureRequest.ts";
|
||||
import type { DemoPipeline2BackfillPayload } from "./bindings/DemoPipeline2BackfillPayload.ts";
|
||||
import type { DemoPipeline2PairCandlesRequest } from "./bindings/DemoPipeline2PairCandlesRequest.ts";
|
||||
import type { DemoPipeline2PairCandlesPayload } from "./bindings/DemoPipeline2PairCandlesPayload.ts";
|
||||
@@ -358,6 +359,9 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
const poolSignatureLimitInput = document.querySelector<HTMLInputElement>("#demoPipeline2PoolSignatureLimitInput");
|
||||
const backfillPoolButton = document.querySelector<HTMLButtonElement>("#demoPipeline2BackfillPoolButton");
|
||||
|
||||
const signatureInput = document.querySelector<HTMLInputElement>("#demoPipeline2SignatureInput");
|
||||
const backfillSignatureButton = document.querySelector<HTMLButtonElement>("#demoPipeline2BackfillSignatureButton");
|
||||
|
||||
const replayLimitInput = document.querySelector<HTMLInputElement>("#demoPipeline2ReplayLimitInput");
|
||||
const replayMetadataCheckbox = document.querySelector<HTMLInputElement>("#demoPipeline2ReplayMetadataCheckbox");
|
||||
const replayMetadataLimitInput = document.querySelector<HTMLInputElement>("#demoPipeline2ReplayMetadataLimitInput");
|
||||
@@ -405,6 +409,8 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
!poolInput ||
|
||||
!poolSignatureLimitInput ||
|
||||
!backfillPoolButton ||
|
||||
!signatureInput ||
|
||||
!backfillSignatureButton ||
|
||||
!replayLimitInput ||
|
||||
!replayMetadataCheckbox ||
|
||||
!replayMetadataLimitInput ||
|
||||
@@ -585,6 +591,43 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
backfillSignatureButton.addEventListener("click", async () => {
|
||||
const signature = signatureInput.value.trim();
|
||||
if (signature === "") {
|
||||
appendLogLine(logTextarea, "[ui] signature is required");
|
||||
return;
|
||||
}
|
||||
|
||||
const httpRoleText = httpRoleInput.value.trim();
|
||||
const httpRole = httpRoleText === "" ? null : httpRoleText;
|
||||
|
||||
appendLogLine(
|
||||
logTextarea,
|
||||
`[ui] launching signature backfill for '${signature}' with role '${httpRole ?? "history_backfill"}'`,
|
||||
);
|
||||
|
||||
const request: DemoPipeline2BackfillSignatureRequest = {
|
||||
signature,
|
||||
httpRole,
|
||||
};
|
||||
|
||||
try {
|
||||
const payload = await invoke<DemoPipeline2BackfillPayload>(
|
||||
"demo_pipeline2_backfill_signature",
|
||||
{ request },
|
||||
);
|
||||
|
||||
backfillSummaryTextarea.value = payload.summaryJson;
|
||||
currentCatalog = payload.catalog;
|
||||
renderCatalogTextareas(payload.catalog, tokensTextarea, poolsTextarea, pairsTextarea);
|
||||
refreshPairSelect(payload.catalog, pairSelect);
|
||||
|
||||
appendLogLine(logTextarea, `[ui] signature backfill completed for '${payload.objectKey}'`);
|
||||
} catch (error) {
|
||||
appendLogLine(logTextarea, `[ui] signature backfill error: ${String(error)}`);
|
||||
}
|
||||
});
|
||||
|
||||
replayLocalPipelineButton.addEventListener("click", async () => {
|
||||
const replayLimit = readOptionalPositiveIntegerInput(
|
||||
replayLimitInput,
|
||||
|
||||
@@ -32,6 +32,24 @@ async function openDemoWsManagerWindow(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function openDemo3Window(): Promise<void> {
|
||||
try {
|
||||
await invoke("open_demo3_window");
|
||||
} catch (error) {
|
||||
console.error("open_demo3_window failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function openDemo3oldWindow(): Promise<void> {
|
||||
try {
|
||||
await invoke("open_demo3old_window");
|
||||
} catch (error) {
|
||||
console.error("open_demo3old_window failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function openDemoPipelineWindow(): Promise<void> {
|
||||
try {
|
||||
await invoke("open_demo_pipeline_window");
|
||||
@@ -93,6 +111,10 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
const openDemoHttpButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoHttpButtonSecondary");
|
||||
const openDemoWsManagerButton = document.querySelector<HTMLButtonElement>("#openDemoWsManagerButton");
|
||||
const openDemoWsManagerButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoWsManagerButtonSecondary");
|
||||
const openDemo3Button = document.querySelector<HTMLButtonElement>("#openDemo3Button");
|
||||
const openDemo3ButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemo3ButtonSecondary");
|
||||
const openDemo3oldButton = document.querySelector<HTMLButtonElement>("#openDemo3oldButton");
|
||||
const openDemo3oldButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemo3oldButtonSecondary");
|
||||
const openDemoPipelineButton = document.querySelector<HTMLButtonElement>("#openDemoPipelineButton");
|
||||
const openDemoPipelineButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoPipelineButtonSecondary");
|
||||
const openDemoPipeline2Button = document.querySelector<HTMLButtonElement>("#openDemoPipeline2Button");
|
||||
@@ -134,6 +156,30 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (openDemo3Button) {
|
||||
openDemo3Button.addEventListener("click", () => {
|
||||
void openDemo3Window();
|
||||
});
|
||||
}
|
||||
|
||||
if (openDemo3ButtonSecondary) {
|
||||
openDemo3ButtonSecondary.addEventListener("click", () => {
|
||||
void openDemo3Window();
|
||||
});
|
||||
}
|
||||
|
||||
if (openDemo3oldButton) {
|
||||
openDemo3oldButton.addEventListener("click", () => {
|
||||
void openDemo3oldWindow();
|
||||
});
|
||||
}
|
||||
|
||||
if (openDemo3oldButtonSecondary) {
|
||||
openDemo3oldButtonSecondary.addEventListener("click", () => {
|
||||
void openDemo3oldWindow();
|
||||
});
|
||||
}
|
||||
|
||||
if (openDemoPipelineButton) {
|
||||
openDemoPipelineButton.addEventListener("click", () => {
|
||||
void openDemoPipelineWindow();
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main","splash","demo_ws","demo_http","demo_ws_manager","demo_pipeline","demo_pipeline2"],"permissions":["core:default","tracing:default"]}}
|
||||
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main","splash","demo_ws","demo_http","demo_ws_manager","demo3old","demo3","demo_pipeline","demo_pipeline2"],"permissions":["core:default","tracing:default"]}}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "kb-demo-app",
|
||||
"private": true,
|
||||
"version": "0.7.39",
|
||||
"version": "0.7.40",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
735
kb_demo_app/src/demo3.rs
Normal file
735
kb_demo_app/src/demo3.rs
Normal file
@@ -0,0 +1,735 @@
|
||||
// file: kb_demo_app/src/demo3.rs
|
||||
|
||||
//! Tauri commands for Demo3 local DEX corpus search.
|
||||
//!
|
||||
//! Demo3 is intentionally a thin UI-facing wrapper around `kb_lib` local
|
||||
//! corpus search services. It does not contain DEX decoding or protocol logic.
|
||||
|
||||
use tauri::Manager;
|
||||
use ts_rs::TS;
|
||||
|
||||
/// Request payload for a local DEX corpus search.
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusSearchRequest.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3LocalDexCorpusSearchRequest {
|
||||
/// Optional DEX code or decoded protocol name.
|
||||
pub dex_code: std::option::Option<std::string::String>,
|
||||
/// Optional Solana program id.
|
||||
pub program_id: std::option::Option<std::string::String>,
|
||||
/// Optional local pair id.
|
||||
#[ts(type = "number | null")]
|
||||
pub pair_id: std::option::Option<i64>,
|
||||
/// Optional pool account/address.
|
||||
pub pool_address: std::option::Option<std::string::String>,
|
||||
/// Optional token mint to match as base, quote or decoded mint.
|
||||
pub token_mint: std::option::Option<std::string::String>,
|
||||
/// Optional transaction signature.
|
||||
pub signature: std::option::Option<std::string::String>,
|
||||
/// Maximum number of rows to return per sample category.
|
||||
pub limit: u32,
|
||||
}
|
||||
|
||||
/// Response payload returned by Demo3 local DEX corpus search.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusSearchPayload.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3LocalDexCorpusSearchPayload {
|
||||
/// Open database URL.
|
||||
pub database_url: std::string::String,
|
||||
/// Pretty JSON representation of the search result.
|
||||
pub result_json: std::string::String,
|
||||
/// Structured local DEX corpus search result.
|
||||
pub result: Demo3LocalDexCorpusSearchResult,
|
||||
}
|
||||
|
||||
/// Structured local DEX corpus search result.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusSearchResult.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3LocalDexCorpusSearchResult {
|
||||
/// Normalized search request applied by the backend service.
|
||||
pub request: Demo3LocalDexCorpusSearchRequest,
|
||||
/// Aggregate counts for the matching local data.
|
||||
pub summary: Demo3LocalDexCorpusSearchSummary,
|
||||
/// Matching transaction samples.
|
||||
pub transaction_samples: std::vec::Vec<Demo3LocalDexCorpusTransactionSample>,
|
||||
/// Matching pool/pair samples.
|
||||
pub pool_pair_samples: std::vec::Vec<Demo3LocalDexCorpusPoolPairSample>,
|
||||
/// Matching decoded event samples.
|
||||
pub decoded_event_samples: std::vec::Vec<Demo3LocalDexCorpusDecodedEventSample>,
|
||||
}
|
||||
|
||||
/// Aggregate counts for one local DEX corpus search.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusSearchSummary.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3LocalDexCorpusSearchSummary {
|
||||
/// Number of distinct matching transactions.
|
||||
#[ts(type = "number")]
|
||||
pub transaction_count: i64,
|
||||
/// Number of distinct matching instructions.
|
||||
#[ts(type = "number")]
|
||||
pub instruction_count: i64,
|
||||
/// Number of distinct matching decoded DEX events.
|
||||
#[ts(type = "number")]
|
||||
pub decoded_event_count: i64,
|
||||
/// Number of distinct matching pools.
|
||||
#[ts(type = "number")]
|
||||
pub pool_count: i64,
|
||||
/// Number of distinct matching pairs.
|
||||
#[ts(type = "number")]
|
||||
pub pair_count: i64,
|
||||
/// Number of distinct matching trade events.
|
||||
#[ts(type = "number")]
|
||||
pub trade_event_count: i64,
|
||||
/// Number of distinct matching candle rows.
|
||||
#[ts(type = "number")]
|
||||
pub pair_candle_count: i64,
|
||||
/// Number of distinct matching protocol candidate rows.
|
||||
#[ts(type = "number")]
|
||||
pub protocol_candidate_count: i64,
|
||||
}
|
||||
|
||||
/// Matching transaction sample for corpus discovery.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusTransactionSample.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3LocalDexCorpusTransactionSample {
|
||||
/// Transaction id.
|
||||
#[ts(type = "number")]
|
||||
pub transaction_id: i64,
|
||||
/// Transaction signature.
|
||||
pub signature: std::string::String,
|
||||
/// Optional Solana slot.
|
||||
#[ts(type = "number | null")]
|
||||
pub slot: std::option::Option<i64>,
|
||||
/// Whether the transaction has a non-null error payload.
|
||||
pub failed: bool,
|
||||
/// Number of persisted instructions for the transaction.
|
||||
#[ts(type = "number")]
|
||||
pub instruction_count: i64,
|
||||
/// Number of persisted decoded DEX events for the transaction.
|
||||
#[ts(type = "number")]
|
||||
pub decoded_event_count: i64,
|
||||
/// Number of persisted trade events for the transaction.
|
||||
#[ts(type = "number")]
|
||||
pub trade_event_count: i64,
|
||||
/// Comma-separated distinct program ids seen in the transaction.
|
||||
pub program_ids_csv: std::string::String,
|
||||
}
|
||||
|
||||
/// Matching pool/pair sample for corpus discovery.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusPoolPairSample.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3LocalDexCorpusPoolPairSample {
|
||||
/// Optional pool id.
|
||||
#[ts(type = "number | null")]
|
||||
pub pool_id: std::option::Option<i64>,
|
||||
/// Optional pool address.
|
||||
pub pool_address: std::option::Option<std::string::String>,
|
||||
/// Optional pair id.
|
||||
#[ts(type = "number | null")]
|
||||
pub pair_id: std::option::Option<i64>,
|
||||
/// Optional DEX code.
|
||||
pub dex_code: std::option::Option<std::string::String>,
|
||||
/// Optional pair symbol.
|
||||
pub pair_symbol: std::option::Option<std::string::String>,
|
||||
/// Optional base token mint.
|
||||
pub base_mint: std::option::Option<std::string::String>,
|
||||
/// Optional base token symbol.
|
||||
pub base_symbol: std::option::Option<std::string::String>,
|
||||
/// Optional quote token mint.
|
||||
pub quote_mint: std::option::Option<std::string::String>,
|
||||
/// Optional quote token symbol.
|
||||
pub quote_symbol: std::option::Option<std::string::String>,
|
||||
/// Number of decoded events attached to the pool.
|
||||
#[ts(type = "number")]
|
||||
pub decoded_event_count: i64,
|
||||
/// Number of trade events attached to the pair.
|
||||
#[ts(type = "number")]
|
||||
pub trade_event_count: i64,
|
||||
/// Number of candle rows attached to the pair.
|
||||
#[ts(type = "number")]
|
||||
pub pair_candle_count: i64,
|
||||
/// Latest known founding or activity signature for the pool/pair.
|
||||
pub latest_signature: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
/// Matching decoded event sample for corpus discovery.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusDecodedEventSample.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3LocalDexCorpusDecodedEventSample {
|
||||
/// Decoded event id.
|
||||
#[ts(type = "number")]
|
||||
pub decoded_event_id: i64,
|
||||
/// Transaction id.
|
||||
#[ts(type = "number")]
|
||||
pub transaction_id: i64,
|
||||
/// Transaction signature.
|
||||
pub signature: std::string::String,
|
||||
/// Optional Solana slot.
|
||||
#[ts(type = "number | null")]
|
||||
pub slot: std::option::Option<i64>,
|
||||
/// Protocol name stored on the decoded event.
|
||||
pub protocol_name: std::string::String,
|
||||
/// Program id stored on the decoded event.
|
||||
pub program_id: std::string::String,
|
||||
/// Event kind.
|
||||
pub event_kind: std::string::String,
|
||||
/// Optional pool account.
|
||||
pub pool_account: std::option::Option<std::string::String>,
|
||||
/// Optional token A mint.
|
||||
pub token_a_mint: std::option::Option<std::string::String>,
|
||||
/// Optional token B mint.
|
||||
pub token_b_mint: std::option::Option<std::string::String>,
|
||||
/// Decoded event category extracted from payload JSON.
|
||||
pub event_category: std::option::Option<std::string::String>,
|
||||
/// Decoded event lifecycle kind extracted from payload JSON.
|
||||
pub event_lifecycle_kind: std::option::Option<std::string::String>,
|
||||
/// Decoded event actionability extracted from payload JSON.
|
||||
pub event_actionability: std::option::Option<std::string::String>,
|
||||
/// Whether the decoded event is a trade candidate.
|
||||
pub trade_candidate: bool,
|
||||
/// Whether the decoded event is a candle candidate.
|
||||
pub candle_candidate: bool,
|
||||
}
|
||||
|
||||
/// Opens the `Demo3` local DEX corpus search window.
|
||||
#[tauri::command]
|
||||
pub(crate) fn open_demo3_window(app_handle: tauri::AppHandle) -> Result<(), std::string::String> {
|
||||
let existing_window_option = app_handle.get_webview_window("demo3");
|
||||
|
||||
let demo_window = match existing_window_option {
|
||||
Some(demo_window) => demo_window,
|
||||
None => {
|
||||
let builder = tauri::WebviewWindowBuilder::new(
|
||||
&app_handle,
|
||||
"demo3",
|
||||
tauri::WebviewUrl::App("demo3.html".into()),
|
||||
)
|
||||
.title("Demo3 Local DEX Corpus")
|
||||
.inner_size(1480.0, 920.0)
|
||||
.min_inner_size(1100.0, 720.0)
|
||||
.center()
|
||||
.visible(true)
|
||||
.transparent(false)
|
||||
.decorations(true);
|
||||
let build_result = builder.build();
|
||||
match build_result {
|
||||
Ok(window) => window,
|
||||
Err(error) => {
|
||||
return Err(format!("cannot create demo3 window: {error:?}"));
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
let show_result = demo_window.show();
|
||||
if let Err(error) = show_result {
|
||||
return Err(format!("cannot show demo3 window: {error:?}"));
|
||||
}
|
||||
let focus_result = demo_window.set_focus();
|
||||
if let Err(error) = focus_result {
|
||||
return Err(format!("cannot focus demo3 window: {error:?}"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Searches local DEX corpus candidates from the persisted SQLite pipeline.
|
||||
#[tauri::command]
|
||||
pub(crate) async fn demo3_search_local_dex_corpus(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: Demo3LocalDexCorpusSearchRequest,
|
||||
) -> Result<Demo3LocalDexCorpusSearchPayload, std::string::String> {
|
||||
let database = state.database.clone();
|
||||
let service = kb_lib::LocalDexCorpusSearchService::new(database.clone());
|
||||
let search_result = service.search(to_lib_search_request(&request)).await;
|
||||
let lib_result = match search_result {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
return Err(format!("local DEX corpus search failed: {}", error));
|
||||
},
|
||||
};
|
||||
let ui_result = from_lib_search_result(lib_result);
|
||||
let result_json_result = serde_json::to_string_pretty(&ui_result);
|
||||
let result_json = match result_json_result {
|
||||
Ok(result_json) => result_json,
|
||||
Err(error) => {
|
||||
return Err(format!("cannot serialize local DEX corpus search result: {}", error));
|
||||
},
|
||||
};
|
||||
Ok(Demo3LocalDexCorpusSearchPayload {
|
||||
database_url: database.database_url().to_string(),
|
||||
result_json,
|
||||
result: ui_result,
|
||||
})
|
||||
}
|
||||
|
||||
fn to_lib_search_request(
|
||||
request: &Demo3LocalDexCorpusSearchRequest,
|
||||
) -> kb_lib::LocalDexCorpusSearchRequestDto {
|
||||
return kb_lib::LocalDexCorpusSearchRequestDto {
|
||||
dex_code: normalize_optional_text(request.dex_code.clone()),
|
||||
program_id: normalize_optional_text(request.program_id.clone()),
|
||||
pair_id: request.pair_id,
|
||||
pool_address: normalize_optional_text(request.pool_address.clone()),
|
||||
token_mint: normalize_optional_text(request.token_mint.clone()),
|
||||
signature: normalize_optional_text(request.signature.clone()),
|
||||
limit: request.limit,
|
||||
};
|
||||
}
|
||||
|
||||
fn normalize_optional_text(
|
||||
value: std::option::Option<std::string::String>,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
let value = match value {
|
||||
Some(value) => value.trim().to_string(),
|
||||
None => return None,
|
||||
};
|
||||
if value.is_empty() {
|
||||
return None;
|
||||
}
|
||||
return Some(value);
|
||||
}
|
||||
|
||||
fn from_lib_search_result(
|
||||
result: kb_lib::LocalDexCorpusSearchResultDto,
|
||||
) -> Demo3LocalDexCorpusSearchResult {
|
||||
let mut transaction_samples = std::vec::Vec::new();
|
||||
for sample in result.transaction_samples {
|
||||
transaction_samples.push(from_lib_transaction_sample(sample));
|
||||
}
|
||||
let mut pool_pair_samples = std::vec::Vec::new();
|
||||
for sample in result.pool_pair_samples {
|
||||
pool_pair_samples.push(from_lib_pool_pair_sample(sample));
|
||||
}
|
||||
let mut decoded_event_samples = std::vec::Vec::new();
|
||||
for sample in result.decoded_event_samples {
|
||||
decoded_event_samples.push(from_lib_decoded_event_sample(sample));
|
||||
}
|
||||
return Demo3LocalDexCorpusSearchResult {
|
||||
request: from_lib_request(result.request),
|
||||
summary: from_lib_summary(result.summary),
|
||||
transaction_samples,
|
||||
pool_pair_samples,
|
||||
decoded_event_samples,
|
||||
};
|
||||
}
|
||||
|
||||
fn from_lib_request(
|
||||
request: kb_lib::LocalDexCorpusSearchRequestDto,
|
||||
) -> Demo3LocalDexCorpusSearchRequest {
|
||||
return Demo3LocalDexCorpusSearchRequest {
|
||||
dex_code: request.dex_code,
|
||||
program_id: request.program_id,
|
||||
pair_id: request.pair_id,
|
||||
pool_address: request.pool_address,
|
||||
token_mint: request.token_mint,
|
||||
signature: request.signature,
|
||||
limit: request.limit,
|
||||
};
|
||||
}
|
||||
|
||||
fn from_lib_summary(
|
||||
summary: kb_lib::LocalDexCorpusSearchSummaryDto,
|
||||
) -> Demo3LocalDexCorpusSearchSummary {
|
||||
return Demo3LocalDexCorpusSearchSummary {
|
||||
transaction_count: summary.transaction_count,
|
||||
instruction_count: summary.instruction_count,
|
||||
decoded_event_count: summary.decoded_event_count,
|
||||
pool_count: summary.pool_count,
|
||||
pair_count: summary.pair_count,
|
||||
trade_event_count: summary.trade_event_count,
|
||||
pair_candle_count: summary.pair_candle_count,
|
||||
protocol_candidate_count: summary.protocol_candidate_count,
|
||||
};
|
||||
}
|
||||
|
||||
fn from_lib_transaction_sample(
|
||||
sample: kb_lib::LocalDexCorpusTransactionSampleDto,
|
||||
) -> Demo3LocalDexCorpusTransactionSample {
|
||||
return Demo3LocalDexCorpusTransactionSample {
|
||||
transaction_id: sample.transaction_id,
|
||||
signature: sample.signature,
|
||||
slot: sample.slot,
|
||||
failed: sample.failed,
|
||||
instruction_count: sample.instruction_count,
|
||||
decoded_event_count: sample.decoded_event_count,
|
||||
trade_event_count: sample.trade_event_count,
|
||||
program_ids_csv: sample.program_ids_csv,
|
||||
};
|
||||
}
|
||||
|
||||
fn from_lib_pool_pair_sample(
|
||||
sample: kb_lib::LocalDexCorpusPoolPairSampleDto,
|
||||
) -> Demo3LocalDexCorpusPoolPairSample {
|
||||
return Demo3LocalDexCorpusPoolPairSample {
|
||||
pool_id: sample.pool_id,
|
||||
pool_address: sample.pool_address,
|
||||
pair_id: sample.pair_id,
|
||||
dex_code: sample.dex_code,
|
||||
pair_symbol: sample.pair_symbol,
|
||||
base_mint: sample.base_mint,
|
||||
base_symbol: sample.base_symbol,
|
||||
quote_mint: sample.quote_mint,
|
||||
quote_symbol: sample.quote_symbol,
|
||||
decoded_event_count: sample.decoded_event_count,
|
||||
trade_event_count: sample.trade_event_count,
|
||||
pair_candle_count: sample.pair_candle_count,
|
||||
latest_signature: sample.latest_signature,
|
||||
};
|
||||
}
|
||||
|
||||
fn from_lib_decoded_event_sample(
|
||||
sample: kb_lib::LocalDexCorpusDecodedEventSampleDto,
|
||||
) -> Demo3LocalDexCorpusDecodedEventSample {
|
||||
return Demo3LocalDexCorpusDecodedEventSample {
|
||||
decoded_event_id: sample.decoded_event_id,
|
||||
transaction_id: sample.transaction_id,
|
||||
signature: sample.signature,
|
||||
slot: sample.slot,
|
||||
protocol_name: sample.protocol_name,
|
||||
program_id: sample.program_id,
|
||||
event_kind: sample.event_kind,
|
||||
pool_account: sample.pool_account,
|
||||
token_a_mint: sample.token_a_mint,
|
||||
token_b_mint: sample.token_b_mint,
|
||||
event_category: sample.event_category,
|
||||
event_lifecycle_kind: sample.event_lifecycle_kind,
|
||||
event_actionability: sample.event_actionability,
|
||||
trade_candidate: sample.trade_candidate,
|
||||
candle_candidate: sample.candle_candidate,
|
||||
};
|
||||
}
|
||||
|
||||
/// Request payload for on-chain DEX pair/pool discovery.
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, TS)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexDiscoveryRequest.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3OnchainDexDiscoveryRequest {
|
||||
/// Optional DEX code from the support matrix.
|
||||
pub dex_code: std::option::Option<std::string::String>,
|
||||
/// Optional Solana program id. When absent, dex_code must resolve to a verified program id.
|
||||
pub program_id: std::option::Option<std::string::String>,
|
||||
/// HTTP role used to query Solana RPC.
|
||||
pub http_role: std::string::String,
|
||||
/// Maximum number of signatures to inspect.
|
||||
pub signature_limit: u32,
|
||||
/// Maximum number of transactions to fetch from the signature list.
|
||||
pub transaction_limit: u32,
|
||||
/// Maximum number of candidate rows to return.
|
||||
pub candidate_limit: u32,
|
||||
}
|
||||
|
||||
/// Response payload returned by Demo3 on-chain DEX discovery.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexDiscoveryPayload.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3OnchainDexDiscoveryPayload {
|
||||
/// HTTP role used by the request.
|
||||
pub http_role: std::string::String,
|
||||
/// Pretty JSON representation of the discovery result.
|
||||
pub result_json: std::string::String,
|
||||
/// Structured discovery result.
|
||||
pub result: Demo3OnchainDexDiscoveryResult,
|
||||
}
|
||||
|
||||
/// Structured on-chain DEX discovery result.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexDiscoveryResult.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3OnchainDexDiscoveryResult {
|
||||
/// Normalized request used by kb_lib.
|
||||
pub request: Demo3OnchainDexDiscoveryRequest,
|
||||
/// DEX code resolved from the support matrix when available.
|
||||
pub resolved_dex_code: std::option::Option<std::string::String>,
|
||||
/// Program id scanned with getSignaturesForAddress.
|
||||
pub resolved_program_id: std::string::String,
|
||||
/// Number of signatures returned by Solana RPC.
|
||||
#[ts(type = "number")]
|
||||
pub fetched_signature_count: usize,
|
||||
/// Number of fetched transactions.
|
||||
#[ts(type = "number")]
|
||||
pub fetched_transaction_count: usize,
|
||||
/// Number of getTransaction calls returning null.
|
||||
#[ts(type = "number")]
|
||||
pub missing_transaction_count: usize,
|
||||
/// Number of failed transactions encountered.
|
||||
#[ts(type = "number")]
|
||||
pub failed_transaction_count: usize,
|
||||
/// Number of candidate rows returned.
|
||||
#[ts(type = "number")]
|
||||
pub candidate_count: usize,
|
||||
/// Candidate on-chain rows.
|
||||
pub candidates: std::vec::Vec<Demo3OnchainDexPairCandidate>,
|
||||
}
|
||||
|
||||
/// Candidate on-chain transaction/instruction for a DEX program id.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexPairCandidate.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3OnchainDexPairCandidate {
|
||||
/// Transaction signature.
|
||||
pub signature: std::string::String,
|
||||
/// Slot when available.
|
||||
#[ts(type = "number | null")]
|
||||
pub slot: std::option::Option<u64>,
|
||||
/// Block time when available.
|
||||
#[ts(type = "number | null")]
|
||||
pub block_time: std::option::Option<i64>,
|
||||
/// Whether the transaction failed.
|
||||
pub failed: bool,
|
||||
/// Program id matched by the candidate.
|
||||
pub program_id: std::string::String,
|
||||
/// DEX code when known.
|
||||
pub dex_code: std::option::Option<std::string::String>,
|
||||
/// Candidate kind inferred from data/logs.
|
||||
pub candidate_kind: std::string::String,
|
||||
/// Extraction confidence.
|
||||
pub confidence: std::string::String,
|
||||
/// Top-level instruction index.
|
||||
#[ts(type = "number | null")]
|
||||
pub instruction_index: std::option::Option<i64>,
|
||||
/// Inner instruction index.
|
||||
#[ts(type = "number | null")]
|
||||
pub inner_instruction_index: std::option::Option<i64>,
|
||||
/// Instruction name inferred from data/logs.
|
||||
pub instruction_name: std::option::Option<std::string::String>,
|
||||
/// Candidate pool address.
|
||||
pub pool_address: std::option::Option<std::string::String>,
|
||||
/// Candidate token A mint.
|
||||
pub token_a_mint: std::option::Option<std::string::String>,
|
||||
/// Candidate token B mint.
|
||||
pub token_b_mint: std::option::Option<std::string::String>,
|
||||
/// Verified pool address when a DEX decoder or stable layout proves it.
|
||||
pub verified_pool_address: std::option::Option<std::string::String>,
|
||||
/// Token mints observed generically from transaction token balances.
|
||||
pub observed_token_mints: std::vec::Vec<std::string::String>,
|
||||
/// Token balance deltas observed through transaction metadata.
|
||||
pub token_balance_deltas: std::vec::Vec<Demo3OnchainDexTokenBalanceDelta>,
|
||||
/// Program-owned or writable accounts that may be pool/config/state accounts.
|
||||
pub candidate_pool_accounts: std::vec::Vec<Demo3OnchainDexCandidateAccount>,
|
||||
/// Token accounts that may be pool vaults.
|
||||
pub candidate_token_vault_accounts: std::vec::Vec<Demo3OnchainDexCandidateAccount>,
|
||||
/// Other candidate accounts attached to the matched instruction.
|
||||
pub candidate_program_accounts: std::vec::Vec<Demo3OnchainDexCandidateAccount>,
|
||||
/// Short account sample.
|
||||
pub account_samples: std::vec::Vec<std::string::String>,
|
||||
/// Short log sample.
|
||||
pub log_samples: std::vec::Vec<std::string::String>,
|
||||
/// Suggested next action.
|
||||
pub backfill_hint: std::string::String,
|
||||
}
|
||||
|
||||
/// Token-balance delta observed in one on-chain candidate transaction.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3OnchainDexTokenBalanceDelta.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3OnchainDexTokenBalanceDelta {
|
||||
/// Token account index in the transaction message when available.
|
||||
#[ts(type = "number | null")]
|
||||
pub account_index: std::option::Option<i64>,
|
||||
/// Token account address resolved from the transaction account keys.
|
||||
pub account_address: std::option::Option<std::string::String>,
|
||||
/// SPL Token or Token-2022 mint address.
|
||||
pub mint: std::string::String,
|
||||
/// Token account owner when Solana RPC exposes it.
|
||||
pub owner: std::option::Option<std::string::String>,
|
||||
/// Token program id when Solana RPC exposes it.
|
||||
pub token_program: std::option::Option<std::string::String>,
|
||||
/// Raw token amount before the transaction.
|
||||
pub pre_amount_raw: std::option::Option<std::string::String>,
|
||||
/// Raw token amount after the transaction.
|
||||
pub post_amount_raw: std::option::Option<std::string::String>,
|
||||
/// Signed raw delta when calculable.
|
||||
pub delta_raw: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
/// Candidate account inferred from generic on-chain transaction evidence.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexCandidateAccount.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3OnchainDexCandidateAccount {
|
||||
/// Account address.
|
||||
pub address: std::string::String,
|
||||
/// Account index in the transaction message when known.
|
||||
#[ts(type = "number | null")]
|
||||
pub account_index: std::option::Option<i64>,
|
||||
/// Whether the account is writable in the transaction message when known.
|
||||
pub writable: std::option::Option<bool>,
|
||||
/// Whether the account is a signer in the transaction message when known.
|
||||
pub signer: std::option::Option<bool>,
|
||||
/// Generic role inferred by Demo3.
|
||||
pub inferred_role: std::string::String,
|
||||
/// Confidence of the generic account inference.
|
||||
pub confidence: std::string::String,
|
||||
/// Short reason explaining why the account is listed.
|
||||
pub reason: std::string::String,
|
||||
}
|
||||
|
||||
/// Discovers candidate DEX pairs/pools directly from Solana RPC.
|
||||
#[tauri::command]
|
||||
pub(crate) async fn demo3_discover_onchain_dex_pairs(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: Demo3OnchainDexDiscoveryRequest,
|
||||
) -> Result<Demo3OnchainDexDiscoveryPayload, std::string::String> {
|
||||
let http_pool = std::sync::Arc::new(state.http_pool.clone());
|
||||
let service = kb_lib::OnchainDexPairDiscoveryService::new(http_pool);
|
||||
let lib_request = to_lib_onchain_request(&request);
|
||||
let discover_result = service.discover(lib_request).await;
|
||||
let lib_result = match discover_result {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
return Err(format!("on-chain DEX discovery failed: {}", error));
|
||||
},
|
||||
};
|
||||
let ui_result = from_lib_onchain_result(lib_result);
|
||||
let result_json_result = serde_json::to_string_pretty(&ui_result);
|
||||
let result_json = match result_json_result {
|
||||
Ok(result_json) => result_json,
|
||||
Err(error) => {
|
||||
return Err(format!("cannot serialize on-chain DEX discovery result: {}", error));
|
||||
},
|
||||
};
|
||||
Ok(Demo3OnchainDexDiscoveryPayload {
|
||||
http_role: ui_result.request.http_role.clone(),
|
||||
result_json,
|
||||
result: ui_result,
|
||||
})
|
||||
}
|
||||
|
||||
fn to_lib_onchain_request(
|
||||
request: &Demo3OnchainDexDiscoveryRequest,
|
||||
) -> kb_lib::OnchainDexPairDiscoveryRequestDto {
|
||||
return kb_lib::OnchainDexPairDiscoveryRequestDto {
|
||||
dex_code: normalize_optional_text(request.dex_code.clone()),
|
||||
program_id: normalize_optional_text(request.program_id.clone()),
|
||||
http_role: request.http_role.trim().to_string(),
|
||||
signature_limit: request.signature_limit,
|
||||
transaction_limit: request.transaction_limit,
|
||||
candidate_limit: request.candidate_limit,
|
||||
};
|
||||
}
|
||||
|
||||
fn from_lib_onchain_result(
|
||||
result: kb_lib::OnchainDexPairDiscoveryResultDto,
|
||||
) -> Demo3OnchainDexDiscoveryResult {
|
||||
let mut candidates = std::vec::Vec::new();
|
||||
for candidate in result.candidates {
|
||||
candidates.push(from_lib_onchain_candidate(candidate));
|
||||
}
|
||||
return Demo3OnchainDexDiscoveryResult {
|
||||
request: Demo3OnchainDexDiscoveryRequest {
|
||||
dex_code: result.request.dex_code,
|
||||
program_id: result.request.program_id,
|
||||
http_role: result.request.http_role,
|
||||
signature_limit: result.request.signature_limit,
|
||||
transaction_limit: result.request.transaction_limit,
|
||||
candidate_limit: result.request.candidate_limit,
|
||||
},
|
||||
resolved_dex_code: result.resolved_dex_code,
|
||||
resolved_program_id: result.resolved_program_id,
|
||||
fetched_signature_count: result.fetched_signature_count,
|
||||
fetched_transaction_count: result.fetched_transaction_count,
|
||||
missing_transaction_count: result.missing_transaction_count,
|
||||
failed_transaction_count: result.failed_transaction_count,
|
||||
candidate_count: result.candidate_count,
|
||||
candidates,
|
||||
};
|
||||
}
|
||||
|
||||
fn from_lib_onchain_candidate(
|
||||
candidate: kb_lib::OnchainDexPairCandidateDto,
|
||||
) -> Demo3OnchainDexPairCandidate {
|
||||
return Demo3OnchainDexPairCandidate {
|
||||
signature: candidate.signature,
|
||||
slot: candidate.slot,
|
||||
block_time: candidate.block_time,
|
||||
failed: candidate.failed,
|
||||
program_id: candidate.program_id,
|
||||
dex_code: candidate.dex_code,
|
||||
candidate_kind: candidate.candidate_kind,
|
||||
confidence: candidate.confidence,
|
||||
instruction_index: candidate.instruction_index,
|
||||
inner_instruction_index: candidate.inner_instruction_index,
|
||||
instruction_name: candidate.instruction_name,
|
||||
pool_address: candidate.pool_address,
|
||||
token_a_mint: candidate.token_a_mint,
|
||||
token_b_mint: candidate.token_b_mint,
|
||||
verified_pool_address: candidate.verified_pool_address,
|
||||
observed_token_mints: candidate.observed_token_mints,
|
||||
token_balance_deltas: from_lib_onchain_token_balance_deltas(candidate.token_balance_deltas),
|
||||
candidate_pool_accounts: from_lib_onchain_candidate_accounts(
|
||||
candidate.candidate_pool_accounts,
|
||||
),
|
||||
candidate_token_vault_accounts: from_lib_onchain_candidate_accounts(
|
||||
candidate.candidate_token_vault_accounts,
|
||||
),
|
||||
candidate_program_accounts: from_lib_onchain_candidate_accounts(
|
||||
candidate.candidate_program_accounts,
|
||||
),
|
||||
account_samples: candidate.account_samples,
|
||||
log_samples: candidate.log_samples,
|
||||
backfill_hint: candidate.backfill_hint,
|
||||
};
|
||||
}
|
||||
|
||||
fn from_lib_onchain_token_balance_deltas(
|
||||
values: std::vec::Vec<kb_lib::OnchainDexTokenBalanceDeltaDto>,
|
||||
) -> std::vec::Vec<Demo3OnchainDexTokenBalanceDelta> {
|
||||
let mut mapped = std::vec::Vec::new();
|
||||
for value in values {
|
||||
mapped.push(Demo3OnchainDexTokenBalanceDelta {
|
||||
account_index: value.account_index,
|
||||
account_address: value.account_address,
|
||||
mint: value.mint,
|
||||
owner: value.owner,
|
||||
token_program: value.token_program,
|
||||
pre_amount_raw: value.pre_amount_raw,
|
||||
post_amount_raw: value.post_amount_raw,
|
||||
delta_raw: value.delta_raw,
|
||||
});
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
fn from_lib_onchain_candidate_accounts(
|
||||
values: std::vec::Vec<kb_lib::OnchainDexCandidateAccountDto>,
|
||||
) -> std::vec::Vec<Demo3OnchainDexCandidateAccount> {
|
||||
let mut mapped = std::vec::Vec::new();
|
||||
for value in values {
|
||||
mapped.push(Demo3OnchainDexCandidateAccount {
|
||||
address: value.address,
|
||||
account_index: value.account_index,
|
||||
writable: value.writable,
|
||||
signer: value.signer,
|
||||
inferred_role: value.inferred_role,
|
||||
confidence: value.confidence,
|
||||
reason: value.reason,
|
||||
});
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
428
kb_demo_app/src/demo3old.rs
Normal file
428
kb_demo_app/src/demo3old.rs
Normal file
@@ -0,0 +1,428 @@
|
||||
// file: kb_demo_app/src/demo3old.rs
|
||||
|
||||
//! Tauri commands for Demo3old local DEX corpus search.
|
||||
//!
|
||||
//! Demo3old is intentionally a thin UI-facing wrapper around `kb_lib` local
|
||||
//! corpus search services. It does not contain DEX decoding or protocol logic.
|
||||
|
||||
use tauri::Manager;
|
||||
use ts_rs::TS;
|
||||
|
||||
/// Request payload for a local DEX corpus search.
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3oldLocalDexCorpusSearchRequest.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3oldLocalDexCorpusSearchRequest {
|
||||
/// Optional DEX code or decoded protocol name.
|
||||
pub dex_code: std::option::Option<std::string::String>,
|
||||
/// Optional Solana program id.
|
||||
pub program_id: std::option::Option<std::string::String>,
|
||||
/// Optional local pair id.
|
||||
#[ts(type = "number | null")]
|
||||
pub pair_id: std::option::Option<i64>,
|
||||
/// Optional pool account/address.
|
||||
pub pool_address: std::option::Option<std::string::String>,
|
||||
/// Optional token mint to match as base, quote or decoded mint.
|
||||
pub token_mint: std::option::Option<std::string::String>,
|
||||
/// Optional transaction signature.
|
||||
pub signature: std::option::Option<std::string::String>,
|
||||
/// Maximum number of rows to return per sample category.
|
||||
pub limit: u32,
|
||||
}
|
||||
|
||||
/// Response payload returned by Demo3old local DEX corpus search.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3oldLocalDexCorpusSearchPayload.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3oldLocalDexCorpusSearchPayload {
|
||||
/// Open database URL.
|
||||
pub database_url: std::string::String,
|
||||
/// Pretty JSON representation of the search result.
|
||||
pub result_json: std::string::String,
|
||||
/// Structured local DEX corpus search result.
|
||||
pub result: Demo3oldLocalDexCorpusSearchResult,
|
||||
}
|
||||
|
||||
/// Structured local DEX corpus search result.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3oldLocalDexCorpusSearchResult.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3oldLocalDexCorpusSearchResult {
|
||||
/// Normalized search request applied by the backend service.
|
||||
pub request: Demo3oldLocalDexCorpusSearchRequest,
|
||||
/// Aggregate counts for the matching local data.
|
||||
pub summary: Demo3oldLocalDexCorpusSearchSummary,
|
||||
/// Matching transaction samples.
|
||||
pub transaction_samples: std::vec::Vec<Demo3oldLocalDexCorpusTransactionSample>,
|
||||
/// Matching pool/pair samples.
|
||||
pub pool_pair_samples: std::vec::Vec<Demo3oldLocalDexCorpusPoolPairSample>,
|
||||
/// Matching decoded event samples.
|
||||
pub decoded_event_samples: std::vec::Vec<Demo3oldLocalDexCorpusDecodedEventSample>,
|
||||
}
|
||||
|
||||
/// Aggregate counts for one local DEX corpus search.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3oldLocalDexCorpusSearchSummary.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3oldLocalDexCorpusSearchSummary {
|
||||
/// Number of distinct matching transactions.
|
||||
#[ts(type = "number")]
|
||||
pub transaction_count: i64,
|
||||
/// Number of distinct matching instructions.
|
||||
#[ts(type = "number")]
|
||||
pub instruction_count: i64,
|
||||
/// Number of distinct matching decoded DEX events.
|
||||
#[ts(type = "number")]
|
||||
pub decoded_event_count: i64,
|
||||
/// Number of distinct matching pools.
|
||||
#[ts(type = "number")]
|
||||
pub pool_count: i64,
|
||||
/// Number of distinct matching pairs.
|
||||
#[ts(type = "number")]
|
||||
pub pair_count: i64,
|
||||
/// Number of distinct matching trade events.
|
||||
#[ts(type = "number")]
|
||||
pub trade_event_count: i64,
|
||||
/// Number of distinct matching candle rows.
|
||||
#[ts(type = "number")]
|
||||
pub pair_candle_count: i64,
|
||||
/// Number of distinct matching protocol candidate rows.
|
||||
#[ts(type = "number")]
|
||||
pub protocol_candidate_count: i64,
|
||||
}
|
||||
|
||||
/// Matching transaction sample for corpus discovery.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3oldLocalDexCorpusTransactionSample.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3oldLocalDexCorpusTransactionSample {
|
||||
/// Transaction id.
|
||||
#[ts(type = "number")]
|
||||
pub transaction_id: i64,
|
||||
/// Transaction signature.
|
||||
pub signature: std::string::String,
|
||||
/// Optional Solana slot.
|
||||
#[ts(type = "number | null")]
|
||||
pub slot: std::option::Option<i64>,
|
||||
/// Whether the transaction has a non-null error payload.
|
||||
pub failed: bool,
|
||||
/// Number of persisted instructions for the transaction.
|
||||
#[ts(type = "number")]
|
||||
pub instruction_count: i64,
|
||||
/// Number of persisted decoded DEX events for the transaction.
|
||||
#[ts(type = "number")]
|
||||
pub decoded_event_count: i64,
|
||||
/// Number of persisted trade events for the transaction.
|
||||
#[ts(type = "number")]
|
||||
pub trade_event_count: i64,
|
||||
/// Comma-separated distinct program ids seen in the transaction.
|
||||
pub program_ids_csv: std::string::String,
|
||||
}
|
||||
|
||||
/// Matching pool/pair sample for corpus discovery.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3oldLocalDexCorpusPoolPairSample.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3oldLocalDexCorpusPoolPairSample {
|
||||
/// Optional pool id.
|
||||
#[ts(type = "number | null")]
|
||||
pub pool_id: std::option::Option<i64>,
|
||||
/// Optional pool address.
|
||||
pub pool_address: std::option::Option<std::string::String>,
|
||||
/// Optional pair id.
|
||||
#[ts(type = "number | null")]
|
||||
pub pair_id: std::option::Option<i64>,
|
||||
/// Optional DEX code.
|
||||
pub dex_code: std::option::Option<std::string::String>,
|
||||
/// Optional pair symbol.
|
||||
pub pair_symbol: std::option::Option<std::string::String>,
|
||||
/// Optional base token mint.
|
||||
pub base_mint: std::option::Option<std::string::String>,
|
||||
/// Optional base token symbol.
|
||||
pub base_symbol: std::option::Option<std::string::String>,
|
||||
/// Optional quote token mint.
|
||||
pub quote_mint: std::option::Option<std::string::String>,
|
||||
/// Optional quote token symbol.
|
||||
pub quote_symbol: std::option::Option<std::string::String>,
|
||||
/// Number of decoded events attached to the pool.
|
||||
#[ts(type = "number")]
|
||||
pub decoded_event_count: i64,
|
||||
/// Number of trade events attached to the pair.
|
||||
#[ts(type = "number")]
|
||||
pub trade_event_count: i64,
|
||||
/// Number of candle rows attached to the pair.
|
||||
#[ts(type = "number")]
|
||||
pub pair_candle_count: i64,
|
||||
/// Latest known founding or activity signature for the pool/pair.
|
||||
pub latest_signature: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
/// Matching decoded event sample for corpus discovery.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/Demo3oldLocalDexCorpusDecodedEventSample.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Demo3oldLocalDexCorpusDecodedEventSample {
|
||||
/// Decoded event id.
|
||||
#[ts(type = "number")]
|
||||
pub decoded_event_id: i64,
|
||||
/// Transaction id.
|
||||
#[ts(type = "number")]
|
||||
pub transaction_id: i64,
|
||||
/// Transaction signature.
|
||||
pub signature: std::string::String,
|
||||
/// Optional Solana slot.
|
||||
#[ts(type = "number | null")]
|
||||
pub slot: std::option::Option<i64>,
|
||||
/// Protocol name stored on the decoded event.
|
||||
pub protocol_name: std::string::String,
|
||||
/// Program id stored on the decoded event.
|
||||
pub program_id: std::string::String,
|
||||
/// Event kind.
|
||||
pub event_kind: std::string::String,
|
||||
/// Optional pool account.
|
||||
pub pool_account: std::option::Option<std::string::String>,
|
||||
/// Optional token A mint.
|
||||
pub token_a_mint: std::option::Option<std::string::String>,
|
||||
/// Optional token B mint.
|
||||
pub token_b_mint: std::option::Option<std::string::String>,
|
||||
/// Decoded event category extracted from payload JSON.
|
||||
pub event_category: std::option::Option<std::string::String>,
|
||||
/// Decoded event lifecycle kind extracted from payload JSON.
|
||||
pub event_lifecycle_kind: std::option::Option<std::string::String>,
|
||||
/// Decoded event actionability extracted from payload JSON.
|
||||
pub event_actionability: std::option::Option<std::string::String>,
|
||||
/// Whether the decoded event is a trade candidate.
|
||||
pub trade_candidate: bool,
|
||||
/// Whether the decoded event is a candle candidate.
|
||||
pub candle_candidate: bool,
|
||||
}
|
||||
|
||||
/// Opens the `Demo3old` local DEX corpus search window.
|
||||
#[tauri::command]
|
||||
pub(crate) fn open_demo3old_window(
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> Result<(), std::string::String> {
|
||||
let existing_window_option = app_handle.get_webview_window("demo3old");
|
||||
|
||||
let demo_window = match existing_window_option {
|
||||
Some(demo_window) => demo_window,
|
||||
None => {
|
||||
let builder = tauri::WebviewWindowBuilder::new(
|
||||
&app_handle,
|
||||
"demo3old",
|
||||
tauri::WebviewUrl::App("demo3old.html".into()),
|
||||
)
|
||||
.title("Demo3old Local DEX Corpus")
|
||||
.inner_size(1480.0, 920.0)
|
||||
.min_inner_size(1100.0, 720.0)
|
||||
.center()
|
||||
.visible(true)
|
||||
.transparent(false)
|
||||
.decorations(true);
|
||||
let build_result = builder.build();
|
||||
match build_result {
|
||||
Ok(window) => window,
|
||||
Err(error) => {
|
||||
return Err(format!("cannot create demo3old window: {error:?}"));
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
let show_result = demo_window.show();
|
||||
if let Err(error) = show_result {
|
||||
return Err(format!("cannot show demo3old window: {error:?}"));
|
||||
}
|
||||
let focus_result = demo_window.set_focus();
|
||||
if let Err(error) = focus_result {
|
||||
return Err(format!("cannot focus demo3old window: {error:?}"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Searches local DEX corpus candidates from the persisted SQLite pipeline.
|
||||
#[tauri::command]
|
||||
pub(crate) async fn demo3old_search_local_dex_corpus(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: Demo3oldLocalDexCorpusSearchRequest,
|
||||
) -> Result<Demo3oldLocalDexCorpusSearchPayload, std::string::String> {
|
||||
let database = state.database.clone();
|
||||
let service = kb_lib::LocalDexCorpusSearchService::new(database.clone());
|
||||
let search_result = service.search(to_lib_search_request(&request)).await;
|
||||
let lib_result = match search_result {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
return Err(format!("local DEX corpus search failed: {}", error));
|
||||
},
|
||||
};
|
||||
let ui_result = from_lib_search_result(lib_result);
|
||||
let result_json_result = serde_json::to_string_pretty(&ui_result);
|
||||
let result_json = match result_json_result {
|
||||
Ok(result_json) => result_json,
|
||||
Err(error) => {
|
||||
return Err(format!("cannot serialize local DEX corpus search result: {}", error));
|
||||
},
|
||||
};
|
||||
Ok(Demo3oldLocalDexCorpusSearchPayload {
|
||||
database_url: database.database_url().to_string(),
|
||||
result_json,
|
||||
result: ui_result,
|
||||
})
|
||||
}
|
||||
|
||||
fn to_lib_search_request(
|
||||
request: &Demo3oldLocalDexCorpusSearchRequest,
|
||||
) -> kb_lib::LocalDexCorpusSearchRequestDto {
|
||||
return kb_lib::LocalDexCorpusSearchRequestDto {
|
||||
dex_code: normalize_optional_text(request.dex_code.clone()),
|
||||
program_id: normalize_optional_text(request.program_id.clone()),
|
||||
pair_id: request.pair_id,
|
||||
pool_address: normalize_optional_text(request.pool_address.clone()),
|
||||
token_mint: normalize_optional_text(request.token_mint.clone()),
|
||||
signature: normalize_optional_text(request.signature.clone()),
|
||||
limit: request.limit,
|
||||
};
|
||||
}
|
||||
|
||||
fn normalize_optional_text(
|
||||
value: std::option::Option<std::string::String>,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
let value = match value {
|
||||
Some(value) => value.trim().to_string(),
|
||||
None => return None,
|
||||
};
|
||||
if value.is_empty() {
|
||||
return None;
|
||||
}
|
||||
return Some(value);
|
||||
}
|
||||
|
||||
fn from_lib_search_result(
|
||||
result: kb_lib::LocalDexCorpusSearchResultDto,
|
||||
) -> Demo3oldLocalDexCorpusSearchResult {
|
||||
let mut transaction_samples = std::vec::Vec::new();
|
||||
for sample in result.transaction_samples {
|
||||
transaction_samples.push(from_lib_transaction_sample(sample));
|
||||
}
|
||||
let mut pool_pair_samples = std::vec::Vec::new();
|
||||
for sample in result.pool_pair_samples {
|
||||
pool_pair_samples.push(from_lib_pool_pair_sample(sample));
|
||||
}
|
||||
let mut decoded_event_samples = std::vec::Vec::new();
|
||||
for sample in result.decoded_event_samples {
|
||||
decoded_event_samples.push(from_lib_decoded_event_sample(sample));
|
||||
}
|
||||
return Demo3oldLocalDexCorpusSearchResult {
|
||||
request: from_lib_request(result.request),
|
||||
summary: from_lib_summary(result.summary),
|
||||
transaction_samples,
|
||||
pool_pair_samples,
|
||||
decoded_event_samples,
|
||||
};
|
||||
}
|
||||
|
||||
fn from_lib_request(
|
||||
request: kb_lib::LocalDexCorpusSearchRequestDto,
|
||||
) -> Demo3oldLocalDexCorpusSearchRequest {
|
||||
return Demo3oldLocalDexCorpusSearchRequest {
|
||||
dex_code: request.dex_code,
|
||||
program_id: request.program_id,
|
||||
pair_id: request.pair_id,
|
||||
pool_address: request.pool_address,
|
||||
token_mint: request.token_mint,
|
||||
signature: request.signature,
|
||||
limit: request.limit,
|
||||
};
|
||||
}
|
||||
|
||||
fn from_lib_summary(
|
||||
summary: kb_lib::LocalDexCorpusSearchSummaryDto,
|
||||
) -> Demo3oldLocalDexCorpusSearchSummary {
|
||||
return Demo3oldLocalDexCorpusSearchSummary {
|
||||
transaction_count: summary.transaction_count,
|
||||
instruction_count: summary.instruction_count,
|
||||
decoded_event_count: summary.decoded_event_count,
|
||||
pool_count: summary.pool_count,
|
||||
pair_count: summary.pair_count,
|
||||
trade_event_count: summary.trade_event_count,
|
||||
pair_candle_count: summary.pair_candle_count,
|
||||
protocol_candidate_count: summary.protocol_candidate_count,
|
||||
};
|
||||
}
|
||||
|
||||
fn from_lib_transaction_sample(
|
||||
sample: kb_lib::LocalDexCorpusTransactionSampleDto,
|
||||
) -> Demo3oldLocalDexCorpusTransactionSample {
|
||||
return Demo3oldLocalDexCorpusTransactionSample {
|
||||
transaction_id: sample.transaction_id,
|
||||
signature: sample.signature,
|
||||
slot: sample.slot,
|
||||
failed: sample.failed,
|
||||
instruction_count: sample.instruction_count,
|
||||
decoded_event_count: sample.decoded_event_count,
|
||||
trade_event_count: sample.trade_event_count,
|
||||
program_ids_csv: sample.program_ids_csv,
|
||||
};
|
||||
}
|
||||
|
||||
fn from_lib_pool_pair_sample(
|
||||
sample: kb_lib::LocalDexCorpusPoolPairSampleDto,
|
||||
) -> Demo3oldLocalDexCorpusPoolPairSample {
|
||||
return Demo3oldLocalDexCorpusPoolPairSample {
|
||||
pool_id: sample.pool_id,
|
||||
pool_address: sample.pool_address,
|
||||
pair_id: sample.pair_id,
|
||||
dex_code: sample.dex_code,
|
||||
pair_symbol: sample.pair_symbol,
|
||||
base_mint: sample.base_mint,
|
||||
base_symbol: sample.base_symbol,
|
||||
quote_mint: sample.quote_mint,
|
||||
quote_symbol: sample.quote_symbol,
|
||||
decoded_event_count: sample.decoded_event_count,
|
||||
trade_event_count: sample.trade_event_count,
|
||||
pair_candle_count: sample.pair_candle_count,
|
||||
latest_signature: sample.latest_signature,
|
||||
};
|
||||
}
|
||||
|
||||
fn from_lib_decoded_event_sample(
|
||||
sample: kb_lib::LocalDexCorpusDecodedEventSampleDto,
|
||||
) -> Demo3oldLocalDexCorpusDecodedEventSample {
|
||||
return Demo3oldLocalDexCorpusDecodedEventSample {
|
||||
decoded_event_id: sample.decoded_event_id,
|
||||
transaction_id: sample.transaction_id,
|
||||
signature: sample.signature,
|
||||
slot: sample.slot,
|
||||
protocol_name: sample.protocol_name,
|
||||
program_id: sample.program_id,
|
||||
event_kind: sample.event_kind,
|
||||
pool_account: sample.pool_account,
|
||||
token_a_mint: sample.token_a_mint,
|
||||
token_b_mint: sample.token_b_mint,
|
||||
event_category: sample.event_category,
|
||||
event_lifecycle_kind: sample.event_lifecycle_kind,
|
||||
event_actionability: sample.event_actionability,
|
||||
trade_candidate: sample.trade_candidate,
|
||||
candle_candidate: sample.candle_candidate,
|
||||
};
|
||||
}
|
||||
@@ -1109,6 +1109,20 @@ pub(crate) struct DemoPipeline2BackfillPoolRequest {
|
||||
pub pool_signature_limit: u32,
|
||||
}
|
||||
|
||||
/// Request payload for single-signature backfill.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/DemoPipeline2BackfillSignatureRequest.ts"
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct DemoPipeline2BackfillSignatureRequest {
|
||||
/// Transaction signature to resolve and replay.
|
||||
pub signature: std::string::String,
|
||||
/// Optional HTTP role.
|
||||
pub http_role: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
/// Shared backfill response payload.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/DemoPipeline2BackfillPayload.ts")]
|
||||
@@ -1116,7 +1130,7 @@ pub(crate) struct DemoPipeline2BackfillPoolRequest {
|
||||
pub(crate) struct DemoPipeline2BackfillPayload {
|
||||
/// Object key used by the backfill.
|
||||
pub object_key: std::string::String,
|
||||
/// Mode: `tokenMint` or `poolAddress`.
|
||||
/// Mode: `tokenMint`, `poolAddress` or `signature`.
|
||||
pub mode: std::string::String,
|
||||
/// HTTP role used.
|
||||
pub http_role: std::string::String,
|
||||
@@ -1265,7 +1279,7 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
|
||||
let service = kb_lib::LocalPipelineValidationService::new(database.clone());
|
||||
let profile_code = match request {
|
||||
Some(request) => request.profile_code,
|
||||
None => "0.7.39_dex_first_effective_swap_surfaces".to_string(),
|
||||
None => "0.7.40_raydium_effective_surfaces".to_string(),
|
||||
};
|
||||
let run_result = match profile_code.as_str() {
|
||||
"0.7.27" | "0.7.27_dexes_non_regression" => {
|
||||
@@ -1304,7 +1318,12 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
|
||||
"0.7.38" | "0.7.38_token_metadata_gap_prioritization" => {
|
||||
service.validate_v0_7_38_current_database().await
|
||||
},
|
||||
"0.7.39" | "0.7.39_dex_first_effective_swap_surfaces" | "0.7.39_launch_surface_origin_baseline" => {
|
||||
"0.7.39"
|
||||
| "0.7.39_dex_first_effective_swap_surfaces"
|
||||
| "0.7.39_launch_surface_origin_baseline" => {
|
||||
service.validate_v0_7_39_current_database().await
|
||||
},
|
||||
"0.7.40" | "0.7.40_raydium_effective_surfaces" => {
|
||||
service.validate_v0_7_39_current_database().await
|
||||
},
|
||||
other => Err(kb_lib::Error::InvalidState(format!(
|
||||
@@ -1495,6 +1514,54 @@ pub(crate) async fn demo_pipeline2_backfill_pool_address(
|
||||
})
|
||||
}
|
||||
|
||||
/// Runs a targeted single-signature backfill then returns the refreshed catalog.
|
||||
#[tauri::command]
|
||||
pub(crate) async fn demo_pipeline2_backfill_signature(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: DemoPipeline2BackfillSignatureRequest,
|
||||
) -> Result<DemoPipeline2BackfillPayload, std::string::String> {
|
||||
let signature = request.signature.trim().to_string();
|
||||
if signature.is_empty() {
|
||||
return Err("signature must not be empty".to_string());
|
||||
}
|
||||
let http_role = demo_pipeline2_normalize_http_role(request.http_role);
|
||||
let database = state.database.clone();
|
||||
let http_pool = std::sync::Arc::new(state.http_pool.clone());
|
||||
let service = kb_lib::TokenBackfillService::new(http_pool, database.clone(), http_role.clone());
|
||||
let result = service.backfill_signature(signature.as_str()).await;
|
||||
let backfill = match result {
|
||||
Ok(backfill) => backfill,
|
||||
Err(error) => {
|
||||
return Err(format!(
|
||||
"cannot backfill signature '{}' with role '{}': {}",
|
||||
signature, http_role, error
|
||||
));
|
||||
},
|
||||
};
|
||||
let summary_json_result = serde_json::to_string_pretty(&backfill);
|
||||
let summary_json = match summary_json_result {
|
||||
Ok(summary_json) => summary_json,
|
||||
Err(error) => {
|
||||
return Err(format!(
|
||||
"cannot serialize signature backfill result for '{}': {}",
|
||||
signature, error
|
||||
));
|
||||
},
|
||||
};
|
||||
let catalog_result = demo_pipeline2_build_catalog(database).await;
|
||||
let catalog = match catalog_result {
|
||||
Ok(catalog) => catalog,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
Ok(DemoPipeline2BackfillPayload {
|
||||
object_key: signature,
|
||||
mode: "signature".to_string(),
|
||||
http_role,
|
||||
summary_json,
|
||||
catalog,
|
||||
})
|
||||
}
|
||||
|
||||
/// Loads candles for one pair and one timeframe.
|
||||
#[tauri::command]
|
||||
pub(crate) async fn demo_pipeline2_get_pair_candles(
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#![deny(unreachable_pub)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod demo3;
|
||||
mod demo3old;
|
||||
mod demo_http;
|
||||
mod demo_pipeline;
|
||||
mod demo_pipeline2;
|
||||
@@ -121,6 +123,11 @@ pub async fn run() -> Result<(), kb_lib::Error> {
|
||||
tauri_builder = tauri_builder.invoke_handler(tauri::generate_handler![
|
||||
start_ws_clients,
|
||||
stop_ws_clients,
|
||||
crate::demo3::open_demo3_window,
|
||||
crate::demo3::demo3_search_local_dex_corpus,
|
||||
crate::demo3::demo3_discover_onchain_dex_pairs,
|
||||
crate::demo3old::open_demo3old_window,
|
||||
crate::demo3old::demo3old_search_local_dex_corpus,
|
||||
crate::demo_ws::open_demo_ws_window,
|
||||
crate::demo_ws::demo_ws_list_endpoints,
|
||||
crate::demo_ws::demo_ws_get_status,
|
||||
@@ -149,6 +156,7 @@ pub async fn run() -> Result<(), kb_lib::Error> {
|
||||
crate::demo_pipeline2::demo_pipeline2_get_catalog,
|
||||
crate::demo_pipeline2::demo_pipeline2_backfill_token_mint,
|
||||
crate::demo_pipeline2::demo_pipeline2_backfill_pool_address,
|
||||
crate::demo_pipeline2::demo_pipeline2_backfill_signature,
|
||||
crate::demo_pipeline2::demo_pipeline2_get_pair_candles,
|
||||
crate::demo_pipeline2::demo_pipeline2_replay_local_pipeline,
|
||||
crate::demo_pipeline2::demo_pipeline2_diagnose_local_pipeline,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "kb-demo-app",
|
||||
"version": "0.7.39",
|
||||
"version": "0.7.40",
|
||||
"identifier": "com.sasedev.kb-demo-app",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
@@ -79,6 +79,34 @@
|
||||
"transparent": false,
|
||||
"decorations": true
|
||||
},
|
||||
{
|
||||
"label": "demo3",
|
||||
"url": "demo3.html",
|
||||
"title": "Demo3 Local DEX Corpus",
|
||||
"width": 1480,
|
||||
"height": 920,
|
||||
"minWidth": 1000,
|
||||
"minHeight": 700,
|
||||
"center": true,
|
||||
"visible": false,
|
||||
"create": false,
|
||||
"transparent": false,
|
||||
"decorations": true
|
||||
},
|
||||
{
|
||||
"label": "demo3old",
|
||||
"url": "demo3old.html",
|
||||
"title": "Demo3old Local DEX Corpus",
|
||||
"width": 1480,
|
||||
"height": 920,
|
||||
"minWidth": 1000,
|
||||
"minHeight": 700,
|
||||
"center": true,
|
||||
"visible": false,
|
||||
"create": false,
|
||||
"transparent": false,
|
||||
"decorations": true
|
||||
},
|
||||
{
|
||||
"label": "demo_pipeline",
|
||||
"url": "demo_pipeline.html",
|
||||
@@ -120,4 +148,4 @@
|
||||
"icons/favicon.ico"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ export default defineConfig(() => ({
|
||||
"demo_ws": normalizePath(resolve(__dirname, 'frontend/demo_ws.html')),
|
||||
"demo_http": normalizePath(resolve(__dirname, 'frontend/demo_http.html')),
|
||||
"demo_ws_manager": normalizePath(resolve(__dirname, 'frontend/demo_ws_manager.html')),
|
||||
"demo3old": normalizePath(resolve(__dirname, 'frontend/demo3old.html')),
|
||||
"demo3": normalizePath(resolve(__dirname, 'frontend/demo3.html')),
|
||||
"demo_pipeline": normalizePath(resolve(__dirname, 'frontend/demo_pipeline.html')),
|
||||
"demo_pipeline2": normalizePath(resolve(__dirname, 'frontend/demo_pipeline2.html'))
|
||||
},
|
||||
|
||||
@@ -31,6 +31,12 @@ pub use dtos::LaunchSurfaceDto;
|
||||
pub use dtos::LaunchSurfaceKeyDto;
|
||||
pub use dtos::LiquidityEventDto;
|
||||
pub use dtos::LocalDecodedEventDiagnosticSummaryDto;
|
||||
pub use dtos::LocalDexCorpusDecodedEventSampleDto;
|
||||
pub use dtos::LocalDexCorpusPoolPairSampleDto;
|
||||
pub use dtos::LocalDexCorpusSearchRequestDto;
|
||||
pub use dtos::LocalDexCorpusSearchResultDto;
|
||||
pub use dtos::LocalDexCorpusSearchSummaryDto;
|
||||
pub use dtos::LocalDexCorpusTransactionSampleDto;
|
||||
pub use dtos::LocalDexDiagnosticSummaryDto;
|
||||
pub use dtos::LocalDuplicateDecodedEventTradeDiagnosticSampleDto;
|
||||
pub use dtos::LocalEventClassificationDiagnosticSummaryDto;
|
||||
@@ -46,6 +52,8 @@ pub use dtos::LocalPairTradingReadinessDiagnosticSummaryDto;
|
||||
pub use dtos::LocalPipelineDiagnosticCountersDto;
|
||||
pub use dtos::LocalPipelineDiagnosticSummaryDto;
|
||||
pub use dtos::LocalPoolOriginDiagnosticSampleDto;
|
||||
pub use dtos::LocalRaydiumProgramInstructionDiagnosticSummaryDto;
|
||||
pub use dtos::LocalRaydiumSurfaceDiagnosticSummaryDto;
|
||||
pub use dtos::LocalTokenMetadataGapDiagnosticSampleDto;
|
||||
pub use dtos::ObservedTokenDto;
|
||||
pub use dtos::OnchainObservationDto;
|
||||
@@ -160,6 +168,10 @@ pub use queries::query_launch_surfaces_upsert;
|
||||
pub use queries::query_liquidity_events_list_recent;
|
||||
pub use queries::query_liquidity_events_upsert;
|
||||
pub use queries::query_local_decoded_event_diagnostic_list_summaries;
|
||||
pub use queries::query_local_dex_corpus_search_get_summary;
|
||||
pub use queries::query_local_dex_corpus_search_list_decoded_event_samples;
|
||||
pub use queries::query_local_dex_corpus_search_list_pool_pair_samples;
|
||||
pub use queries::query_local_dex_corpus_search_list_transaction_samples;
|
||||
pub use queries::query_local_duplicate_decoded_event_trade_diagnostic_list_samples;
|
||||
pub use queries::query_local_event_classification_diagnostic_list_summaries;
|
||||
pub use queries::query_local_launch_origin_diagnostic_list_samples;
|
||||
@@ -175,6 +187,7 @@ pub use queries::query_local_pair_without_trade_diagnostic_list_samples;
|
||||
pub use queries::query_local_pipeline_diagnostic_get_counters;
|
||||
pub use queries::query_local_pipeline_diagnostic_list_summaries;
|
||||
pub use queries::query_local_pool_origin_diagnostic_list_samples;
|
||||
pub use queries::query_local_raydium_program_instruction_diagnostic_list_summaries;
|
||||
pub use queries::query_local_token_metadata_gap_diagnostic_list_samples;
|
||||
pub use queries::query_observed_tokens_get_by_mint;
|
||||
pub use queries::query_observed_tokens_list;
|
||||
|
||||
@@ -17,6 +17,7 @@ mod launch_attribution;
|
||||
mod launch_surface;
|
||||
mod launch_surface_key;
|
||||
mod liquidity_event;
|
||||
mod local_dex_corpus_search;
|
||||
mod local_pipeline_diagnostics;
|
||||
mod observed_token;
|
||||
mod onchain_observation;
|
||||
@@ -45,6 +46,10 @@ mod wallet;
|
||||
mod wallet_holding;
|
||||
mod wallet_participation;
|
||||
|
||||
pub(crate) use local_dex_corpus_search::LocalDexCorpusDecodedEventSampleRow;
|
||||
pub(crate) use local_dex_corpus_search::LocalDexCorpusPoolPairSampleRow;
|
||||
pub(crate) use local_dex_corpus_search::LocalDexCorpusSearchSummaryRow;
|
||||
pub(crate) use local_dex_corpus_search::LocalDexCorpusTransactionSampleRow;
|
||||
pub(crate) use local_pipeline_diagnostics::LocalDecodedEventDiagnosticSummaryRow;
|
||||
pub(crate) use local_pipeline_diagnostics::LocalDexDiagnosticSummaryRow;
|
||||
pub(crate) use local_pipeline_diagnostics::LocalDuplicateDecodedEventTradeDiagnosticSampleRow;
|
||||
@@ -60,6 +65,7 @@ pub(crate) use local_pipeline_diagnostics::LocalPairGapDiagnosticSampleRow;
|
||||
pub(crate) use local_pipeline_diagnostics::LocalPairTradingReadinessDiagnosticSummaryRow;
|
||||
pub(crate) use local_pipeline_diagnostics::LocalPipelineDiagnosticCountersRow;
|
||||
pub(crate) use local_pipeline_diagnostics::LocalPoolOriginDiagnosticSampleRow;
|
||||
pub(crate) use local_pipeline_diagnostics::LocalRaydiumProgramInstructionDiagnosticSummaryRow;
|
||||
pub(crate) use local_pipeline_diagnostics::LocalTokenMetadataGapDiagnosticSampleRow;
|
||||
|
||||
pub use analysis_signal::AnalysisSignalDto;
|
||||
@@ -77,6 +83,12 @@ pub use launch_attribution::LaunchAttributionDto;
|
||||
pub use launch_surface::LaunchSurfaceDto;
|
||||
pub use launch_surface_key::LaunchSurfaceKeyDto;
|
||||
pub use liquidity_event::LiquidityEventDto;
|
||||
pub use local_dex_corpus_search::LocalDexCorpusDecodedEventSampleDto;
|
||||
pub use local_dex_corpus_search::LocalDexCorpusPoolPairSampleDto;
|
||||
pub use local_dex_corpus_search::LocalDexCorpusSearchRequestDto;
|
||||
pub use local_dex_corpus_search::LocalDexCorpusSearchResultDto;
|
||||
pub use local_dex_corpus_search::LocalDexCorpusSearchSummaryDto;
|
||||
pub use local_dex_corpus_search::LocalDexCorpusTransactionSampleDto;
|
||||
pub use local_pipeline_diagnostics::LocalDecodedEventDiagnosticSummaryDto;
|
||||
pub use local_pipeline_diagnostics::LocalDexDiagnosticSummaryDto;
|
||||
pub use local_pipeline_diagnostics::LocalDuplicateDecodedEventTradeDiagnosticSampleDto;
|
||||
@@ -93,6 +105,8 @@ pub use local_pipeline_diagnostics::LocalPairTradingReadinessDiagnosticSummaryDt
|
||||
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticCountersDto;
|
||||
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticSummaryDto;
|
||||
pub use local_pipeline_diagnostics::LocalPoolOriginDiagnosticSampleDto;
|
||||
pub use local_pipeline_diagnostics::LocalRaydiumProgramInstructionDiagnosticSummaryDto;
|
||||
pub use local_pipeline_diagnostics::LocalRaydiumSurfaceDiagnosticSummaryDto;
|
||||
pub use local_pipeline_diagnostics::LocalTokenMetadataGapDiagnosticSampleDto;
|
||||
pub use observed_token::ObservedTokenDto;
|
||||
pub use onchain_observation::OnchainObservationDto;
|
||||
|
||||
281
kb_lib/src/db/dtos/local_dex_corpus_search.rs
Normal file
281
kb_lib/src/db/dtos/local_dex_corpus_search.rs
Normal file
@@ -0,0 +1,281 @@
|
||||
// file: kb_lib/src/db/dtos/local_dex_corpus_search.rs
|
||||
|
||||
//! DTOs for local DEX corpus searches used by Demo3.
|
||||
|
||||
/// Search request for local DEX corpus discovery.
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct LocalDexCorpusSearchRequestDto {
|
||||
/// Optional DEX code or decoded protocol name.
|
||||
pub dex_code: std::option::Option<std::string::String>,
|
||||
/// Optional Solana program id.
|
||||
pub program_id: std::option::Option<std::string::String>,
|
||||
/// Optional local pair id.
|
||||
pub pair_id: std::option::Option<i64>,
|
||||
/// Optional pool account/address.
|
||||
pub pool_address: std::option::Option<std::string::String>,
|
||||
/// Optional token mint to match as base, quote or decoded mint.
|
||||
pub token_mint: std::option::Option<std::string::String>,
|
||||
/// Optional transaction signature.
|
||||
pub signature: std::option::Option<std::string::String>,
|
||||
/// Maximum number of rows to return per sample category.
|
||||
pub limit: u32,
|
||||
}
|
||||
|
||||
/// Result of a local DEX corpus search.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct LocalDexCorpusSearchResultDto {
|
||||
/// Normalized request used for the search.
|
||||
pub request: crate::LocalDexCorpusSearchRequestDto,
|
||||
/// Aggregate counts for matching local data.
|
||||
pub summary: crate::LocalDexCorpusSearchSummaryDto,
|
||||
/// Matching transaction samples.
|
||||
pub transaction_samples: std::vec::Vec<crate::LocalDexCorpusTransactionSampleDto>,
|
||||
/// Matching pool/pair samples.
|
||||
pub pool_pair_samples: std::vec::Vec<crate::LocalDexCorpusPoolPairSampleDto>,
|
||||
/// Matching decoded event samples.
|
||||
pub decoded_event_samples: std::vec::Vec<crate::LocalDexCorpusDecodedEventSampleDto>,
|
||||
}
|
||||
|
||||
/// Aggregate counts for a local DEX corpus search.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct LocalDexCorpusSearchSummaryDto {
|
||||
/// Number of distinct matching transactions.
|
||||
pub transaction_count: i64,
|
||||
/// Number of distinct matching instructions.
|
||||
pub instruction_count: i64,
|
||||
/// Number of distinct matching decoded DEX events.
|
||||
pub decoded_event_count: i64,
|
||||
/// Number of distinct matching pools.
|
||||
pub pool_count: i64,
|
||||
/// Number of distinct matching pairs.
|
||||
pub pair_count: i64,
|
||||
/// Number of distinct matching trade events.
|
||||
pub trade_event_count: i64,
|
||||
/// Number of distinct matching candle rows.
|
||||
pub pair_candle_count: i64,
|
||||
/// Number of distinct matching protocol-candidate rows.
|
||||
pub protocol_candidate_count: i64,
|
||||
}
|
||||
|
||||
/// Matching transaction sample for corpus discovery.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct LocalDexCorpusTransactionSampleDto {
|
||||
/// Transaction id.
|
||||
pub transaction_id: i64,
|
||||
/// Transaction signature.
|
||||
pub signature: std::string::String,
|
||||
/// Optional Solana slot.
|
||||
pub slot: std::option::Option<i64>,
|
||||
/// Whether the transaction has a non-null error payload.
|
||||
pub failed: bool,
|
||||
/// Number of persisted instructions for the transaction.
|
||||
pub instruction_count: i64,
|
||||
/// Number of persisted decoded DEX events for the transaction.
|
||||
pub decoded_event_count: i64,
|
||||
/// Number of persisted trade events for the transaction.
|
||||
pub trade_event_count: i64,
|
||||
/// Comma-separated distinct program ids seen in the transaction.
|
||||
pub program_ids_csv: std::string::String,
|
||||
}
|
||||
|
||||
/// Matching pool/pair sample for corpus discovery.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct LocalDexCorpusPoolPairSampleDto {
|
||||
/// Optional pool id.
|
||||
pub pool_id: std::option::Option<i64>,
|
||||
/// Optional pool address.
|
||||
pub pool_address: std::option::Option<std::string::String>,
|
||||
/// Optional pair id.
|
||||
pub pair_id: std::option::Option<i64>,
|
||||
/// Optional DEX code.
|
||||
pub dex_code: std::option::Option<std::string::String>,
|
||||
/// Optional pair symbol.
|
||||
pub pair_symbol: std::option::Option<std::string::String>,
|
||||
/// Optional base token mint.
|
||||
pub base_mint: std::option::Option<std::string::String>,
|
||||
/// Optional base token symbol.
|
||||
pub base_symbol: std::option::Option<std::string::String>,
|
||||
/// Optional quote token mint.
|
||||
pub quote_mint: std::option::Option<std::string::String>,
|
||||
/// Optional quote token symbol.
|
||||
pub quote_symbol: std::option::Option<std::string::String>,
|
||||
/// Number of decoded events attached to the pool.
|
||||
pub decoded_event_count: i64,
|
||||
/// Number of trade events attached to the pair.
|
||||
pub trade_event_count: i64,
|
||||
/// Number of candle rows attached to the pair.
|
||||
pub pair_candle_count: i64,
|
||||
/// Latest known founding or activity signature for the pool/pair.
|
||||
pub latest_signature: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
/// Matching decoded event sample for corpus discovery.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct LocalDexCorpusDecodedEventSampleDto {
|
||||
/// Decoded event id.
|
||||
pub decoded_event_id: i64,
|
||||
/// Transaction id.
|
||||
pub transaction_id: i64,
|
||||
/// Transaction signature.
|
||||
pub signature: std::string::String,
|
||||
/// Optional Solana slot.
|
||||
pub slot: std::option::Option<i64>,
|
||||
/// Protocol name stored on the decoded event.
|
||||
pub protocol_name: std::string::String,
|
||||
/// Program id stored on the decoded event.
|
||||
pub program_id: std::string::String,
|
||||
/// Event kind.
|
||||
pub event_kind: std::string::String,
|
||||
/// Optional pool account.
|
||||
pub pool_account: std::option::Option<std::string::String>,
|
||||
/// Optional token A mint.
|
||||
pub token_a_mint: std::option::Option<std::string::String>,
|
||||
/// Optional token B mint.
|
||||
pub token_b_mint: std::option::Option<std::string::String>,
|
||||
/// Decoded event category extracted from payload JSON.
|
||||
pub event_category: std::option::Option<std::string::String>,
|
||||
/// Decoded event lifecycle kind extracted from payload JSON.
|
||||
pub event_lifecycle_kind: std::option::Option<std::string::String>,
|
||||
/// Decoded event actionability extracted from payload JSON.
|
||||
pub event_actionability: std::option::Option<std::string::String>,
|
||||
/// Whether the decoded event is a trade candidate.
|
||||
pub trade_candidate: bool,
|
||||
/// Whether the decoded event is a candle candidate.
|
||||
pub candle_candidate: bool,
|
||||
}
|
||||
|
||||
/// SQL row for aggregate counts of a local DEX corpus search.
|
||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||
pub(crate) struct LocalDexCorpusSearchSummaryRow {
|
||||
pub(crate) transaction_count: i64,
|
||||
pub(crate) instruction_count: i64,
|
||||
pub(crate) decoded_event_count: i64,
|
||||
pub(crate) pool_count: i64,
|
||||
pub(crate) pair_count: i64,
|
||||
pub(crate) trade_event_count: i64,
|
||||
pub(crate) pair_candle_count: i64,
|
||||
pub(crate) protocol_candidate_count: i64,
|
||||
}
|
||||
|
||||
/// SQL row for a matching transaction sample.
|
||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||
pub(crate) struct LocalDexCorpusTransactionSampleRow {
|
||||
pub(crate) transaction_id: i64,
|
||||
pub(crate) signature: std::string::String,
|
||||
pub(crate) slot: std::option::Option<i64>,
|
||||
pub(crate) failed: i64,
|
||||
pub(crate) instruction_count: i64,
|
||||
pub(crate) decoded_event_count: i64,
|
||||
pub(crate) trade_event_count: i64,
|
||||
pub(crate) program_ids_csv: std::string::String,
|
||||
}
|
||||
|
||||
/// SQL row for a matching pool/pair sample.
|
||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||
pub(crate) struct LocalDexCorpusPoolPairSampleRow {
|
||||
pub(crate) pool_id: std::option::Option<i64>,
|
||||
pub(crate) pool_address: std::option::Option<std::string::String>,
|
||||
pub(crate) pair_id: std::option::Option<i64>,
|
||||
pub(crate) dex_code: std::option::Option<std::string::String>,
|
||||
pub(crate) pair_symbol: std::option::Option<std::string::String>,
|
||||
pub(crate) base_mint: std::option::Option<std::string::String>,
|
||||
pub(crate) base_symbol: std::option::Option<std::string::String>,
|
||||
pub(crate) quote_mint: std::option::Option<std::string::String>,
|
||||
pub(crate) quote_symbol: std::option::Option<std::string::String>,
|
||||
pub(crate) decoded_event_count: i64,
|
||||
pub(crate) trade_event_count: i64,
|
||||
pub(crate) pair_candle_count: i64,
|
||||
pub(crate) latest_signature: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
/// SQL row for a matching decoded event sample.
|
||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||
pub(crate) struct LocalDexCorpusDecodedEventSampleRow {
|
||||
pub(crate) decoded_event_id: i64,
|
||||
pub(crate) transaction_id: i64,
|
||||
pub(crate) signature: std::string::String,
|
||||
pub(crate) slot: std::option::Option<i64>,
|
||||
pub(crate) protocol_name: std::string::String,
|
||||
pub(crate) program_id: std::string::String,
|
||||
pub(crate) event_kind: std::string::String,
|
||||
pub(crate) pool_account: std::option::Option<std::string::String>,
|
||||
pub(crate) token_a_mint: std::option::Option<std::string::String>,
|
||||
pub(crate) token_b_mint: std::option::Option<std::string::String>,
|
||||
pub(crate) event_category: std::option::Option<std::string::String>,
|
||||
pub(crate) event_lifecycle_kind: std::option::Option<std::string::String>,
|
||||
pub(crate) event_actionability: std::option::Option<std::string::String>,
|
||||
pub(crate) trade_candidate: std::option::Option<i64>,
|
||||
pub(crate) candle_candidate: std::option::Option<i64>,
|
||||
}
|
||||
|
||||
impl From<LocalDexCorpusSearchSummaryRow> for LocalDexCorpusSearchSummaryDto {
|
||||
fn from(row: LocalDexCorpusSearchSummaryRow) -> Self {
|
||||
return Self {
|
||||
transaction_count: row.transaction_count,
|
||||
instruction_count: row.instruction_count,
|
||||
decoded_event_count: row.decoded_event_count,
|
||||
pool_count: row.pool_count,
|
||||
pair_count: row.pair_count,
|
||||
trade_event_count: row.trade_event_count,
|
||||
pair_candle_count: row.pair_candle_count,
|
||||
protocol_candidate_count: row.protocol_candidate_count,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LocalDexCorpusTransactionSampleRow> for LocalDexCorpusTransactionSampleDto {
|
||||
fn from(row: LocalDexCorpusTransactionSampleRow) -> Self {
|
||||
return Self {
|
||||
transaction_id: row.transaction_id,
|
||||
signature: row.signature,
|
||||
slot: row.slot,
|
||||
failed: row.failed != 0,
|
||||
instruction_count: row.instruction_count,
|
||||
decoded_event_count: row.decoded_event_count,
|
||||
trade_event_count: row.trade_event_count,
|
||||
program_ids_csv: row.program_ids_csv,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LocalDexCorpusPoolPairSampleRow> for LocalDexCorpusPoolPairSampleDto {
|
||||
fn from(row: LocalDexCorpusPoolPairSampleRow) -> Self {
|
||||
return Self {
|
||||
pool_id: row.pool_id,
|
||||
pool_address: row.pool_address,
|
||||
pair_id: row.pair_id,
|
||||
dex_code: row.dex_code,
|
||||
pair_symbol: row.pair_symbol,
|
||||
base_mint: row.base_mint,
|
||||
base_symbol: row.base_symbol,
|
||||
quote_mint: row.quote_mint,
|
||||
quote_symbol: row.quote_symbol,
|
||||
decoded_event_count: row.decoded_event_count,
|
||||
trade_event_count: row.trade_event_count,
|
||||
pair_candle_count: row.pair_candle_count,
|
||||
latest_signature: row.latest_signature,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LocalDexCorpusDecodedEventSampleRow> for LocalDexCorpusDecodedEventSampleDto {
|
||||
fn from(row: LocalDexCorpusDecodedEventSampleRow) -> Self {
|
||||
return Self {
|
||||
decoded_event_id: row.decoded_event_id,
|
||||
transaction_id: row.transaction_id,
|
||||
signature: row.signature,
|
||||
slot: row.slot,
|
||||
protocol_name: row.protocol_name,
|
||||
program_id: row.program_id,
|
||||
event_kind: row.event_kind,
|
||||
pool_account: row.pool_account,
|
||||
token_a_mint: row.token_a_mint,
|
||||
token_b_mint: row.token_b_mint,
|
||||
event_category: row.event_category,
|
||||
event_lifecycle_kind: row.event_lifecycle_kind,
|
||||
event_actionability: row.event_actionability,
|
||||
trade_candidate: row.trade_candidate.unwrap_or(0) != 0,
|
||||
candle_candidate: row.candle_candidate.unwrap_or(0) != 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,9 @@ pub struct LocalPipelineDiagnosticSummaryDto {
|
||||
pub pair_without_candle_count: i64,
|
||||
/// Diagnostics grouped by DEX.
|
||||
pub dex_summaries: std::vec::Vec<crate::LocalDexDiagnosticSummaryDto>,
|
||||
/// Raydium surface diagnostics derived from the matrix and observed instructions.
|
||||
pub raydium_surface_summaries:
|
||||
std::vec::Vec<crate::LocalRaydiumSurfaceDiagnosticSummaryDto>,
|
||||
/// Diagnostics grouped by pair.
|
||||
pub pair_summaries: std::vec::Vec<crate::LocalPairDiagnosticSummaryDto>,
|
||||
/// Diagnostics grouped by pair materialization/actionability class.
|
||||
@@ -185,6 +188,60 @@ pub struct LocalDexDiagnosticSummaryDto {
|
||||
pub pair_candle_count: i64,
|
||||
}
|
||||
|
||||
/// Local diagnostics for one Raydium surface from the DEX-first matrix.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct LocalRaydiumSurfaceDiagnosticSummaryDto {
|
||||
/// DEX matrix code, such as `raydium_clmm` or `raydium_amm_v4`.
|
||||
pub dex_code: std::string::String,
|
||||
/// Human readable surface name.
|
||||
pub display_name: std::string::String,
|
||||
/// DEX-first role attached to this surface.
|
||||
pub surface_role: std::string::String,
|
||||
/// Program id from the support matrix, when known.
|
||||
pub program_id: std::option::Option<std::string::String>,
|
||||
/// Program id verification status from the support matrix.
|
||||
pub program_id_status: std::string::String,
|
||||
/// Support status from the support matrix.
|
||||
pub status: std::string::String,
|
||||
/// Whether this surface is enabled in the local DEX catalog.
|
||||
pub catalog_enabled: bool,
|
||||
/// Total projected instructions for this program in the current database.
|
||||
pub instruction_count: i64,
|
||||
/// Total distinct projected transactions for this program in the current database.
|
||||
pub transaction_count: i64,
|
||||
/// Total decoded events for this DEX code.
|
||||
pub decoded_event_count: i64,
|
||||
/// Total materialized trade events for this DEX code.
|
||||
pub trade_event_count: i64,
|
||||
/// Total candle buckets for this DEX code.
|
||||
pub pair_candle_count: i64,
|
||||
/// Latest slot where this program was observed, when present.
|
||||
pub latest_slot: std::option::Option<i64>,
|
||||
/// Latest signature where this program was observed, when present.
|
||||
pub latest_signature: std::option::Option<std::string::String>,
|
||||
/// Whether projected instructions prove this program exists in the current corpus.
|
||||
pub observed_in_current_corpus: bool,
|
||||
/// Whether this DEX code has decoded events in the current corpus.
|
||||
pub decoded_in_current_corpus: bool,
|
||||
/// Whether this DEX code has materialized trades in the current corpus.
|
||||
pub trade_materialized_in_current_corpus: bool,
|
||||
}
|
||||
|
||||
/// Projected instruction diagnostics grouped by Raydium program id.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct LocalRaydiumProgramInstructionDiagnosticSummaryDto {
|
||||
/// Program id.
|
||||
pub program_id: std::string::String,
|
||||
/// Total projected instruction rows.
|
||||
pub instruction_count: i64,
|
||||
/// Total distinct transactions.
|
||||
pub transaction_count: i64,
|
||||
/// Latest slot where the program appears, when present.
|
||||
pub latest_slot: std::option::Option<i64>,
|
||||
/// Latest signature where the program appears, when present.
|
||||
pub latest_signature: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
/// Local pair diagnostics summary.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct LocalPairDiagnosticSummaryDto {
|
||||
@@ -537,6 +594,16 @@ pub(crate) struct LocalDexDiagnosticSummaryRow {
|
||||
pub(crate) pair_candle_count: i64,
|
||||
}
|
||||
|
||||
/// SQL row for Raydium program instruction diagnostics.
|
||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||
pub(crate) struct LocalRaydiumProgramInstructionDiagnosticSummaryRow {
|
||||
pub(crate) program_id: std::string::String,
|
||||
pub(crate) instruction_count: i64,
|
||||
pub(crate) transaction_count: i64,
|
||||
pub(crate) latest_slot: std::option::Option<i64>,
|
||||
pub(crate) latest_signature: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
/// SQL row for local pair diagnostics.
|
||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||
pub(crate) struct LocalPairDiagnosticSummaryRow {
|
||||
|
||||
@@ -17,6 +17,7 @@ mod launch_attribution;
|
||||
mod launch_surface;
|
||||
mod launch_surface_key;
|
||||
mod liquidity_event;
|
||||
mod local_dex_corpus_search;
|
||||
mod local_pipeline_diagnostics;
|
||||
mod observed_token;
|
||||
mod onchain_observation;
|
||||
@@ -88,6 +89,10 @@ pub use launch_surface_key::query_launch_surface_keys_list_by_surface_id;
|
||||
pub use launch_surface_key::query_launch_surface_keys_upsert;
|
||||
pub use liquidity_event::query_liquidity_events_list_recent;
|
||||
pub use liquidity_event::query_liquidity_events_upsert;
|
||||
pub use local_dex_corpus_search::query_local_dex_corpus_search_get_summary;
|
||||
pub use local_dex_corpus_search::query_local_dex_corpus_search_list_decoded_event_samples;
|
||||
pub use local_dex_corpus_search::query_local_dex_corpus_search_list_pool_pair_samples;
|
||||
pub use local_dex_corpus_search::query_local_dex_corpus_search_list_transaction_samples;
|
||||
pub use local_pipeline_diagnostics::query_local_decoded_event_diagnostic_list_summaries;
|
||||
pub use local_pipeline_diagnostics::query_local_duplicate_decoded_event_trade_diagnostic_list_samples;
|
||||
pub use local_pipeline_diagnostics::query_local_event_classification_diagnostic_list_summaries;
|
||||
@@ -104,6 +109,7 @@ pub use local_pipeline_diagnostics::query_local_pair_without_trade_diagnostic_li
|
||||
pub use local_pipeline_diagnostics::query_local_pipeline_diagnostic_get_counters;
|
||||
pub use local_pipeline_diagnostics::query_local_pipeline_diagnostic_list_summaries;
|
||||
pub use local_pipeline_diagnostics::query_local_pool_origin_diagnostic_list_samples;
|
||||
pub use local_pipeline_diagnostics::query_local_raydium_program_instruction_diagnostic_list_summaries;
|
||||
pub use local_pipeline_diagnostics::query_local_token_metadata_gap_diagnostic_list_samples;
|
||||
pub use observed_token::query_observed_tokens_get_by_mint;
|
||||
pub use observed_token::query_observed_tokens_list;
|
||||
|
||||
548
kb_lib/src/db/queries/local_dex_corpus_search.rs
Normal file
548
kb_lib/src/db/queries/local_dex_corpus_search.rs
Normal file
@@ -0,0 +1,548 @@
|
||||
// file: kb_lib/src/db/queries/local_dex_corpus_search.rs
|
||||
|
||||
//! Queries for local DEX corpus searches used by Demo3.
|
||||
|
||||
/// Returns aggregate counts for a local DEX corpus search.
|
||||
pub async fn query_local_dex_corpus_search_get_summary(
|
||||
database: &crate::Database,
|
||||
request: &crate::LocalDexCorpusSearchRequestDto,
|
||||
) -> Result<crate::LocalDexCorpusSearchSummaryDto, crate::Error> {
|
||||
match database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
let dex_code = normalized_filter_text(request.dex_code.as_deref());
|
||||
let program_id = normalized_filter_text(request.program_id.as_deref());
|
||||
let pool_address = normalized_filter_text(request.pool_address.as_deref());
|
||||
let token_mint = normalized_filter_text(request.token_mint.as_deref());
|
||||
let signature = normalized_filter_text(request.signature.as_deref());
|
||||
let pool_address_like = like_filter_text(pool_address.as_str());
|
||||
let token_mint_like = like_filter_text(token_mint.as_str());
|
||||
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::db::dtos::LocalDexCorpusSearchSummaryRow>(
|
||||
r#"
|
||||
SELECT
|
||||
COUNT(DISTINCT tx.id) AS transaction_count,
|
||||
COUNT(DISTINCT ix.id) AS instruction_count,
|
||||
COUNT(DISTINCT dde.id) AS decoded_event_count,
|
||||
COUNT(DISTINCT pool.id) AS pool_count,
|
||||
COUNT(DISTINCT pair.id) AS pair_count,
|
||||
COUNT(DISTINCT te.id) AS trade_event_count,
|
||||
COUNT(DISTINCT candle.id) AS pair_candle_count,
|
||||
COUNT(DISTINCT pc.id) AS protocol_candidate_count
|
||||
FROM k_sol_chain_transactions tx
|
||||
LEFT JOIN k_sol_chain_instructions ix
|
||||
ON ix.transaction_id = tx.id
|
||||
LEFT JOIN k_sol_dex_decoded_events dde
|
||||
ON dde.transaction_id = tx.id
|
||||
LEFT JOIN k_sol_pools pool
|
||||
ON pool.address = dde.pool_account
|
||||
LEFT JOIN k_sol_pairs pair
|
||||
ON pair.pool_id = pool.id
|
||||
LEFT JOIN k_sol_dexes dex
|
||||
ON dex.id = pair.dex_id
|
||||
LEFT JOIN k_sol_tokens base_token
|
||||
ON base_token.id = pair.base_token_id
|
||||
LEFT JOIN k_sol_tokens quote_token
|
||||
ON quote_token.id = pair.quote_token_id
|
||||
LEFT JOIN k_sol_trade_events te
|
||||
ON te.transaction_id = tx.id
|
||||
LEFT JOIN k_sol_pair_candles candle
|
||||
ON candle.pair_id = pair.id
|
||||
LEFT JOIN k_sol_protocol_candidates pc
|
||||
ON pc.transaction_id = tx.id
|
||||
WHERE
|
||||
(NULLIF(TRIM(?), '') IS NULL
|
||||
OR dex.code = ?
|
||||
OR dde.protocol_name = ?
|
||||
OR pc.candidate_protocol = ?
|
||||
OR pc.candidate_surface = ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL
|
||||
OR ix.program_id = ?
|
||||
OR dde.program_id = ?
|
||||
OR pc.program_id = ?)
|
||||
AND (? IS NULL OR pair.id = ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL
|
||||
OR pool.address = ?
|
||||
OR dde.pool_account = ?
|
||||
OR ix.accounts_json LIKE ?
|
||||
OR pc.evidence_json LIKE ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL
|
||||
OR base_token.mint = ?
|
||||
OR quote_token.mint = ?
|
||||
OR dde.token_a_mint = ?
|
||||
OR dde.token_b_mint = ?
|
||||
OR ix.accounts_json LIKE ?
|
||||
OR tx.transaction_json LIKE ?
|
||||
OR pc.evidence_json LIKE ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL OR tx.signature = ?)
|
||||
"#,
|
||||
)
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(request.pair_id)
|
||||
.bind(request.pair_id)
|
||||
.bind(pool_address.clone())
|
||||
.bind(pool_address.clone())
|
||||
.bind(pool_address.clone())
|
||||
.bind(pool_address_like.clone())
|
||||
.bind(pool_address_like.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint_like.clone())
|
||||
.bind(token_mint_like.clone())
|
||||
.bind(token_mint_like.clone())
|
||||
.bind(signature.clone())
|
||||
.bind(signature.clone())
|
||||
.fetch_one(pool)
|
||||
.await;
|
||||
let row = match query_result {
|
||||
Ok(row) => row,
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot query local DEX corpus search summary on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
},
|
||||
};
|
||||
return Ok(crate::LocalDexCorpusSearchSummaryDto::from(row));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Lists matching transaction samples for a local DEX corpus search.
|
||||
pub async fn query_local_dex_corpus_search_list_transaction_samples(
|
||||
database: &crate::Database,
|
||||
request: &crate::LocalDexCorpusSearchRequestDto,
|
||||
) -> Result<std::vec::Vec<crate::LocalDexCorpusTransactionSampleDto>, crate::Error> {
|
||||
let limit = normalized_limit(request.limit);
|
||||
if limit == 0 {
|
||||
return Ok(std::vec::Vec::new());
|
||||
}
|
||||
match database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
let dex_code = normalized_filter_text(request.dex_code.as_deref());
|
||||
let program_id = normalized_filter_text(request.program_id.as_deref());
|
||||
let pool_address = normalized_filter_text(request.pool_address.as_deref());
|
||||
let token_mint = normalized_filter_text(request.token_mint.as_deref());
|
||||
let signature = normalized_filter_text(request.signature.as_deref());
|
||||
let pool_address_like = like_filter_text(pool_address.as_str());
|
||||
let token_mint_like = like_filter_text(token_mint.as_str());
|
||||
let query_result = sqlx::query_as::<
|
||||
sqlx::Sqlite,
|
||||
crate::db::dtos::LocalDexCorpusTransactionSampleRow,
|
||||
>(
|
||||
r#"
|
||||
SELECT
|
||||
tx.id AS transaction_id,
|
||||
tx.signature AS signature,
|
||||
tx.slot AS slot,
|
||||
CASE WHEN tx.err_json IS NULL THEN 0 ELSE 1 END AS failed,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM k_sol_chain_instructions count_ix
|
||||
WHERE count_ix.transaction_id = tx.id
|
||||
) AS instruction_count,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM k_sol_dex_decoded_events count_dde
|
||||
WHERE count_dde.transaction_id = tx.id
|
||||
) AS decoded_event_count,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM k_sol_trade_events count_te
|
||||
WHERE count_te.transaction_id = tx.id
|
||||
) AS trade_event_count,
|
||||
COALESCE((
|
||||
SELECT GROUP_CONCAT(DISTINCT program_ix.program_id)
|
||||
FROM k_sol_chain_instructions program_ix
|
||||
WHERE program_ix.transaction_id = tx.id
|
||||
AND program_ix.program_id IS NOT NULL
|
||||
), '') AS program_ids_csv
|
||||
FROM k_sol_chain_transactions tx
|
||||
LEFT JOIN k_sol_chain_instructions ix
|
||||
ON ix.transaction_id = tx.id
|
||||
LEFT JOIN k_sol_dex_decoded_events dde
|
||||
ON dde.transaction_id = tx.id
|
||||
LEFT JOIN k_sol_pools pool
|
||||
ON pool.address = dde.pool_account
|
||||
LEFT JOIN k_sol_pairs pair
|
||||
ON pair.pool_id = pool.id
|
||||
LEFT JOIN k_sol_dexes dex
|
||||
ON dex.id = pair.dex_id
|
||||
LEFT JOIN k_sol_tokens base_token
|
||||
ON base_token.id = pair.base_token_id
|
||||
LEFT JOIN k_sol_tokens quote_token
|
||||
ON quote_token.id = pair.quote_token_id
|
||||
LEFT JOIN k_sol_protocol_candidates pc
|
||||
ON pc.transaction_id = tx.id
|
||||
WHERE
|
||||
(NULLIF(TRIM(?), '') IS NULL
|
||||
OR dex.code = ?
|
||||
OR dde.protocol_name = ?
|
||||
OR pc.candidate_protocol = ?
|
||||
OR pc.candidate_surface = ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL
|
||||
OR ix.program_id = ?
|
||||
OR dde.program_id = ?
|
||||
OR pc.program_id = ?)
|
||||
AND (? IS NULL OR pair.id = ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL
|
||||
OR pool.address = ?
|
||||
OR dde.pool_account = ?
|
||||
OR ix.accounts_json LIKE ?
|
||||
OR pc.evidence_json LIKE ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL
|
||||
OR base_token.mint = ?
|
||||
OR quote_token.mint = ?
|
||||
OR dde.token_a_mint = ?
|
||||
OR dde.token_b_mint = ?
|
||||
OR ix.accounts_json LIKE ?
|
||||
OR tx.transaction_json LIKE ?
|
||||
OR pc.evidence_json LIKE ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL OR tx.signature = ?)
|
||||
GROUP BY tx.id
|
||||
ORDER BY tx.slot DESC, tx.id DESC
|
||||
LIMIT ?
|
||||
"#,
|
||||
)
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(request.pair_id)
|
||||
.bind(request.pair_id)
|
||||
.bind(pool_address.clone())
|
||||
.bind(pool_address.clone())
|
||||
.bind(pool_address.clone())
|
||||
.bind(pool_address_like.clone())
|
||||
.bind(pool_address_like.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint_like.clone())
|
||||
.bind(token_mint_like.clone())
|
||||
.bind(token_mint_like.clone())
|
||||
.bind(signature.clone())
|
||||
.bind(signature.clone())
|
||||
.bind(i64::from(limit))
|
||||
.fetch_all(pool)
|
||||
.await;
|
||||
let rows = match query_result {
|
||||
Ok(rows) => rows,
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot list local DEX corpus transaction samples on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
},
|
||||
};
|
||||
let mut dtos = std::vec::Vec::new();
|
||||
for row in rows {
|
||||
dtos.push(crate::LocalDexCorpusTransactionSampleDto::from(row));
|
||||
}
|
||||
return Ok(dtos);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Lists matching pool/pair samples for a local DEX corpus search.
|
||||
pub async fn query_local_dex_corpus_search_list_pool_pair_samples(
|
||||
database: &crate::Database,
|
||||
request: &crate::LocalDexCorpusSearchRequestDto,
|
||||
) -> Result<std::vec::Vec<crate::LocalDexCorpusPoolPairSampleDto>, crate::Error> {
|
||||
let limit = normalized_limit(request.limit);
|
||||
if limit == 0 {
|
||||
return Ok(std::vec::Vec::new());
|
||||
}
|
||||
match database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
let dex_code = normalized_filter_text(request.dex_code.as_deref());
|
||||
let program_id = normalized_filter_text(request.program_id.as_deref());
|
||||
let pool_address = normalized_filter_text(request.pool_address.as_deref());
|
||||
let token_mint = normalized_filter_text(request.token_mint.as_deref());
|
||||
let signature = normalized_filter_text(request.signature.as_deref());
|
||||
let pool_address_like = like_filter_text(pool_address.as_str());
|
||||
let token_mint_like = like_filter_text(token_mint.as_str());
|
||||
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::db::dtos::LocalDexCorpusPoolPairSampleRow>(
|
||||
r#"
|
||||
SELECT
|
||||
pool.id AS pool_id,
|
||||
pool.address AS pool_address,
|
||||
pair.id AS pair_id,
|
||||
dex.code AS dex_code,
|
||||
pair.symbol AS pair_symbol,
|
||||
base_token.mint AS base_mint,
|
||||
base_token.symbol AS base_symbol,
|
||||
quote_token.mint AS quote_mint,
|
||||
quote_token.symbol AS quote_symbol,
|
||||
COUNT(DISTINCT dde.id) AS decoded_event_count,
|
||||
COUNT(DISTINCT te.id) AS trade_event_count,
|
||||
COUNT(DISTINCT candle.id) AS pair_candle_count,
|
||||
MAX(COALESCE(te.signature, tx.signature, pc.signature)) AS latest_signature
|
||||
FROM k_sol_pools pool
|
||||
LEFT JOIN k_sol_pairs pair
|
||||
ON pair.pool_id = pool.id
|
||||
LEFT JOIN k_sol_dexes dex
|
||||
ON dex.id = pool.dex_id
|
||||
LEFT JOIN k_sol_tokens base_token
|
||||
ON base_token.id = pair.base_token_id
|
||||
LEFT JOIN k_sol_tokens quote_token
|
||||
ON quote_token.id = pair.quote_token_id
|
||||
LEFT JOIN k_sol_dex_decoded_events dde
|
||||
ON dde.pool_account = pool.address
|
||||
LEFT JOIN k_sol_chain_transactions tx
|
||||
ON tx.id = dde.transaction_id
|
||||
LEFT JOIN k_sol_chain_instructions ix
|
||||
ON ix.transaction_id = tx.id
|
||||
LEFT JOIN k_sol_trade_events te
|
||||
ON te.pair_id = pair.id
|
||||
LEFT JOIN k_sol_pair_candles candle
|
||||
ON candle.pair_id = pair.id
|
||||
LEFT JOIN k_sol_protocol_candidates pc
|
||||
ON pc.transaction_id = tx.id
|
||||
WHERE
|
||||
(NULLIF(TRIM(?), '') IS NULL
|
||||
OR dex.code = ?
|
||||
OR dde.protocol_name = ?
|
||||
OR pc.candidate_protocol = ?
|
||||
OR pc.candidate_surface = ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL
|
||||
OR ix.program_id = ?
|
||||
OR dde.program_id = ?
|
||||
OR pc.program_id = ?)
|
||||
AND (? IS NULL OR pair.id = ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL
|
||||
OR pool.address = ?
|
||||
OR dde.pool_account = ?
|
||||
OR ix.accounts_json LIKE ?
|
||||
OR pc.evidence_json LIKE ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL
|
||||
OR base_token.mint = ?
|
||||
OR quote_token.mint = ?
|
||||
OR dde.token_a_mint = ?
|
||||
OR dde.token_b_mint = ?
|
||||
OR ix.accounts_json LIKE ?
|
||||
OR tx.transaction_json LIKE ?
|
||||
OR pc.evidence_json LIKE ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL OR tx.signature = ? OR te.signature = ? OR pc.signature = ?)
|
||||
GROUP BY pool.id, pair.id
|
||||
ORDER BY MAX(COALESCE(tx.slot, te.slot, pc.slot)) DESC, pool.id DESC
|
||||
LIMIT ?
|
||||
"#,
|
||||
)
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(request.pair_id)
|
||||
.bind(request.pair_id)
|
||||
.bind(pool_address.clone())
|
||||
.bind(pool_address.clone())
|
||||
.bind(pool_address.clone())
|
||||
.bind(pool_address_like.clone())
|
||||
.bind(pool_address_like.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint_like.clone())
|
||||
.bind(token_mint_like.clone())
|
||||
.bind(token_mint_like.clone())
|
||||
.bind(signature.clone())
|
||||
.bind(signature.clone())
|
||||
.bind(signature.clone())
|
||||
.bind(signature.clone())
|
||||
.bind(i64::from(limit))
|
||||
.fetch_all(pool)
|
||||
.await;
|
||||
let rows = match query_result {
|
||||
Ok(rows) => rows,
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot list local DEX corpus pool/pair samples on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
},
|
||||
};
|
||||
let mut dtos = std::vec::Vec::new();
|
||||
for row in rows {
|
||||
dtos.push(crate::LocalDexCorpusPoolPairSampleDto::from(row));
|
||||
}
|
||||
return Ok(dtos);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Lists matching decoded-event samples for a local DEX corpus search.
|
||||
pub async fn query_local_dex_corpus_search_list_decoded_event_samples(
|
||||
database: &crate::Database,
|
||||
request: &crate::LocalDexCorpusSearchRequestDto,
|
||||
) -> Result<std::vec::Vec<crate::LocalDexCorpusDecodedEventSampleDto>, crate::Error> {
|
||||
let limit = normalized_limit(request.limit);
|
||||
if limit == 0 {
|
||||
return Ok(std::vec::Vec::new());
|
||||
}
|
||||
match database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
let dex_code = normalized_filter_text(request.dex_code.as_deref());
|
||||
let program_id = normalized_filter_text(request.program_id.as_deref());
|
||||
let pool_address = normalized_filter_text(request.pool_address.as_deref());
|
||||
let token_mint = normalized_filter_text(request.token_mint.as_deref());
|
||||
let signature = normalized_filter_text(request.signature.as_deref());
|
||||
let pool_address_like = like_filter_text(pool_address.as_str());
|
||||
let token_mint_like = like_filter_text(token_mint.as_str());
|
||||
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::db::dtos::LocalDexCorpusDecodedEventSampleRow>(
|
||||
r#"
|
||||
SELECT
|
||||
dde.id AS decoded_event_id,
|
||||
tx.id AS transaction_id,
|
||||
tx.signature AS signature,
|
||||
tx.slot AS slot,
|
||||
dde.protocol_name AS protocol_name,
|
||||
dde.program_id AS program_id,
|
||||
dde.event_kind AS event_kind,
|
||||
dde.pool_account AS pool_account,
|
||||
dde.token_a_mint AS token_a_mint,
|
||||
dde.token_b_mint AS token_b_mint,
|
||||
json_extract(dde.payload_json, '$.eventCategory') AS event_category,
|
||||
json_extract(dde.payload_json, '$.eventLifecycleKind') AS event_lifecycle_kind,
|
||||
json_extract(dde.payload_json, '$.eventActionability') AS event_actionability,
|
||||
json_extract(dde.payload_json, '$.tradeCandidate') AS trade_candidate,
|
||||
json_extract(dde.payload_json, '$.candleCandidate') AS candle_candidate
|
||||
FROM k_sol_dex_decoded_events dde
|
||||
JOIN k_sol_chain_transactions tx
|
||||
ON tx.id = dde.transaction_id
|
||||
LEFT JOIN k_sol_chain_instructions ix
|
||||
ON ix.transaction_id = tx.id
|
||||
LEFT JOIN k_sol_pools pool
|
||||
ON pool.address = dde.pool_account
|
||||
LEFT JOIN k_sol_pairs pair
|
||||
ON pair.pool_id = pool.id
|
||||
LEFT JOIN k_sol_dexes dex
|
||||
ON dex.id = pair.dex_id
|
||||
LEFT JOIN k_sol_tokens base_token
|
||||
ON base_token.id = pair.base_token_id
|
||||
LEFT JOIN k_sol_tokens quote_token
|
||||
ON quote_token.id = pair.quote_token_id
|
||||
LEFT JOIN k_sol_protocol_candidates pc
|
||||
ON pc.transaction_id = tx.id
|
||||
WHERE
|
||||
(NULLIF(TRIM(?), '') IS NULL
|
||||
OR dex.code = ?
|
||||
OR dde.protocol_name = ?
|
||||
OR pc.candidate_protocol = ?
|
||||
OR pc.candidate_surface = ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL
|
||||
OR ix.program_id = ?
|
||||
OR dde.program_id = ?
|
||||
OR pc.program_id = ?)
|
||||
AND (? IS NULL OR pair.id = ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL
|
||||
OR pool.address = ?
|
||||
OR dde.pool_account = ?
|
||||
OR ix.accounts_json LIKE ?
|
||||
OR pc.evidence_json LIKE ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL
|
||||
OR base_token.mint = ?
|
||||
OR quote_token.mint = ?
|
||||
OR dde.token_a_mint = ?
|
||||
OR dde.token_b_mint = ?
|
||||
OR ix.accounts_json LIKE ?
|
||||
OR tx.transaction_json LIKE ?
|
||||
OR pc.evidence_json LIKE ?)
|
||||
AND (NULLIF(TRIM(?), '') IS NULL OR tx.signature = ? OR pc.signature = ?)
|
||||
GROUP BY dde.id
|
||||
ORDER BY tx.slot DESC, dde.id DESC
|
||||
LIMIT ?
|
||||
"#,
|
||||
)
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(dex_code.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(program_id.clone())
|
||||
.bind(request.pair_id)
|
||||
.bind(request.pair_id)
|
||||
.bind(pool_address.clone())
|
||||
.bind(pool_address.clone())
|
||||
.bind(pool_address.clone())
|
||||
.bind(pool_address_like.clone())
|
||||
.bind(pool_address_like.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint.clone())
|
||||
.bind(token_mint_like.clone())
|
||||
.bind(token_mint_like.clone())
|
||||
.bind(token_mint_like.clone())
|
||||
.bind(signature.clone())
|
||||
.bind(signature.clone())
|
||||
.bind(signature.clone())
|
||||
.bind(i64::from(limit))
|
||||
.fetch_all(pool)
|
||||
.await;
|
||||
let rows = match query_result {
|
||||
Ok(rows) => rows,
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot list local DEX corpus decoded-event samples on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
},
|
||||
};
|
||||
let mut dtos = std::vec::Vec::new();
|
||||
for row in rows {
|
||||
dtos.push(crate::LocalDexCorpusDecodedEventSampleDto::from(row));
|
||||
}
|
||||
return Ok(dtos);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn normalized_limit(limit: u32) -> u32 {
|
||||
if limit > 200 {
|
||||
return 200;
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
fn normalized_filter_text(value: std::option::Option<&str>) -> std::string::String {
|
||||
let value = match value {
|
||||
Some(value) => value.trim(),
|
||||
None => return std::string::String::new(),
|
||||
};
|
||||
return value.to_string();
|
||||
}
|
||||
|
||||
fn like_filter_text(value: &str) -> std::string::String {
|
||||
if value.is_empty() {
|
||||
return std::string::String::new();
|
||||
}
|
||||
return format!("%{}%", value);
|
||||
}
|
||||
@@ -584,6 +584,80 @@ ORDER BY dex_code
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Lists observed Raydium program instruction diagnostics.
|
||||
pub async fn query_local_raydium_program_instruction_diagnostic_list_summaries(
|
||||
database: &crate::Database,
|
||||
) -> Result<
|
||||
std::vec::Vec<crate::LocalRaydiumProgramInstructionDiagnosticSummaryDto>,
|
||||
crate::Error,
|
||||
> {
|
||||
match database.connection() {
|
||||
crate::DatabaseConnection::Sqlite(pool) => {
|
||||
let rows_result = sqlx::query_as::<
|
||||
sqlx::Sqlite,
|
||||
crate::db::dtos::LocalRaydiumProgramInstructionDiagnosticSummaryRow,
|
||||
>(
|
||||
r#"
|
||||
SELECT
|
||||
ix.program_id AS program_id,
|
||||
COUNT(ix.id) AS instruction_count,
|
||||
COUNT(DISTINCT tx.signature) AS transaction_count,
|
||||
MAX(tx.slot) AS latest_slot,
|
||||
(
|
||||
SELECT tx_latest.signature
|
||||
FROM k_sol_chain_instructions ix_latest
|
||||
JOIN k_sol_chain_transactions tx_latest
|
||||
ON tx_latest.id = ix_latest.transaction_id
|
||||
WHERE ix_latest.program_id = ix.program_id
|
||||
ORDER BY
|
||||
tx_latest.slot DESC,
|
||||
tx_latest.id DESC,
|
||||
ix_latest.instruction_index DESC,
|
||||
COALESCE(ix_latest.inner_instruction_index, -1) DESC
|
||||
LIMIT 1
|
||||
) AS latest_signature
|
||||
FROM k_sol_chain_instructions ix
|
||||
JOIN k_sol_chain_transactions tx
|
||||
ON tx.id = ix.transaction_id
|
||||
WHERE ix.program_id IN (
|
||||
'CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C',
|
||||
'CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK',
|
||||
'675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8',
|
||||
'5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h',
|
||||
'routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS',
|
||||
'LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj'
|
||||
)
|
||||
GROUP BY ix.program_id
|
||||
ORDER BY transaction_count DESC, instruction_count DESC, program_id
|
||||
"#,
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await;
|
||||
let rows = match rows_result {
|
||||
Ok(rows) => rows,
|
||||
Err(error) => {
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot list local Raydium program instruction diagnostics on sqlite: {}",
|
||||
error
|
||||
)));
|
||||
},
|
||||
};
|
||||
let mut summaries = std::vec::Vec::new();
|
||||
for row in rows {
|
||||
summaries.push(crate::LocalRaydiumProgramInstructionDiagnosticSummaryDto {
|
||||
program_id: row.program_id,
|
||||
instruction_count: row.instruction_count,
|
||||
transaction_count: row.transaction_count,
|
||||
latest_slot: row.latest_slot,
|
||||
latest_signature: row.latest_signature,
|
||||
});
|
||||
}
|
||||
return Ok(summaries);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Lists local pair diagnostic summaries.
|
||||
pub async fn query_local_pair_diagnostic_list_summaries(
|
||||
database: &crate::Database,
|
||||
|
||||
@@ -53,6 +53,8 @@ mod http_pool;
|
||||
mod json_rpc_ws;
|
||||
/// Launch surface attribution service.
|
||||
mod launch_origin;
|
||||
/// Local DEX corpus search service used by Demo3.
|
||||
mod local_dex_corpus_search;
|
||||
/// Local pipeline diagnostics service.
|
||||
mod local_pipeline_diagnostics;
|
||||
/// Local pipeline replay from already persisted raw transaction data.
|
||||
@@ -61,6 +63,8 @@ mod local_pipeline_replay;
|
||||
mod local_pipeline_validation;
|
||||
/// Useful non-trade DEX event materialization service.
|
||||
mod non_trade_event_materialization;
|
||||
/// On-chain DEX pair/pool discovery helpers used by Demo3.
|
||||
mod onchain_dex_pair_discovery;
|
||||
/// Pair analytic signal service.
|
||||
mod pair_analytic_signal;
|
||||
/// Pair-candle aggregation service.
|
||||
@@ -359,6 +363,18 @@ pub use db::LiquidityEventEntity;
|
||||
pub use db::LiquidityEventKind;
|
||||
/// Local decoded-event diagnostics summary.
|
||||
pub use db::LocalDecodedEventDiagnosticSummaryDto;
|
||||
/// Local DEX corpus decoded-event sample for Demo3.
|
||||
pub use db::LocalDexCorpusDecodedEventSampleDto;
|
||||
/// Local DEX corpus pool/pair sample for Demo3.
|
||||
pub use db::LocalDexCorpusPoolPairSampleDto;
|
||||
/// Local DEX corpus search request for Demo3.
|
||||
pub use db::LocalDexCorpusSearchRequestDto;
|
||||
/// Local DEX corpus search result for Demo3.
|
||||
pub use db::LocalDexCorpusSearchResultDto;
|
||||
/// Local DEX corpus search summary for Demo3.
|
||||
pub use db::LocalDexCorpusSearchSummaryDto;
|
||||
/// Local DEX corpus transaction sample for Demo3.
|
||||
pub use db::LocalDexCorpusTransactionSampleDto;
|
||||
/// Local DEX diagnostics summary.
|
||||
pub use db::LocalDexDiagnosticSummaryDto;
|
||||
/// Sample of duplicated trade rows grouped by decoded event id.
|
||||
@@ -389,6 +405,10 @@ pub use db::LocalPipelineDiagnosticCountersDto;
|
||||
pub use db::LocalPipelineDiagnosticSummaryDto;
|
||||
/// Sample of a pool-origin row and optional launch linkage.
|
||||
pub use db::LocalPoolOriginDiagnosticSampleDto;
|
||||
/// Projected instruction diagnostics grouped by Raydium program id.
|
||||
pub use db::LocalRaydiumProgramInstructionDiagnosticSummaryDto;
|
||||
/// Raydium DEX-first surface diagnostics.
|
||||
pub use db::LocalRaydiumSurfaceDiagnosticSummaryDto;
|
||||
/// Prioritized sample of an incomplete token metadata row.
|
||||
pub use db::LocalTokenMetadataGapDiagnosticSampleDto;
|
||||
/// Source family for one on-chain observation.
|
||||
@@ -603,6 +623,14 @@ pub use db::query_liquidity_events_list_recent;
|
||||
pub use db::query_liquidity_events_upsert;
|
||||
/// Lists local decoded-event diagnostic summaries.
|
||||
pub use db::query_local_decoded_event_diagnostic_list_summaries;
|
||||
/// Returns aggregate counts for a local DEX corpus search.
|
||||
pub use db::query_local_dex_corpus_search_get_summary;
|
||||
/// Lists matching decoded-event samples for a local DEX corpus search.
|
||||
pub use db::query_local_dex_corpus_search_list_decoded_event_samples;
|
||||
/// Lists matching pool/pair samples for a local DEX corpus search.
|
||||
pub use db::query_local_dex_corpus_search_list_pool_pair_samples;
|
||||
/// Lists matching transaction samples for a local DEX corpus search.
|
||||
pub use db::query_local_dex_corpus_search_list_transaction_samples;
|
||||
/// Lists samples of duplicated trade rows by decoded event id.
|
||||
pub use db::query_local_duplicate_decoded_event_trade_diagnostic_list_samples;
|
||||
/// Lists local decoded-event classification diagnostic summaries.
|
||||
@@ -633,6 +661,8 @@ pub use db::query_local_pipeline_diagnostic_get_counters;
|
||||
pub use db::query_local_pipeline_diagnostic_list_summaries;
|
||||
/// Lists pool-origin diagnostic samples.
|
||||
pub use db::query_local_pool_origin_diagnostic_list_samples;
|
||||
/// Lists observed Raydium program instruction diagnostics.
|
||||
pub use db::query_local_raydium_program_instruction_diagnostic_list_summaries;
|
||||
/// Lists prioritized token metadata gap diagnostic samples.
|
||||
pub use db::query_local_token_metadata_gap_diagnostic_list_samples;
|
||||
/// Reads one observed token by mint.
|
||||
@@ -1049,6 +1079,8 @@ pub use json_rpc_ws::parse_json_rpc_ws_incoming_value;
|
||||
pub use launch_origin::LaunchAttributionResult;
|
||||
/// Launch surface attribution service.
|
||||
pub use launch_origin::LaunchOriginService;
|
||||
/// Local DEX corpus search service used by Demo3.
|
||||
pub use local_dex_corpus_search::LocalDexCorpusSearchService;
|
||||
/// Local pipeline diagnostics service.
|
||||
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticsService;
|
||||
/// Configuration for a local pipeline replay pass.
|
||||
@@ -1071,6 +1103,18 @@ pub use local_pipeline_validation::LocalPipelineValidationRunDto;
|
||||
pub use local_pipeline_validation::LocalPipelineValidationService;
|
||||
/// Validates a diagnostics summary without performing database access.
|
||||
pub use local_pipeline_validation::validate_local_pipeline_diagnostics_summary;
|
||||
/// Candidate account inferred from generic transaction evidence.
|
||||
pub use onchain_dex_pair_discovery::OnchainDexCandidateAccountDto;
|
||||
/// Candidate transaction/instruction observed on-chain for one DEX program id.
|
||||
pub use onchain_dex_pair_discovery::OnchainDexPairCandidateDto;
|
||||
/// Request for on-chain DEX pair/pool discovery.
|
||||
pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryRequestDto;
|
||||
/// Result of one on-chain DEX pair/pool discovery run.
|
||||
pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryResultDto;
|
||||
/// On-chain pair/pool discovery service.
|
||||
pub use onchain_dex_pair_discovery::OnchainDexPairDiscoveryService;
|
||||
/// Token-balance delta observed in one transaction through Solana transaction metadata.
|
||||
pub use onchain_dex_pair_discovery::OnchainDexTokenBalanceDeltaDto;
|
||||
/// One pair-analytic-signal recording result.
|
||||
pub use pair_analytic_signal::PairAnalyticSignalResult;
|
||||
/// Pair analytic signal service.
|
||||
@@ -1112,6 +1156,8 @@ pub use solana_pubsub_ws::parse_solana_ws_typed_notification;
|
||||
pub use solana_pubsub_ws::parse_solana_ws_typed_notification_from_event;
|
||||
/// One pool-backfill result summary.
|
||||
pub use token_backfill::PoolBackfillResult;
|
||||
/// One signature-backfill result summary.
|
||||
pub use token_backfill::SignatureBackfillResult;
|
||||
/// One token-backfill result summary.
|
||||
pub use token_backfill::TokenBackfillResult;
|
||||
/// Historical token backfill service.
|
||||
|
||||
103
kb_lib/src/local_dex_corpus_search.rs
Normal file
103
kb_lib/src/local_dex_corpus_search.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
// file: kb_lib/src/local_dex_corpus_search.rs
|
||||
|
||||
//! Local DEX corpus search service used by Demo3.
|
||||
|
||||
/// Local DEX corpus search service.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalDexCorpusSearchService {
|
||||
database: std::sync::Arc<crate::Database>,
|
||||
}
|
||||
|
||||
impl LocalDexCorpusSearchService {
|
||||
/// Creates a new local DEX corpus search service.
|
||||
pub fn new(database: std::sync::Arc<crate::Database>) -> Self {
|
||||
return Self { database };
|
||||
}
|
||||
|
||||
/// Searches the local persisted pipeline for DEX corpus candidates.
|
||||
pub async fn search(
|
||||
&self,
|
||||
request: crate::LocalDexCorpusSearchRequestDto,
|
||||
) -> Result<crate::LocalDexCorpusSearchResultDto, crate::Error> {
|
||||
let normalized_request = normalize_request(request);
|
||||
let summary_result = crate::query_local_dex_corpus_search_get_summary(
|
||||
self.database.as_ref(),
|
||||
&normalized_request,
|
||||
)
|
||||
.await;
|
||||
let summary = match summary_result {
|
||||
Ok(summary) => summary,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let transaction_samples_result =
|
||||
crate::query_local_dex_corpus_search_list_transaction_samples(
|
||||
self.database.as_ref(),
|
||||
&normalized_request,
|
||||
)
|
||||
.await;
|
||||
let transaction_samples = match transaction_samples_result {
|
||||
Ok(transaction_samples) => transaction_samples,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let pool_pair_samples_result = crate::query_local_dex_corpus_search_list_pool_pair_samples(
|
||||
self.database.as_ref(),
|
||||
&normalized_request,
|
||||
)
|
||||
.await;
|
||||
let pool_pair_samples = match pool_pair_samples_result {
|
||||
Ok(pool_pair_samples) => pool_pair_samples,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let decoded_event_samples_result =
|
||||
crate::query_local_dex_corpus_search_list_decoded_event_samples(
|
||||
self.database.as_ref(),
|
||||
&normalized_request,
|
||||
)
|
||||
.await;
|
||||
let decoded_event_samples = match decoded_event_samples_result {
|
||||
Ok(decoded_event_samples) => decoded_event_samples,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
return Ok(crate::LocalDexCorpusSearchResultDto {
|
||||
request: normalized_request,
|
||||
summary,
|
||||
transaction_samples,
|
||||
pool_pair_samples,
|
||||
decoded_event_samples,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_request(
|
||||
request: crate::LocalDexCorpusSearchRequestDto,
|
||||
) -> crate::LocalDexCorpusSearchRequestDto {
|
||||
let limit = if request.limit == 0 {
|
||||
50
|
||||
} else if request.limit > 200 {
|
||||
200
|
||||
} else {
|
||||
request.limit
|
||||
};
|
||||
return crate::LocalDexCorpusSearchRequestDto {
|
||||
dex_code: normalize_optional_text(request.dex_code),
|
||||
program_id: normalize_optional_text(request.program_id),
|
||||
pair_id: request.pair_id,
|
||||
pool_address: normalize_optional_text(request.pool_address),
|
||||
token_mint: normalize_optional_text(request.token_mint),
|
||||
signature: normalize_optional_text(request.signature),
|
||||
limit,
|
||||
};
|
||||
}
|
||||
|
||||
fn normalize_optional_text(
|
||||
value: std::option::Option<std::string::String>,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
let value = match value {
|
||||
Some(value) => value.trim().to_string(),
|
||||
None => return None,
|
||||
};
|
||||
if value.is_empty() {
|
||||
return None;
|
||||
}
|
||||
return Some(value);
|
||||
}
|
||||
@@ -29,6 +29,20 @@ impl LocalPipelineDiagnosticsService {
|
||||
Ok(dex_summaries) => dex_summaries,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let raydium_program_instruction_summaries_result =
|
||||
crate::query_local_raydium_program_instruction_diagnostic_list_summaries(
|
||||
self.database.as_ref(),
|
||||
)
|
||||
.await;
|
||||
let raydium_program_instruction_summaries =
|
||||
match raydium_program_instruction_summaries_result {
|
||||
Ok(summaries) => summaries,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let raydium_surface_summaries = build_raydium_surface_summaries(
|
||||
&dex_summaries,
|
||||
&raydium_program_instruction_summaries,
|
||||
);
|
||||
let pair_summaries_result =
|
||||
crate::query_local_pair_diagnostic_list_summaries(self.database.as_ref()).await;
|
||||
let pair_summaries = match pair_summaries_result {
|
||||
@@ -229,6 +243,7 @@ impl LocalPipelineDiagnosticsService {
|
||||
pair_without_trade_count: counters.pair_without_trade_count,
|
||||
pair_without_candle_count: counters.pair_without_candle_count,
|
||||
dex_summaries,
|
||||
raydium_surface_summaries,
|
||||
pair_summaries,
|
||||
pair_actionability_summaries,
|
||||
pair_trading_readiness_summaries,
|
||||
@@ -248,3 +263,99 @@ impl LocalPipelineDiagnosticsService {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn build_raydium_surface_summaries(
|
||||
dex_summaries: &[crate::LocalDexDiagnosticSummaryDto],
|
||||
program_instruction_summaries: &[crate::LocalRaydiumProgramInstructionDiagnosticSummaryDto],
|
||||
) -> std::vec::Vec<crate::LocalRaydiumSurfaceDiagnosticSummaryDto> {
|
||||
let mut summaries = std::vec::Vec::new();
|
||||
for entry in crate::dex_support_matrix_entries() {
|
||||
if entry.family != "raydium" {
|
||||
continue;
|
||||
}
|
||||
let dex_summary = find_dex_summary(dex_summaries, entry.code);
|
||||
let program_summary = match entry.program_id {
|
||||
Some(program_id) => {
|
||||
find_raydium_program_summary(program_instruction_summaries, program_id)
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
let instruction_count = match program_summary {
|
||||
Some(program_summary) => program_summary.instruction_count,
|
||||
None => 0,
|
||||
};
|
||||
let transaction_count = match program_summary {
|
||||
Some(program_summary) => program_summary.transaction_count,
|
||||
None => 0,
|
||||
};
|
||||
let latest_slot = match program_summary {
|
||||
Some(program_summary) => program_summary.latest_slot,
|
||||
None => None,
|
||||
};
|
||||
let latest_signature = match program_summary {
|
||||
Some(program_summary) => program_summary.latest_signature.clone(),
|
||||
None => None,
|
||||
};
|
||||
let decoded_event_count = match dex_summary {
|
||||
Some(dex_summary) => dex_summary.decoded_event_count,
|
||||
None => 0,
|
||||
};
|
||||
let trade_event_count = match dex_summary {
|
||||
Some(dex_summary) => dex_summary.trade_event_count,
|
||||
None => 0,
|
||||
};
|
||||
let pair_candle_count = match dex_summary {
|
||||
Some(dex_summary) => dex_summary.pair_candle_count,
|
||||
None => 0,
|
||||
};
|
||||
summaries.push(crate::LocalRaydiumSurfaceDiagnosticSummaryDto {
|
||||
dex_code: entry.code.to_string(),
|
||||
display_name: entry.display_name.to_string(),
|
||||
surface_role: entry.surface_role.to_string(),
|
||||
program_id: match entry.program_id {
|
||||
Some(program_id) => Some(program_id.to_string()),
|
||||
None => None,
|
||||
},
|
||||
program_id_status: entry.program_id_status.to_string(),
|
||||
status: entry.status.to_string(),
|
||||
catalog_enabled: entry.catalog_enabled,
|
||||
instruction_count,
|
||||
transaction_count,
|
||||
decoded_event_count,
|
||||
trade_event_count,
|
||||
pair_candle_count,
|
||||
latest_slot,
|
||||
latest_signature,
|
||||
observed_in_current_corpus: instruction_count > 0,
|
||||
decoded_in_current_corpus: decoded_event_count > 0,
|
||||
trade_materialized_in_current_corpus: trade_event_count > 0,
|
||||
});
|
||||
}
|
||||
summaries.sort_by(|left, right| return left.dex_code.cmp(&right.dex_code));
|
||||
return summaries;
|
||||
}
|
||||
|
||||
fn find_dex_summary<'a>(
|
||||
dex_summaries: &'a [crate::LocalDexDiagnosticSummaryDto],
|
||||
dex_code: &str,
|
||||
) -> std::option::Option<&'a crate::LocalDexDiagnosticSummaryDto> {
|
||||
for summary in dex_summaries {
|
||||
if summary.dex_code == dex_code {
|
||||
return Some(summary);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn find_raydium_program_summary<'a>(
|
||||
program_instruction_summaries: &'a [crate::LocalRaydiumProgramInstructionDiagnosticSummaryDto],
|
||||
program_id: &str,
|
||||
) -> std::option::Option<&'a crate::LocalRaydiumProgramInstructionDiagnosticSummaryDto> {
|
||||
for summary in program_instruction_summaries {
|
||||
if summary.program_id.as_str() == program_id {
|
||||
return Some(summary);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -290,6 +290,26 @@ impl LocalPipelineValidationConfig {
|
||||
return config;
|
||||
}
|
||||
|
||||
/// Builds the `0.7.40` Raydium effective surfaces validation config.
|
||||
///
|
||||
/// This profile keeps the DEX-first matrix invariants and focuses expected
|
||||
/// corpus warnings on Raydium effective swap surfaces. Missing Raydium
|
||||
/// variants are intentionally warnings so AMM v4 or Stable Swap are not
|
||||
/// promoted without transaction evidence.
|
||||
pub fn v0_7_40_raydium_effective_surfaces() -> Self {
|
||||
let mut config = Self::v0_7_39_dex_first_effective_swap_surfaces();
|
||||
config.profile_code = "0.7.40_raydium_effective_surfaces".to_string();
|
||||
config.expected_dex_codes = vec![
|
||||
"raydium_cpmm".to_string(),
|
||||
"raydium_clmm".to_string(),
|
||||
"raydium_amm_v4".to_string(),
|
||||
"raydium_stable_swap".to_string(),
|
||||
];
|
||||
config.require_all_expected_dexes = false;
|
||||
config.allow_unexpected_dexes = true;
|
||||
return config;
|
||||
}
|
||||
|
||||
/// Builds the legacy `0.7.39` launch-surface validation alias.
|
||||
///
|
||||
/// The implementation now delegates to the DEX-first profile so callers that
|
||||
@@ -534,6 +554,14 @@ impl LocalPipelineValidationService {
|
||||
let config = crate::LocalPipelineValidationConfig::v0_7_39_dex_first_effective_swap_surfaces();
|
||||
return self.validate_current_database(&config).await;
|
||||
}
|
||||
|
||||
/// Diagnoses the current database with the `0.7.40` Raydium profile.
|
||||
pub async fn validate_v0_7_40_current_database(
|
||||
&self,
|
||||
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
|
||||
let config = crate::LocalPipelineValidationConfig::v0_7_40_raydium_effective_surfaces();
|
||||
return self.validate_current_database(&config).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates a diagnostics summary without performing database access.
|
||||
@@ -658,7 +686,8 @@ pub fn validate_local_pipeline_diagnostics_summary(
|
||||
|| config.profile_code == "0.7.37_token_metadata_catalog_enrichment"
|
||||
|| config.profile_code == "0.7.38_token_metadata_gap_prioritization"
|
||||
|| config.profile_code == "0.7.39_dex_first_effective_swap_surfaces"
|
||||
|| config.profile_code == "0.7.39_launch_surface_origin_baseline";
|
||||
|| config.profile_code == "0.7.39_launch_surface_origin_baseline"
|
||||
|| config.profile_code == "0.7.40_raydium_effective_surfaces";
|
||||
if config.require_all_expected_dexes || missing_expected_dex_is_warning {
|
||||
for expected_dex_code in &expected_dex_codes {
|
||||
if !observed_dex_codes.contains(expected_dex_code) {
|
||||
@@ -1083,6 +1112,27 @@ mod tests {
|
||||
pair_candle_count: 131,
|
||||
},
|
||||
],
|
||||
raydium_surface_summaries: vec![
|
||||
crate::LocalRaydiumSurfaceDiagnosticSummaryDto {
|
||||
dex_code: "raydium_clmm".to_string(),
|
||||
display_name: "Raydium CLMM".to_string(),
|
||||
surface_role: "dex_effective".to_string(),
|
||||
program_id: Some(crate::RAYDIUM_CLMM_PROGRAM_ID.to_string()),
|
||||
program_id_status: "known".to_string(),
|
||||
status: "supported".to_string(),
|
||||
catalog_enabled: true,
|
||||
instruction_count: 106,
|
||||
transaction_count: 101,
|
||||
decoded_event_count: 106,
|
||||
trade_event_count: 101,
|
||||
pair_candle_count: 131,
|
||||
latest_slot: Some(1),
|
||||
latest_signature: Some("raydium_clmm_fixture".to_string()),
|
||||
observed_in_current_corpus: true,
|
||||
decoded_in_current_corpus: true,
|
||||
trade_materialized_in_current_corpus: true,
|
||||
},
|
||||
],
|
||||
pair_summaries: vec![],
|
||||
pair_actionability_summaries: vec![
|
||||
crate::LocalPairActionabilityDiagnosticSummaryDto {
|
||||
@@ -1447,6 +1497,18 @@ mod tests {
|
||||
assert_eq!(report.blocking_issue_count, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_accepts_0_7_40_raydium_effective_surfaces_with_missing_variants_as_warnings() {
|
||||
let summary = make_0_7_28_summary_with_meteora();
|
||||
let config = crate::LocalPipelineValidationConfig::v0_7_40_raydium_effective_surfaces();
|
||||
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
|
||||
assert!(report.validation_passed);
|
||||
assert_eq!(report.validation_profile_code, "0.7.40_raydium_effective_surfaces");
|
||||
assert_eq!(report.blocking_issue_count, 0);
|
||||
assert!(report.warning_count >= 1);
|
||||
assert!(report.expected_dex_codes.contains(&"raydium_amm_v4".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_rejects_0_7_33_pair_trading_readiness_mismatch() {
|
||||
let mut summary = make_0_7_28_summary_with_meteora();
|
||||
|
||||
1550
kb_lib/src/onchain_dex_pair_discovery.rs
Normal file
1550
kb_lib/src/onchain_dex_pair_discovery.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -84,6 +84,41 @@ pub struct PoolBackfillResult {
|
||||
pub pair_candle_count: usize,
|
||||
}
|
||||
|
||||
/// One signature-backfill result summary.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct SignatureBackfillResult {
|
||||
/// Input transaction signature.
|
||||
pub signature: std::string::String,
|
||||
/// Number of transactions resolved through HTTP during this run.
|
||||
pub resolved_transaction_count: usize,
|
||||
/// Number of signatures whose `getTransaction` lookup returned `null`.
|
||||
pub missing_transaction_count: usize,
|
||||
/// Total number of decoded DEX events replayed during this run.
|
||||
pub decoded_event_count: usize,
|
||||
/// Total number of DEX detection results produced during this run.
|
||||
pub detection_count: usize,
|
||||
/// Total number of launch-attribution results produced during this run.
|
||||
pub launch_attribution_count: usize,
|
||||
/// Total number of pool-origin results produced during this run.
|
||||
pub pool_origin_count: usize,
|
||||
/// Total number of wallet-participation observations produced during this run.
|
||||
pub wallet_participation_count: usize,
|
||||
/// Total number of trade-aggregation results produced during this run.
|
||||
pub trade_event_count: usize,
|
||||
/// Total number of liquidity event materialization results produced during this run.
|
||||
pub liquidity_event_count: usize,
|
||||
/// Total number of pool lifecycle event materialization results produced during this run.
|
||||
pub pool_lifecycle_event_count: usize,
|
||||
/// Total number of fee event materialization results produced during this run.
|
||||
pub fee_event_count: usize,
|
||||
/// Total number of reward event materialization results produced during this run.
|
||||
pub reward_event_count: usize,
|
||||
/// Total number of pool administration event materialization results produced during this run.
|
||||
pub pool_admin_event_count: usize,
|
||||
/// Total number of pair-candle aggregation results produced during this run.
|
||||
pub pair_candle_count: usize,
|
||||
}
|
||||
|
||||
/// Historical token backfill service.
|
||||
///
|
||||
/// This service reuses the existing transaction projection and downstream
|
||||
@@ -681,6 +716,87 @@ impl TokenBackfillService {
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// Replays one known transaction signature through the existing pipeline.
|
||||
pub async fn backfill_signature(
|
||||
&self,
|
||||
signature: &str,
|
||||
) -> Result<crate::SignatureBackfillResult, crate::Error> {
|
||||
let trimmed_signature = signature.trim().to_string();
|
||||
if trimmed_signature.is_empty() {
|
||||
return Err(crate::Error::Config("signature must not be empty".to_string()));
|
||||
}
|
||||
let replay_result = self.replay_signature(trimmed_signature.clone()).await;
|
||||
let replay = match replay_result {
|
||||
Ok(replay) => replay,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
self.backfill_missing_token_metadata_best_effort(100).await;
|
||||
let result = crate::SignatureBackfillResult {
|
||||
signature: trimmed_signature.clone(),
|
||||
resolved_transaction_count: replay.resolved_transaction_count,
|
||||
missing_transaction_count: replay.missing_transaction_count,
|
||||
decoded_event_count: replay.decoded_event_count,
|
||||
detection_count: replay.detection_count,
|
||||
launch_attribution_count: replay.launch_attribution_count,
|
||||
pool_origin_count: replay.pool_origin_count,
|
||||
wallet_participation_count: replay.wallet_participation_count,
|
||||
trade_event_count: replay.trade_event_count,
|
||||
liquidity_event_count: replay.liquidity_event_count,
|
||||
pool_lifecycle_event_count: replay.pool_lifecycle_event_count,
|
||||
fee_event_count: replay.fee_event_count,
|
||||
reward_event_count: replay.reward_event_count,
|
||||
pool_admin_event_count: replay.pool_admin_event_count,
|
||||
pair_candle_count: replay.pair_candle_count,
|
||||
};
|
||||
let summary_payload = serde_json::json!({
|
||||
"signature": result.signature.clone(),
|
||||
"resolvedTransactionCount": result.resolved_transaction_count,
|
||||
"missingTransactionCount": result.missing_transaction_count,
|
||||
"decodedEventCount": result.decoded_event_count,
|
||||
"detectionCount": result.detection_count,
|
||||
"launchAttributionCount": result.launch_attribution_count,
|
||||
"poolOriginCount": result.pool_origin_count,
|
||||
"walletParticipationCount": result.wallet_participation_count,
|
||||
"tradeEventCount": result.trade_event_count,
|
||||
"liquidityEventCount": result.liquidity_event_count,
|
||||
"poolLifecycleEventCount": result.pool_lifecycle_event_count,
|
||||
"feeEventCount": result.fee_event_count,
|
||||
"rewardEventCount": result.reward_event_count,
|
||||
"poolAdminEventCount": result.pool_admin_event_count,
|
||||
"pairCandleCount": result.pair_candle_count
|
||||
});
|
||||
let observation_result = self
|
||||
.persistence
|
||||
.record_observation(&crate::DetectionObservationInput::new(
|
||||
"signature.backfill.completed".to_string(),
|
||||
crate::ObservationSourceKind::HttpRpc,
|
||||
Some(format!("backfill:{}", self.http_role)),
|
||||
trimmed_signature.clone(),
|
||||
None,
|
||||
summary_payload.clone(),
|
||||
))
|
||||
.await;
|
||||
let observation_id = match observation_result {
|
||||
Ok(observation_id) => observation_id,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let signal_result = self
|
||||
.persistence
|
||||
.record_signal(&crate::DetectionSignalInput::new(
|
||||
"signal.signature.backfill.completed".to_string(),
|
||||
crate::AnalysisSignalSeverity::Low,
|
||||
trimmed_signature,
|
||||
Some(observation_id),
|
||||
None,
|
||||
summary_payload,
|
||||
))
|
||||
.await;
|
||||
if let Err(error) = signal_result {
|
||||
return Err(error);
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
async fn backfill_missing_token_metadata_best_effort(&self, limit: i64) {
|
||||
let metadata_result =
|
||||
self.token_metadata_service.backfill_missing_token_metadata(Some(limit)).await;
|
||||
|
||||
Reference in New Issue
Block a user