// 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(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(id).value, 10); return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; } function escapeHtml(value: string): string { return value .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } 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("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("demo3StatusBadge"); badge.className = `badge ${cssClass}`; badge.textContent = label; } function populatePresetSelect(): void { const select = byId("demo3PresetSelect"); select.innerHTML = ''; 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("demo3DexCodeInput").value = preset.dexCode; byId("demo3ProgramIdInput").value = preset.programId; byId("demo3PresetHelp").textContent = preset.description; } function readOnchainRequest(): Demo3OnchainDexDiscoveryRequest { return { dexCode: valueOrNull(byId("demo3DexCodeInput").value), programId: valueOrNull(byId("demo3ProgramIdInput").value), httpRole: byId("demo3HttpRoleInput").value.trim() || "history_backfill", signatureLimit: intValue("demo3SignatureLimitInput", 50), transactionLimit: intValue("demo3TransactionLimitInput", 25), candidateLimit: intValue("demo3CandidateLimitInput", 25), }; } function readLocalRequest(): Demo3LocalDexCorpusSearchRequest { return { dexCode: valueOrNull(byId("demo3DexCodeInput").value), programId: valueOrNull(byId("demo3ProgramIdInput").value), pairId: numberValueOrNull(byId("demo3PairIdInput").value), poolAddress: valueOrNull(byId("demo3PoolAddressInput").value), tokenMint: valueOrNull(byId("demo3TokenMintInput").value), signature: valueOrNull(byId("demo3SignatureInput").value), limit: intValue("demo3CandidateLimitInput", 25), }; } function clearFilters(): void { byId("demo3DexCodeInput").value = ""; byId("demo3ProgramIdInput").value = ""; byId("demo3PairIdInput").value = ""; byId("demo3PoolAddressInput").value = ""; byId("demo3TokenMintInput").value = ""; byId("demo3SignatureInput").value = ""; byId("demo3PresetSelect").value = ""; byId("demo3PresetHelp").textContent = "Choisis un DEX ou saisis un program id manuellement."; } function renderOnchainResult(result: Demo3OnchainDexDiscoveryResult): void { byId("demo3SummarySignatureCount").textContent = String(result.fetchedSignatureCount); byId("demo3SummaryFetchedTxCount").textContent = String(result.fetchedTransactionCount); byId("demo3SummaryMissingTxCount").textContent = String(result.missingTransactionCount); byId("demo3SummaryFailedTxCount").textContent = String(result.failedTransactionCount); byId("demo3SummaryCandidateCount").textContent = String(result.candidateCount); byId("demo3TargetText").textContent = `${result.resolvedDexCode ?? "custom"} / ${result.resolvedProgramId}`; renderOnchainCandidates(result.candidates); } function renderOnchainCandidates(candidates: Demo3OnchainDexPairCandidate[]): void { const body = byId("demo3OnchainCandidateTableBody"); if (candidates.length === 0) { body.innerHTML = 'No on-chain candidate.'; 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("demo3PoolAddressInput").value = poolForFilter; } if (candidate.tokenAMint !== null && byId("demo3TokenMintInput").value.trim() === "") { byId("demo3TokenMintInput").value = candidate.tokenAMint; } if (candidate.tokenAMint === null && candidate.observedTokenMints.length > 0 && byId("demo3TokenMintInput").value.trim() === "") { byId("demo3TokenMintInput").value = candidate.observedTokenMints[0]; } byId("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 ` ${escapeHtml(shortText(candidate.signature, 18))} ${candidate.slot ?? "-"} ${escapeHtml(candidate.candidateKind)} ${escapeHtml(candidate.confidence)} ${escapeHtml(shortText(verifiedPool, 14))} ${escapeHtml(shortText(candidate.tokenAMint, 14))} ${escapeHtml(shortText(candidate.tokenBMint, 14))} ${escapeHtml(shortList(candidate.observedTokenMints, 3, 10))} ${escapeHtml(tokenDeltaList(candidate))} ${escapeHtml(candidateAccountList(candidate.candidatePoolAccounts, 3))}
vaults: ${escapeHtml(candidateAccountList(candidate.candidateTokenVaultAccounts, 2))} ${escapeHtml(candidate.backfillHint)} `; }).join(""); } function renderLocalResult(result: Demo3LocalDexCorpusSearchResult): void { byId("demo3SummaryLocalPairCount").textContent = String(result.summary.pairCount); const body = byId("demo3LocalPoolPairTableBody"); if (result.poolPairSamples.length === 0) { body.innerHTML = 'No local pool/pair sample.'; return; } body.innerHTML = result.poolPairSamples.map((sample) => ` ${escapeHtml(sample.dexCode ?? "-")} ${escapeHtml(shortText(sample.poolAddress, 16))} ${sample.pairId ?? "-"} ${escapeHtml(sample.pairSymbol ?? "-")} ${escapeHtml(sample.baseSymbol ?? shortText(sample.baseMint, 12))} ${escapeHtml(sample.quoteSymbol ?? shortText(sample.quoteMint, 12))} ${sample.tradeEventCount} ${sample.pairCandleCount} `).join(""); } async function discoverOnchain(): Promise { 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("demo3_discover_onchain_dex_pairs", { request }); lastResultJson = payload.resultJson; byId("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 { const request = readLocalRequest(); setStatus("running", "text-bg-warning"); appendLogLine("local DB search started"); try { const payload = await invoke("demo3_search_local_dex_corpus", { request }); lastResultJson = payload.resultJson; byId("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 { 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('#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('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("demo3PresetSelect").addEventListener("change", (event) => { applyPreset((event.target as HTMLSelectElement).value); }); byId("demo3DiscoverButton").addEventListener("click", () => { void discoverOnchain(); }); byId("demo3LocalSearchButton").addEventListener("click", () => { void searchLocalDb(); }); byId("demo3ClearFiltersButton").addEventListener("click", clearFilters); byId("demo3CopyJsonButton").addEventListener("click", () => { void copyJson(); }); byId("demo3ClearLogButton").addEventListener("click", () => { byId("demo3LogTextarea").value = ""; }); appendLogLine("ready"); } document.addEventListener("DOMContentLoaded", init);