0.7.43-E5C

This commit is contained in:
2026-05-27 11:28:36 +02:00
parent 69c8f6c957
commit d9558a5c16
28 changed files with 4451 additions and 325 deletions

View File

@@ -24,7 +24,7 @@
<main class="app-main">
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
<div class="container-fluid py-4">
<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">
@@ -37,24 +37,59 @@
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>
<label for="demo3ProgramIdInput" class="form-label">Program id filter</label>
<input id="demo3ProgramIdInput" type="text" class="form-control font-monospace" spellcheck="false" />
<div class="form-text">Used to filter matched instructions. With address source, signatures are fetched from Source address instead.</div>
</div>
<div class="row g-2 mb-3">
<div class="col-6">
<label for="demo3SignatureSourceSelect" class="form-label">Signature source</label>
<select id="demo3SignatureSourceSelect" class="form-select">
<option value="program_id">program_id</option>
<option value="address">address / pool / vault / position</option>
</select>
</div>
<div class="col-6">
<label for="demo3SourceAddressInput" class="form-label">Source address</label>
<input id="demo3SourceAddressInput" type="text" class="form-control font-monospace" spellcheck="false" placeholder="pool / vault / position / config / mint address" />
</div>
<div class="col-12 form-text">Use address source to discover signatures around a pool, vault, position, config or mint while keeping the program id filter.</div>
</div>
<div class="row g-2">
<div class="col-12">
<label for="demo3TargetEventSelect" class="form-label">Target event</label>
<select id="demo3TargetEventSelect" class="form-select">
<option value="">Any / generic pair-pool discovery</option>
<option value="swap">swap</option>
<option value="add_liquidity">add_liquidity</option>
<option value="remove_liquidity">remove_liquidity</option>
<option value="claim_fee">claim_fee / collect_fee</option>
<option value="claim_reward">claim_reward</option>
<option value="position_open">position_open</option>
<option value="position_close">position_close</option>
<option value="pool_create">pool_create / initialize_pool</option>
<option value="pool_admin">pool_admin / config / authority</option>
<option value="unknown_non_swap">unknown_non_swap</option>
<option value="audit_non_swap_like">audit_non_swap_like</option>
<option value="unclassified_instruction">unclassified_instruction</option>
</select>
<div class="form-text">Use this to find corpus signatures for non-swap decoders without promoting unverified events.</div>
</div>
<div class="col-6">
<label for="demo3HttpRoleInput" class="form-label">HTTP role</label>
<input id="demo3HttpRoleInput" type="text" class="form-control" value="history_backfill" />
@@ -71,8 +106,20 @@
<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 class="col-6">
<div class="form-check mt-2">
<input id="demo3ExcludeSwapsInput" class="form-check-input" type="checkbox" />
<label for="demo3ExcludeSwapsInput" class="form-check-label">Exclude tx with swap logs</label>
</div>
</div>
<div class="col-6">
<div class="form-check mt-2">
<input id="demo3IncludeFailedInput" class="form-check-input" type="checkbox" checked />
<label for="demo3IncludeFailedInput" class="form-check-label">Include failed tx</label>
</div>
</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>
@@ -81,7 +128,7 @@
</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>
@@ -103,15 +150,20 @@
</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>Unique candidates:</strong> <span id="demo3SummaryUniqueSignatureCount">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>Skipped failed:</strong> <span id="demo3SummarySkippedFailedTxCount">0</span></div>
<div class="col-6"><strong>Skipped swap tx:</strong> <span id="demo3SummarySkippedSwapTxCount">0</span></div>
<div class="col-6"><strong>Extracted:</strong> <span id="demo3SummaryExtractedCandidateCount">0</span></div>
<div class="col-6"><strong>Rejected by target:</strong> <span id="demo3SummaryRejectedCandidateCount">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>
@@ -120,9 +172,13 @@
<strong>Target:</strong>
<span id="demo3TargetText" class="font-monospace">-</span>
</div>
<div class="small text-body-secondary mt-2">
<strong>Backfill signatures:</strong>
<span id="demo3UniqueSignatureText" 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>
@@ -131,7 +187,7 @@
</div>
</div>
</div>
<div class="col-12 col-xxl-8">
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
@@ -144,6 +200,7 @@
<th>Slot</th>
<th>Kind</th>
<th>Confidence</th>
<th>Data prefix</th>
<th>Verified pool</th>
<th>Token A</th>
<th>Token B</th>
@@ -154,13 +211,39 @@
</tr>
</thead>
<tbody id="demo3OnchainCandidateTableBody">
<tr><td colspan="11" class="text-body-secondary">No on-chain candidate.</td></tr>
<tr>
<td colspan="12" 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">Rejected candidate summary</h2>
<div class="table-responsive">
<table class="table table-sm align-middle mb-0">
<thead>
<tr>
<th>Kind</th>
<th>Prefix</th>
<th>Instruction</th>
<th>Reason</th>
<th>Count</th>
</tr>
</thead>
<tbody id="demo3RejectedSummaryTableBody">
<tr>
<td colspan="5" class="text-body-secondary">No rejected candidate summary.</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>
@@ -179,13 +262,15 @@
</tr>
</thead>
<tbody id="demo3LocalPoolPairTableBody">
<tr><td colspan="8" class="text-body-secondary">No local pool/pair sample.</td></tr>
<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>
@@ -211,4 +296,4 @@
<script type="module" src="ts/demo3.ts" defer></script>
</body>
</html>
</html>

View File

@@ -179,7 +179,8 @@
<div class="mb-3">
<label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label>
<select id="demoPipeline2ValidationProfileSelect" class="form-select">
<option value="0.7.42_raydium_family_event_coverage" selected>0.7.42Raydium family event coverage</option>
<option value="0.7.43_meteora_effective_surfaces" selected>0.7.43Meteora family event coverage</option>
<option value="0.7.42_raydium_family_event_coverage">0.7.42 — Raydium family event coverage</option>
<option value="0.7.41_raydium_amm_v4_swap_decoder">0.7.41 — Raydium AMM v4 swap decoder</option>
<option value="0.7.40_raydium_effective_surfaces">0.7.40 — Raydium effective surfaces</option>
<option value="0.7.39_dex_first_effective_swap_surfaces">0.7.39 — DEX-first effective swap surfaces</option>

View File

@@ -12,6 +12,26 @@ dexCode: string | null,
* Optional Solana program id. When absent, dex_code must resolve to a verified program id.
*/
programId: string | null,
/**
* Optional signature source: `program_id` or `address`.
*/
signatureSource: string | null,
/**
* Optional source address used when signature_source is `address`.
*/
sourceAddress: string | null,
/**
* Optional target event family used to find non-swap signatures.
*/
targetEvent: string | null,
/**
* Whether transactions containing swap-like logs should be skipped.
*/
excludeSwaps: boolean,
/**
* Whether failed transactions should be returned as candidates.
*/
includeFailed: boolean,
/**
* HTTP role used to query Solana RPC.
*/

View File

@@ -1,6 +1,7 @@
// 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";
import type { Demo3OnchainDexRejectedCandidateSummary } from "./Demo3OnchainDexRejectedCandidateSummary";
/**
* Structured on-chain DEX discovery result.
@@ -15,9 +16,29 @@ request: Demo3OnchainDexDiscoveryRequest,
*/
resolvedDexCode: string | null,
/**
* Program id scanned with getSignaturesForAddress.
* Program id used to filter matched instructions.
*/
resolvedProgramId: string,
/**
* Signature source actually used by getSignaturesForAddress.
*/
resolvedSignatureSource: string,
/**
* Address scanned with getSignaturesForAddress.
*/
resolvedSignatureAddress: string,
/**
* Number of unique candidate signatures.
*/
uniqueSignatureCount: number,
/**
* Unique signatures ready for signature backfill.
*/
uniqueBackfillSignatures: Array<string>,
/**
* Rejected candidate summary.
*/
rejectedCandidateSummary: Array<Demo3OnchainDexRejectedCandidateSummary>,
/**
* Number of signatures returned by Solana RPC.
*/
@@ -34,6 +55,22 @@ missingTransactionCount: number,
* Number of failed transactions encountered.
*/
failedTransactionCount: number,
/**
* Number of failed transactions skipped because include_failed is false.
*/
skippedFailedTransactionCount: number,
/**
* Number of swap-log transactions skipped by the transaction-level swap guard.
*/
skippedSwapLogTransactionCount: number,
/**
* Number of candidate rows extracted before target-event filtering.
*/
extractedCandidateCount: number,
/**
* Number of candidate rows rejected by target-event filtering.
*/
targetRejectedCandidateCount: number,
/**
* Number of candidate rows returned.
*/

View File

@@ -50,6 +50,10 @@ innerInstructionIndex: number | null,
* Instruction name inferred from data/logs.
*/
instructionName: string | null,
/**
* Prefix of the raw base58 instruction data, useful for audit grouping.
*/
instructionDataPrefix: string | null,
/**
* Candidate pool address.
*/

View File

@@ -0,0 +1,26 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* Rejected on-chain discovery candidate summary.
*/
export type Demo3OnchainDexRejectedCandidateSummary = {
/**
* Candidate kind rejected by target filtering.
*/
candidateKind: string,
/**
* Optional instruction data prefix.
*/
instructionDataPrefix: string | null,
/**
* Optional instruction name.
*/
instructionName: string | null,
/**
* Rejection reason.
*/
rejectionReason: string,
/**
* Count of matching rejected candidates.
*/
count: number, };

View File

@@ -7,11 +7,11 @@ 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";
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;
@@ -37,6 +37,59 @@ const presets: Demo3Preset[] = [
{ 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: "Aldrin (historical)", dexCode: "aldrin", programId: "AMM55ShdkoGRB5jVYPjWziwk8m5MpwyDgsMWHaMSQWH6", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Aldrin V2 (historical)", dexCode: "aldrin_v2", programId: "CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Crema (historical)", dexCode: "crema", programId: "CLMM9tUoggJu2wagPkkqs9eFG4BWhVBZWkP1qv3Sp7tR", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Cropper (historical)", dexCode: "cropper", programId: "H8W3ctz92svYg6mkn1UtGfu2aQr2fnUFHM1RhScEtQDt", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Lifinity V1 (historical)", dexCode: "lifinity_v1", programId: "EewxydAPCCVuNEyrVN68PuSYdQ7wKn27V9Gjeoi8dy3S", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Lifinity V2 (historical)", dexCode: "lifinity_v2", programId: "2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Mercurial (historical)", dexCode: "mercurial", programId: "MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Orca V1 (historical)", dexCode: "orca_v1", programId: "DjVE6JNiYqPL2QXyCUUh8rNjHrbz9hXHNYt99MQ59qw1", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Orca V2 (historical)", dexCode: "orca_v2", programId: "9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Phoenix (historical)", dexCode: "phoenix", programId: "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Saber (historical)", dexCode: "saber", programId: "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "OpenBook V2 (historical)", dexCode: "openbook_v2", programId: "opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "OpenBook (historical)", dexCode: "openbook", programId: "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Fox (historical)", dexCode: "fox", programId: "HyhpEq587ANShDdbx1mP4dTmDZC44CXWft29oYQXDb53", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Sanctum Infinity (historical)", dexCode: "sanctum_infinity", programId: "5ocnV1qiCgaQR8Jb8xWnVbApfaygJ8tNoZfgPwsgx9kx", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Saros (historical)", dexCode: "saros", programId: "SSwapUtytfBdBn1b9NUGG6foMVPtcWgpRU32HToDUZr", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Perps (historical)", dexCode: "perps", programId: "PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "StepN (historical)", dexCode: "stepn", programId: "Dooar9JkhdZ7J3LHN3A7YCuoGRUggXhQaG4kijfLGU2j", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Solayer (historical)", dexCode: "solayer", programId: "endoLNCKTqDn8gSVnN2hDdpgACUPWHZTwoYnnMybpAT", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Penguin (historical)", dexCode: "penguin", programId: "PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Sanctum (historical)", dexCode: "sanctum", programId: "stkitrT1Uoy18Dk1fTrgPw8W6MVzoCfYoAFT4MLsmhq", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Guacswap (historical)", dexCode: "guacswap", programId: "Gswppe6ERWKpUTXvRPfXdzHhiCyJvLadVvXGfdpBqcE1", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Oasis (historical)", dexCode: "oasis", programId: "9tKE7Mbmj4mxDjWatikzGAtkoWosiiZX9y6J4Hfm2R8H", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Saber Decimals (historical)", dexCode: "saber_decimals", programId: "DecZY86MU5Gj7kppfUCEmd4LbXXuyZH1yHaP2NTqdiZB", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Stabble Stable Swap (historical)", dexCode: "stabble_stable_swap", programId: "swapNyd8XiQwJ6ianp9snpu4brUqFxadzvHebnAXjJZ", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Stabble Weighted Swap (historical)", dexCode: "stabble_weighted_swap", programId: "swapFpHZwjELNnjvThjajtiVmkz3yPQEHjLtka2fwHW", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "1DEX (historical)", dexCode: "one_dex", programId: "DEXYosS6oEGvk8uCDayvwEZz4qEyDJRf9nFgYCaqPMTm", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "SolFi (historical)", dexCode: "solfi", programId: "SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Moonshot (historical)", dexCode: "moonshot", programId: "MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Bonkswap (historical)", dexCode: "bonkswap", programId: "BSwp6bEBihVLdqJRKGgzjcGLHkcTuzmSo1TQkHepzH8p", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Invariant (historical)", dexCode: "invariant", programId: "HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Token Swap (historical)", dexCode: "token_swap", programId: "SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Helium Network (historical)", dexCode: "helium_network", programId: "treaf4wWBBty3fHdyBpo35Mz84M8k3heKXmjmi9vFt5", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Raydium Legacy V2 (historical)", dexCode: "raydium_legacy_v2", programId: "27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Raydium Legacy V3 (historical)", dexCode: "raydium_legacy_v3", programId: "7quYqsZdpWSZ3qgDextersDqoKjZy7aCgwHBBfRb7KPt", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Serum V3 (historical)", dexCode: "serum_v3", programId: "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Mango Private Pools (historical)", dexCode: "mango_private_pools", programId: "AtdP2iyfh6xBGwVZzHvY73E7uKKkZBTH2siHh3ZuEf1P", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Marinade Liquid Staking (historical)", dexCode: "marinade_liquid_staking", programId: "MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Step Finance Pools (historical)", dexCode: "step_finance_pools", programId: "StepAscQoEioFxxWGnh2sLBDFp9d8rvKz2Yp39iDpyT", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Francium Yield Pools (historical)", dexCode: "francium_yield_pools", programId: "FC81tbGt6JWRXidaWYFXxGnTk4VgobhJHATvTRVMqgWj", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Cropper Legacy (historical)", dexCode: "cropper_legacy", programId: "CyZuD7RPDcrqCGbNvzrNVs1zpCQehqp7SuXB7rdFKSzo", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Dexlab Beta (historical)", dexCode: "dexlab_beta", programId: "9qvG1zP8ZzY1sTnExx7mkyxhW1063YHVYZxMYz2HkM4m", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Marinade Governance (historical)", dexCode: "marinade_governance", programId: "GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Serum DAO (historical)", dexCode: "serum_dao", programId: "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Port Finance (historical)", dexCode: "port_finance", programId: "Port7uDYB3wk6GJAw4KT1WpTeMtSu9bTcChBHkX2LfR", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Solend Classic (historical)", dexCode: "solend_classic", programId: "So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Hyperspace NFT AMM (historical)", dexCode: "hyperspace_nft_amm", programId: "HYPERfwdTjyJ2SCaKHmpF2MtrXqWxrsotYDsTrshHWq8", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Magic Eden NFT AMM (historical)", dexCode: "magic_eden_nft_amm", programId: "MEisE1HzehtrDpAAT8PnLHjpSSkRYakotTuJRPjTpo8", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Raydium Staking Early (historical)", dexCode: "raydium_staking_early", programId: "EhhTKczWMGQt46ynNeRX1WfeagwwJd7ufHvCDjRxjo5Q", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Orca Aquafarm V1 (historical)", dexCode: "orca_aquafarm_v1", programId: "82yxjeMsvaURa4MbZZ7WZZHfobirZYkH1zF8fmeGtyaQ", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Quarry Merge Mining (historical)", dexCode: "quarry_merge_mining", programId: "QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "SWAB Finance Beta (historical)", dexCode: "swab_finance_beta", programId: "SWABxNGyxEBVoNRGn6RvYBt5UqercSE5PBHuJeYXYHq", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "CRAFT Early (historical)", dexCode: "craft_early", programId: "CrAFTUv7zKXBaS5471aBiARBu6x7nP4rDzr8xwBewbr1", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ 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é." },
];
@@ -56,6 +109,26 @@ function valueOrNull(value: string): string | null {
return trimmed === "" ? null : trimmed;
}
function isSolanaAddressLike(value: string): boolean {
const trimmed = value.trim();
if (trimmed.length < 32 || trimmed.length > 44) {
return false;
}
return /^[1-9A-HJ-NP-Za-km-z]+$/.test(trimmed);
}
function validateOnchainRequest(request: Demo3OnchainDexDiscoveryRequest): void {
if (request.signatureSource === "address") {
const sourceAddress = request.sourceAddress ?? "";
if (!isSolanaAddressLike(sourceAddress)) {
throw new Error("Signature source is 'address': Source address must be a real Solana account address, pool, vault, position, config or mint. It cannot be empty or the literal value 'address'.");
}
}
if (request.programId !== null && !isSolanaAddressLike(request.programId)) {
throw new Error("Program id filter must be a valid Solana program id, or empty when using a preset that resolves it.");
}
}
function numberValueOrNull(value: string): number | null {
const trimmed = value.trim();
if (trimmed === "") {
@@ -157,6 +230,11 @@ function readOnchainRequest(): Demo3OnchainDexDiscoveryRequest {
return {
dexCode: valueOrNull(byId<HTMLInputElement>("demo3DexCodeInput").value),
programId: valueOrNull(byId<HTMLInputElement>("demo3ProgramIdInput").value),
signatureSource: valueOrNull(byId<HTMLSelectElement>("demo3SignatureSourceSelect").value),
sourceAddress: valueOrNull(byId<HTMLInputElement>("demo3SourceAddressInput").value),
targetEvent: valueOrNull(byId<HTMLSelectElement>("demo3TargetEventSelect").value),
excludeSwaps: byId<HTMLInputElement>("demo3ExcludeSwapsInput").checked,
includeFailed: byId<HTMLInputElement>("demo3IncludeFailedInput").checked,
httpRole: byId<HTMLInputElement>("demo3HttpRoleInput").value.trim() || "history_backfill",
signatureLimit: intValue("demo3SignatureLimitInput", 50),
transactionLimit: intValue("demo3TransactionLimitInput", 25),
@@ -179,28 +257,57 @@ function readLocalRequest(): Demo3LocalDexCorpusSearchRequest {
function clearFilters(): void {
byId<HTMLInputElement>("demo3DexCodeInput").value = "";
byId<HTMLInputElement>("demo3ProgramIdInput").value = "";
byId<HTMLSelectElement>("demo3SignatureSourceSelect").value = "program_id";
byId<HTMLInputElement>("demo3SourceAddressInput").value = "";
byId<HTMLInputElement>("demo3PairIdInput").value = "";
byId<HTMLInputElement>("demo3PoolAddressInput").value = "";
byId<HTMLInputElement>("demo3TokenMintInput").value = "";
byId<HTMLInputElement>("demo3SignatureInput").value = "";
byId<HTMLSelectElement>("demo3TargetEventSelect").value = "";
byId<HTMLInputElement>("demo3ExcludeSwapsInput").checked = false;
byId<HTMLInputElement>("demo3IncludeFailedInput").checked = true;
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>("demo3SummaryUniqueSignatureCount").textContent = String(result.uniqueSignatureCount);
byId<HTMLElement>("demo3SummaryFetchedTxCount").textContent = String(result.fetchedTransactionCount);
byId<HTMLElement>("demo3SummaryMissingTxCount").textContent = String(result.missingTransactionCount);
byId<HTMLElement>("demo3SummaryFailedTxCount").textContent = String(result.failedTransactionCount);
byId<HTMLElement>("demo3SummarySkippedFailedTxCount").textContent = String(result.skippedFailedTransactionCount);
byId<HTMLElement>("demo3SummarySkippedSwapTxCount").textContent = String(result.skippedSwapLogTransactionCount);
byId<HTMLElement>("demo3SummaryExtractedCandidateCount").textContent = String(result.extractedCandidateCount);
byId<HTMLElement>("demo3SummaryRejectedCandidateCount").textContent = String(result.targetRejectedCandidateCount);
byId<HTMLElement>("demo3SummaryCandidateCount").textContent = String(result.candidateCount);
byId<HTMLElement>("demo3TargetText").textContent = `${result.resolvedDexCode ?? "custom"} / ${result.resolvedProgramId}`;
const targetEvent = result.request.targetEvent ?? "any";
byId<HTMLElement>("demo3TargetText").textContent = `${result.resolvedDexCode ?? "custom"} / program=${result.resolvedProgramId} / source=${result.resolvedSignatureSource}:${result.resolvedSignatureAddress} / target=${targetEvent}`;
byId<HTMLElement>("demo3UniqueSignatureText").textContent = result.uniqueBackfillSignatures.length === 0 ? "-" : result.uniqueBackfillSignatures.join(", ");
renderRejectedSummary(result);
renderOnchainCandidates(result.candidates);
}
function renderRejectedSummary(result: Demo3OnchainDexDiscoveryResult): void {
const body = byId<HTMLTableSectionElement>("demo3RejectedSummaryTableBody");
if (result.rejectedCandidateSummary.length === 0) {
body.innerHTML = '<tr><td colspan="5" class="text-body-secondary">No rejected candidate summary.</td></tr>';
return;
}
body.innerHTML = result.rejectedCandidateSummary.map((summary) => `
<tr>
<td>${escapeHtml(summary.candidateKind)}</td>
<td class="font-monospace" title="${escapeHtml(summary.instructionDataPrefix ?? "")}">${escapeHtml(shortText(summary.instructionDataPrefix, 16))}</td>
<td>${escapeHtml(summary.instructionName ?? "-")}</td>
<td>${escapeHtml(summary.rejectionReason)}</td>
<td>${summary.count}</td>
</tr>`).join("");
}
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>';
body.innerHTML = '<tr><td colspan="12" class="text-body-secondary">No on-chain candidate.</td></tr>';
return;
}
body.innerHTML = candidates.map((candidate) => {
@@ -225,6 +332,7 @@ function renderOnchainCandidates(candidates: Demo3OnchainDexPairCandidate[]): vo
<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(candidate.instructionDataPrefix ?? "")}">${escapeHtml(shortText(candidate.instructionDataPrefix, 14))}</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>
@@ -258,15 +366,22 @@ function renderLocalResult(result: Demo3LocalDexCorpusSearchResult): void {
async function discoverOnchain(): Promise<void> {
const request = readOnchainRequest();
try {
validateOnchainRequest(request);
} catch (error) {
setStatus("error", "text-bg-danger");
appendLogLine(`on-chain discovery rejected locally: ${String(error)}`);
return;
}
setStatus("running", "text-bg-warning");
appendLogLine(`on-chain discovery dex='${request.dexCode ?? ""}' program='${request.programId ?? ""}' role='${request.httpRole}'`);
appendLogLine(`on-chain discovery dex='${request.dexCode ?? ""}' program='${request.programId ?? ""}' source='${request.signatureSource ?? "program_id"}:${request.sourceAddress ?? ""}' target='${request.targetEvent ?? "any"}' excludeSwaps='${request.excludeSwaps}' 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}'`);
appendLogLine(`on-chain discovery completed: candidates='${payload.result.candidateCount}' unique='${payload.result.uniqueSignatureCount}' signatures='${payload.result.fetchedSignatureCount}' extracted='${payload.result.extractedCandidateCount}' rejected='${payload.result.targetRejectedCandidateCount}' skippedSwapTx='${payload.result.skippedSwapLogTransactionCount}'`);
} catch (error) {
setStatus("error", "text-bg-danger");
appendLogLine(`on-chain discovery failed: ${String(error)}`);

View File

@@ -1,7 +1,7 @@
{
"name": "kb-demo-app",
"private": true,
"version": "0.7.42",
"version": "0.7.43",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -431,6 +431,16 @@ pub(crate) struct Demo3OnchainDexDiscoveryRequest {
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>,
/// Optional signature source: `program_id` or `address`.
pub signature_source: std::option::Option<std::string::String>,
/// Optional source address used when signature_source is `address`.
pub source_address: std::option::Option<std::string::String>,
/// Optional target event family used to find non-swap signatures.
pub target_event: std::option::Option<std::string::String>,
/// Whether transactions containing swap-like logs should be skipped.
pub exclude_swaps: bool,
/// Whether failed transactions should be returned as candidates.
pub include_failed: bool,
/// HTTP role used to query Solana RPC.
pub http_role: std::string::String,
/// Maximum number of signatures to inspect.
@@ -463,8 +473,19 @@ pub(crate) struct Demo3OnchainDexDiscoveryResult {
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.
/// Program id used to filter matched instructions.
pub resolved_program_id: std::string::String,
/// Signature source actually used by getSignaturesForAddress.
pub resolved_signature_source: std::string::String,
/// Address scanned with getSignaturesForAddress.
pub resolved_signature_address: std::string::String,
/// Number of unique candidate signatures.
#[ts(type = "number")]
pub unique_signature_count: usize,
/// Unique signatures ready for signature backfill.
pub unique_backfill_signatures: std::vec::Vec<std::string::String>,
/// Rejected candidate summary.
pub rejected_candidate_summary: std::vec::Vec<Demo3OnchainDexRejectedCandidateSummary>,
/// Number of signatures returned by Solana RPC.
#[ts(type = "number")]
pub fetched_signature_count: usize,
@@ -477,6 +498,18 @@ pub(crate) struct Demo3OnchainDexDiscoveryResult {
/// Number of failed transactions encountered.
#[ts(type = "number")]
pub failed_transaction_count: usize,
/// Number of failed transactions skipped because include_failed is false.
#[ts(type = "number")]
pub skipped_failed_transaction_count: usize,
/// Number of swap-log transactions skipped by the transaction-level swap guard.
#[ts(type = "number")]
pub skipped_swap_log_transaction_count: usize,
/// Number of candidate rows extracted before target-event filtering.
#[ts(type = "number")]
pub extracted_candidate_count: usize,
/// Number of candidate rows rejected by target-event filtering.
#[ts(type = "number")]
pub target_rejected_candidate_count: usize,
/// Number of candidate rows returned.
#[ts(type = "number")]
pub candidate_count: usize,
@@ -484,6 +517,27 @@ pub(crate) struct Demo3OnchainDexDiscoveryResult {
pub candidates: std::vec::Vec<Demo3OnchainDexPairCandidate>,
}
/// Rejected on-chain discovery candidate summary.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/Demo3OnchainDexRejectedCandidateSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3OnchainDexRejectedCandidateSummary {
/// Candidate kind rejected by target filtering.
pub candidate_kind: std::string::String,
/// Optional instruction data prefix.
pub instruction_data_prefix: std::option::Option<std::string::String>,
/// Optional instruction name.
pub instruction_name: std::option::Option<std::string::String>,
/// Rejection reason.
pub rejection_reason: std::string::String,
/// Count of matching rejected candidates.
#[ts(type = "number")]
pub count: usize,
}
/// 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")]
@@ -515,6 +569,8 @@ pub(crate) struct Demo3OnchainDexPairCandidate {
pub inner_instruction_index: std::option::Option<i64>,
/// Instruction name inferred from data/logs.
pub instruction_name: std::option::Option<std::string::String>,
/// Prefix of the raw base58 instruction data, useful for audit grouping.
pub instruction_data_prefix: std::option::Option<std::string::String>,
/// Candidate pool address.
pub pool_address: std::option::Option<std::string::String>,
/// Candidate token A mint.
@@ -627,6 +683,11 @@ fn to_lib_onchain_request(
return kb_lib::OnchainDexPairDiscoveryRequestDto {
dex_code: normalize_optional_text(request.dex_code.clone()),
program_id: normalize_optional_text(request.program_id.clone()),
signature_source: normalize_optional_text(request.signature_source.clone()),
source_address: normalize_optional_text(request.source_address.clone()),
target_event: normalize_optional_text(request.target_event.clone()),
exclude_swaps: request.exclude_swaps,
include_failed: request.include_failed,
http_role: request.http_role.trim().to_string(),
signature_limit: request.signature_limit,
transaction_limit: request.transaction_limit,
@@ -645,6 +706,11 @@ fn from_lib_onchain_result(
request: Demo3OnchainDexDiscoveryRequest {
dex_code: result.request.dex_code,
program_id: result.request.program_id,
signature_source: result.request.signature_source,
source_address: result.request.source_address,
target_event: result.request.target_event,
exclude_swaps: result.request.exclude_swaps,
include_failed: result.request.include_failed,
http_role: result.request.http_role,
signature_limit: result.request.signature_limit,
transaction_limit: result.request.transaction_limit,
@@ -652,15 +718,42 @@ fn from_lib_onchain_result(
},
resolved_dex_code: result.resolved_dex_code,
resolved_program_id: result.resolved_program_id,
resolved_signature_source: result.resolved_signature_source,
resolved_signature_address: result.resolved_signature_address,
unique_signature_count: result.unique_signature_count,
unique_backfill_signatures: result.unique_backfill_signatures,
rejected_candidate_summary: from_lib_rejected_candidate_summary(
result.rejected_candidate_summary,
),
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,
skipped_failed_transaction_count: result.skipped_failed_transaction_count,
skipped_swap_log_transaction_count: result.skipped_swap_log_transaction_count,
extracted_candidate_count: result.extracted_candidate_count,
target_rejected_candidate_count: result.target_rejected_candidate_count,
candidate_count: result.candidate_count,
candidates,
};
}
fn from_lib_rejected_candidate_summary(
values: std::vec::Vec<kb_lib::OnchainDexRejectedCandidateSummaryDto>,
) -> std::vec::Vec<Demo3OnchainDexRejectedCandidateSummary> {
let mut mapped = std::vec::Vec::new();
for value in values {
mapped.push(Demo3OnchainDexRejectedCandidateSummary {
candidate_kind: value.candidate_kind,
instruction_data_prefix: value.instruction_data_prefix,
instruction_name: value.instruction_name,
rejection_reason: value.rejection_reason,
count: value.count,
});
}
return mapped;
}
fn from_lib_onchain_candidate(
candidate: kb_lib::OnchainDexPairCandidateDto,
) -> Demo3OnchainDexPairCandidate {
@@ -676,6 +769,7 @@ fn from_lib_onchain_candidate(
instruction_index: candidate.instruction_index,
inner_instruction_index: candidate.inner_instruction_index,
instruction_name: candidate.instruction_name,
instruction_data_prefix: candidate.instruction_data_prefix,
pool_address: candidate.pool_address,
token_a_mint: candidate.token_a_mint,
token_b_mint: candidate.token_b_mint,

View File

@@ -1279,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.42_raydium_family_event_coverage".to_string(),
None => "0.7.43_meteora_effective_surfaces".to_string(),
};
let run_result = match profile_code.as_str() {
"0.7.27" | "0.7.27_dexes_non_regression" => {
@@ -1332,6 +1332,9 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
"0.7.42" | "0.7.42_raydium_family_event_coverage" => {
service.validate_v0_7_42_current_database().await
},
"0.7.43" | "0.7.43_meteora_effective_surfaces" => {
service.validate_v0_7_43_current_database().await
},
other => Err(kb_lib::Error::InvalidState(format!(
"unsupported local pipeline validation profile: {other}"
))),

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "kb-demo-app",
"version": "0.7.42",
"version": "0.7.43",
"identifier": "com.sasedev.kb-demo-app",
"build": {
"beforeDevCommand": "npm run dev",