This commit is contained in:
2026-05-20 23:57:15 +02:00
parent fad7ec5107
commit 62831a0abe
56 changed files with 6603 additions and 114 deletions

View File

@@ -8,6 +8,8 @@
"demo_ws",
"demo_http",
"demo_ws_manager",
"demo3old",
"demo3",
"demo_pipeline",
"demo_pipeline2"
],

View 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">
&copy; 2026 SASEDEV — Demo3 On-chain DEX Discovery
</div>
</div>
</div>
</footer>
<script type="module" src="ts/demo3.ts" defer></script>
</body>
</html>

View 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">
&copy; 2026 SASEDEV — Demo3old Local DEX Corpus
</div>
</div>
</div>
</footer>
<script type="module" src="ts/demo3old.ts" defer></script>
</body>
</html>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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>, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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>, };

View 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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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>, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -10,7 +10,7 @@ export type DemoPipeline2BackfillPayload = {
*/
objectKey: string,
/**
* Mode: `tokenMint` or `poolAddress`.
* Mode: `tokenMint`, `poolAddress` or `signature`.
*/
mode: string,
/**

View File

@@ -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, };

View 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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
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);

View 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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
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");
});

View File

@@ -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,

View File

@@ -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();

View File

@@ -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"]}}

View File

@@ -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
View 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
View 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,
};
}

View File

@@ -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(

View File

@@ -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,

View File

@@ -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"
]
}
}
}

View File

@@ -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'))
},