Files
khadhroony-bobobot/kb_demo_app/frontend/ts/demo3old.ts
2026-05-20 23:57:15 +02:00

436 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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");
});