Files
khadhroony-bobobot/kb_demo_app/frontend/ts/demo3.ts
2026-05-31 16:43:19 +02:00

576 lines
40 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/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: "GooseFX V1 (Vybe)", dexCode: "goosefx_v1", programId: "GAMMA7meSFWaBXF25oSUgmGRwaW6sCMFLmBNiMSdbHVT", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "Obric V2 (Vybe)", dexCode: "obric_v2", programId: "obriQD1zbpyLz95G5n7nJe6a4DPjpFwa5XYPoNm113y", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "Ondo Global Market (Vybe)", dexCode: "ondo_global_market", programId: "XzTT4XB8m7sLD2xi6snefSasaswsKCxx5Tifjondogm", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "Scorch (Vybe)", dexCode: "scorch", programId: "SCoRcH8c2dpjvcJD6FiPbCSQyQgu3PcUAWj2Xxx3mqn", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "ZeroFi (Vybe)", dexCode: "zerofi", programId: "ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "Manifest CLOB (Vybe)", dexCode: "manifest_clob", programId: "MNFSTqtC93rEfYHB6hF82sKdZpUDFWkViLByLd1k1Ms", description: "Entrée Vybe orderbook; à vérifier par corpus local." },
{ label: "AlphaQ (Vybe)", dexCode: "alphaq", programId: "ALPHAQmeA7bjrVuccPsYPiCvsi428SNwte66Srvs4pHA", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "Goonfi (Vybe)", dexCode: "goonfi", programId: "goonERTdGsjnkZqWuVjs73BZ3Pb9qoCUdBUL17BnS5j", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "Goonfi V2 (Vybe)", dexCode: "goonfi_v2", programId: "goonuddtQRrWqqn5nFyczVKaie28f3kDkHWkHtURSLE", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "Byreal (Vybe)", dexCode: "byreal", programId: "REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "BisonFi (Vybe)", dexCode: "bisonfi", programId: "BiSoNHVpsVZW2F7rx2eQ59yQwKxzU5NvBcmKshCSUypi", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "FusionAMM (Vybe)", dexCode: "fusionamm", programId: "fUSioN9YKKSa3CUC2YUc4tPkHJ5Y6XW1yz8y6F7qWz9", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "Woofi (Vybe)", dexCode: "woofi", programId: "WooFif76YGRNjk1pA8wCsN67aQsD9f9iLsz4NcJ1AVb", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "Aquifer (Vybe)", dexCode: "aquifer", programId: "AQU1FRd7papthgdrwPTTq5JacJh8YtwEXaBfKU3bTz45", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "Humidifi (Vybe)", dexCode: "humidifi", programId: "9H6tua7jkLhdm3w8BvgpTn5LZNU7g4ZynDmCiNN3q6Rp", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "SolFi V2 (Vybe)", dexCode: "solfi_v2", programId: "SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF", description: "Entrée Vybe; à vérifier par corpus local." },
{ label: "DexLab", dexCode: "dexlab", programId: "DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N", description: "DexLab Swap/Pool." },
{ label: "Aldrin (historical)", dexCode: "aldrin", programId: "AMM55ShdkoGRB5jVYPjWziwk8m5MpwyDgsMWHaMSQWH6", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Aldrin V2 (historical)", dexCode: "aldrin_v2", programId: "CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Crema (historical)", dexCode: "crema", programId: "CLMM9tUoggJu2wagPkkqs9eFG4BWhVBZWkP1qv3Sp7tR", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Cropper (historical)", dexCode: "cropper", programId: "H8W3ctz92svYg6mkn1UtGfu2aQr2fnUFHM1RhScEtQDt", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Lifinity V1 (historical)", dexCode: "lifinity_v1", programId: "EewxydAPCCVuNEyrVN68PuSYdQ7wKn27V9Gjeoi8dy3S", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Lifinity V2 (historical)", dexCode: "lifinity_v2", programId: "2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Mercurial (historical)", dexCode: "mercurial", programId: "MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Orca V1 (historical)", dexCode: "orca_v1", programId: "DjVE6JNiYqPL2QXyCUUh8rNjHrbz9hXHNYt99MQ59qw1", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Orca V2 (historical)", dexCode: "orca_v2", programId: "9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Phoenix (historical)", dexCode: "phoenix", programId: "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Saber (historical)", dexCode: "saber", programId: "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "OpenBook V2 (historical)", dexCode: "openbook_v2", programId: "opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Fox (historical)", dexCode: "fox", programId: "HyhpEq587ANShDdbx1mP4dTmDZC44CXWft29oYQXDb53", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Sanctum Infinity (historical)", dexCode: "sanctum_infinity", programId: "5ocnV1qiCgaQR8Jb8xWnVbApfaygJ8tNoZfgPwsgx9kx", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Saros (historical)", dexCode: "saros", programId: "SSwapUtytfBdBn1b9NUGG6foMVPtcWgpRU32HToDUZr", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Perps (historical)", dexCode: "perps", programId: "PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "StepN (historical)", dexCode: "stepn", programId: "Dooar9JkhdZ7J3LHN3A7YCuoGRUggXhQaG4kijfLGU2j", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Solayer (historical)", dexCode: "solayer", programId: "endoLNCKTqDn8gSVnN2hDdpgACUPWHZTwoYnnMybpAT", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Penguin (historical)", dexCode: "penguin", programId: "PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Sanctum (historical)", dexCode: "sanctum", programId: "stkitrT1Uoy18Dk1fTrgPw8W6MVzoCfYoAFT4MLsmhq", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Guacswap (historical)", dexCode: "guacswap", programId: "Gswppe6ERWKpUTXvRPfXdzHhiCyJvLadVvXGfdpBqcE1", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Oasis (historical)", dexCode: "oasis", programId: "9tKE7Mbmj4mxDjWatikzGAtkoWosiiZX9y6J4Hfm2R8H", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Saber Decimals (historical)", dexCode: "saber_decimals", programId: "DecZY86MU5Gj7kppfUCEmd4LbXXuyZH1yHaP2NTqdiZB", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Stabble Stable Swap (historical)", dexCode: "stabble_stable_swap", programId: "swapNyd8XiQwJ6ianp9snpu4brUqFxadzvHebnAXjJZ", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Stabble Weighted Swap (historical)", dexCode: "stabble_weighted_swap", programId: "swapFpHZwjELNnjvThjajtiVmkz3yPQEHjLtka2fwHW", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "1DEX (historical)", dexCode: "one_dex", programId: "DEXYosS6oEGvk8uCDayvwEZz4qEyDJRf9nFgYCaqPMTm", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "SolFi (historical)", dexCode: "solfi", programId: "SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Moonshot (historical)", dexCode: "moonshot", programId: "MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Bonkswap (historical)", dexCode: "bonkswap", programId: "BSwp6bEBihVLdqJRKGgzjcGLHkcTuzmSo1TQkHepzH8p", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Invariant (historical)", dexCode: "invariant", programId: "HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Token Swap (historical)", dexCode: "token_swap", programId: "SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Helium Network (historical)", dexCode: "helium_network", programId: "treaf4wWBBty3fHdyBpo35Mz84M8k3heKXmjmi9vFt5", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Marinade Liquid Staking (historical)", dexCode: "marinade_liquid_staking", programId: "MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Francium Yield Pools (historical)", dexCode: "francium_yield_pools", programId: "FC81tbGt6JWRXidaWYFXxGnTk4VgobhJHATvTRVMqgWj", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Marinade Governance (historical)", dexCode: "marinade_governance", programId: "GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Serum DAO (historical)", dexCode: "serum_dao", programId: "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Port Finance (historical)", dexCode: "port_finance", programId: "Port7uDYB3wk6GJAw4KT1WpTeMtSu9bTcChBHkX2LfR", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Solend Classic (historical)", dexCode: "solend_classic", programId: "So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Hyperspace NFT AMM (historical)", dexCode: "hyperspace_nft_amm", programId: "HYPERfwdTjyJ2SCaKHmpF2MtrXqWxrsotYDsTrshHWq8", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Magic Eden NFT AMM (historical)", dexCode: "magic_eden_nft_amm", programId: "MEisE1HzehtrDpAAT8PnLHjpSSkRYakotTuJRPjTpo8", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Raydium Staking Early (historical)", dexCode: "raydium_staking_early", programId: "EhhTKczWMGQt46ynNeRX1WfeagwwJd7ufHvCDjRxjo5Q", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Orca Aquafarm V1 (historical)", dexCode: "orca_aquafarm_v1", programId: "82yxjeMsvaURa4MbZZ7WZZHfobirZYkH1zF8fmeGtyaQ", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Quarry Merge Mining (historical)", dexCode: "quarry_merge_mining", programId: "QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB", description: "Program id historique importé depuis entities.py; à vérifier par corpus." },
{ label: "Printr", dexCode: "printr", programId: "T8HsGYv7sMk3kTnyaRqZrbRPuntYzdh12evXBkprint", description: "Candidat Printr; à vérifier par corpus local." },
{ label: "metaDAO — umbrella (manual)", dexCode: "metadao", programId: "", description: "Umbrella MetaDAO. Choisir de préférence une surface spécifique ci-dessous." },
{ label: "metaDAO Launchpad v0.7.0", dexCode: "metadao_launchpad_v0_7_0", programId: "moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM", description: "Programme MetaDAO officiel candidat; à vérifier par corpus local." },
{ label: "metaDAO Bid Wall v0.7.0", dexCode: "metadao_bid_wall_v0_7_0", programId: "WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx", description: "Programme MetaDAO officiel candidat; à vérifier par corpus local." },
{ label: "metaDAO Futarchy v0.6.0", dexCode: "metadao_futarchy_v0_6_0", programId: "FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq", description: "Programme MetaDAO officiel candidat; à vérifier par corpus local." },
{ label: "metaDAO AMM v0.5.0", dexCode: "metadao_amm_v0_5_0", programId: "AMMJdEiCCa8mdugg6JPF7gFirmmxisTfDJoSNSUi5zDJ", description: "Programme MetaDAO officiel candidat; à vérifier par corpus local." },
];
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 invalidBase58Characters(value: string): string[] {
const invalid = new Set<string>();
for (const character of value.trim()) {
if (!/^[1-9A-HJ-NP-Za-km-z]$/.test(character)) {
invalid.add(character);
}
}
return Array.from(invalid);
}
function isSolanaAddressLike(value: string): boolean {
const trimmed = value.trim();
if (trimmed.length < 32 || trimmed.length > 44) {
return false;
}
return /^[1-9A-HJ-NP-Za-km-z]+$/.test(trimmed);
}
function splitSourceAddresses(value: string): string[] {
const seen = new Set<string>();
const addresses: string[] = [];
for (const token of value.split(/[\s,;]+/g)) {
const trimmed = token.trim();
if (trimmed === "" || seen.has(trimmed)) {
continue;
}
seen.add(trimmed);
addresses.push(trimmed);
}
return addresses;
}
function isSolanaSignatureLike(value: string): boolean {
const trimmed = value.trim();
if (trimmed.length < 64 || trimmed.length > 128) {
return false;
}
return /^[1-9A-HJ-NP-Za-km-z]+$/.test(trimmed);
}
function validateOptionalSignature(value: string | null, label: string): void {
if (value === null || value.trim() === "") {
return;
}
if (!isSolanaSignatureLike(value)) {
throw new Error(`${label} must be a valid Solana transaction signature.`);
}
}
function validateOnchainRequest(request: Demo3OnchainDexDiscoveryRequest): void {
const addresses = request.sourceAddresses ?? [];
if (request.signatureSource === "address" && addresses.length === 0) {
throw new Error("Signature source is 'address': provide at least one Solana account, pool, vault, position, config or mint address.");
}
for (const address of addresses) {
if (!isSolanaAddressLike(address)) {
throw new Error(`Invalid source address '${address}'. Provide Solana account addresses separated by commas, spaces or new lines.`);
}
}
validateOptionalSignature(request.beforeSignature, "Before signature");
validateOptionalSignature(request.untilSignature, "Until signature");
if (request.programId !== null && !isSolanaAddressLike(request.programId)) {
const invalidCharacters = invalidBase58Characters(request.programId);
if (invalidCharacters.length > 0) {
throw new Error(`Program id filter must be a valid Solana base58 program id. Invalid character(s): ${invalidCharacters.join(", ")}.`);
}
throw new Error("Program id filter must be a valid Solana program id, or empty when using a preset that resolves it.");
}
}
function numberValueOrNull(value: string): number | null {
const trimmed = value.trim();
if (trimmed === "") {
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 selectedTargetEvents(): string[] {
return Array.from(document.querySelectorAll<HTMLInputElement>('input[name="demo3TargetEventInput"]:checked'))
.map((input) => input.value.trim())
.filter((value) => value !== "");
}
function readTargetEventFilter(): string | null {
const selected = selectedTargetEvents();
return selected.length === 0 ? null : selected.join(",");
}
function targetEventLabel(targetEvent: string | null): string {
return targetEvent === null || targetEvent.trim() === "" ? "any" : targetEvent;
}
function clearTargetEventFilters(): void {
document.querySelectorAll<HTMLInputElement>('input[name="demo3TargetEventInput"]').forEach((input) => {
input.checked = false;
});
}
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 {
const sourceAddresses = splitSourceAddresses(byId<HTMLTextAreaElement>("demo3SourceAddressInput").value);
return {
dexCode: valueOrNull(byId<HTMLInputElement>("demo3DexCodeInput").value),
programId: valueOrNull(byId<HTMLInputElement>("demo3ProgramIdInput").value),
signatureSource: valueOrNull(byId<HTMLSelectElement>("demo3SignatureSourceSelect").value),
sourceAddress: sourceAddresses.length === 1 ? sourceAddresses[0] : null,
sourceAddresses,
beforeSignature: valueOrNull(byId<HTMLInputElement>("demo3BeforeSignatureInput").value),
untilSignature: valueOrNull(byId<HTMLInputElement>("demo3UntilSignatureInput").value),
maxPages: intValue("demo3MaxPagesInput", 1),
scanOrder: valueOrNull(byId<HTMLSelectElement>("demo3ScanOrderSelect").value),
targetEvent: readTargetEventFilter(),
excludeSwaps: byId<HTMLInputElement>("demo3ExcludeSwapsInput").checked,
includeFailed: byId<HTMLInputElement>("demo3IncludeFailedInput").checked,
httpRole: byId<HTMLInputElement>("demo3HttpRoleInput").value.trim() || "history_backfill",
signatureLimit: intValue("demo3SignatureLimitInput", 50),
transactionLimit: intValue("demo3TransactionLimitInput", 25),
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<HTMLSelectElement>("demo3SignatureSourceSelect").value = "program_id";
byId<HTMLTextAreaElement>("demo3SourceAddressInput").value = "";
byId<HTMLInputElement>("demo3BeforeSignatureInput").value = "";
byId<HTMLInputElement>("demo3UntilSignatureInput").value = "";
byId<HTMLInputElement>("demo3MaxPagesInput").value = "1";
byId<HTMLSelectElement>("demo3ScanOrderSelect").value = "newest_first";
byId<HTMLInputElement>("demo3PairIdInput").value = "";
byId<HTMLInputElement>("demo3PoolAddressInput").value = "";
byId<HTMLInputElement>("demo3TokenMintInput").value = "";
byId<HTMLInputElement>("demo3SignatureInput").value = "";
clearTargetEventFilters();
byId<HTMLInputElement>("demo3ExcludeSwapsInput").checked = false;
byId<HTMLInputElement>("demo3IncludeFailedInput").checked = true;
byId<HTMLSelectElement>("demo3PresetSelect").value = "";
byId<HTMLElement>("demo3PresetHelp").textContent = "Choisis un DEX ou saisis un program id manuellement.";
}
function renderOnchainResult(result: Demo3OnchainDexDiscoveryResult): void {
byId<HTMLElement>("demo3SummarySignatureCount").textContent = String(result.fetchedSignatureCount);
byId<HTMLElement>("demo3SummaryUniqueFetchedSignatureCount").textContent = String(result.uniqueFetchedSignatureCount);
byId<HTMLElement>("demo3SummaryFetchedPageCount").textContent = String(result.fetchedSignaturePageCount);
byId<HTMLElement>("demo3SummaryUniqueSignatureCount").textContent = String(result.uniqueSignatureCount);
byId<HTMLElement>("demo3SummaryFetchedTxCount").textContent = String(result.fetchedTransactionCount);
byId<HTMLElement>("demo3SummaryMissingTxCount").textContent = String(result.missingTransactionCount);
byId<HTMLElement>("demo3SummaryFailedTxCount").textContent = String(result.failedTransactionCount);
byId<HTMLElement>("demo3SummarySkippedFailedTxCount").textContent = String(result.skippedFailedTransactionCount);
byId<HTMLElement>("demo3SummarySkippedSwapTxCount").textContent = String(result.skippedSwapLogTransactionCount);
byId<HTMLElement>("demo3SummaryExtractedCandidateCount").textContent = String(result.extractedCandidateCount);
byId<HTMLElement>("demo3SummaryRejectedCandidateCount").textContent = String(result.targetRejectedCandidateCount);
byId<HTMLElement>("demo3SummaryCandidateCount").textContent = String(result.candidateCount);
const targetEvent = targetEventLabel(result.request.targetEvent);
const sourceText = result.resolvedSignatureAddresses.length === 0 ? result.resolvedSignatureAddress : result.resolvedSignatureAddresses.join(",");
byId<HTMLElement>("demo3TargetText").textContent = `${result.resolvedDexCode ?? "custom"} / program=${result.resolvedProgramId} / source=${result.resolvedSignatureSource}:${sourceText} / target=${targetEvent} / order=${result.request.scanOrder ?? "newest_first"}`;
byId<HTMLElement>("demo3UniqueSignatureText").textContent = result.uniqueBackfillSignatures.length === 0 ? "-" : result.uniqueBackfillSignatures.join(", ");
byId<HTMLElement>("demo3NextBeforeText").textContent = result.nextBeforeByAddress.length === 0 ? "-" : result.nextBeforeByAddress.map((cursor) => `${cursor.address}:${cursor.nextBeforeSignature ?? "-"}`).join(" | ");
renderRejectedSummary(result);
renderOnchainCandidates(result.candidates);
}
function renderRejectedSummary(result: Demo3OnchainDexDiscoveryResult): void {
const body = byId<HTMLTableSectionElement>("demo3RejectedSummaryTableBody");
if (result.rejectedCandidateSummary.length === 0) {
body.innerHTML = '<tr><td colspan="5" class="text-body-secondary">No rejected candidate summary.</td></tr>';
return;
}
body.innerHTML = result.rejectedCandidateSummary.map((summary) => `
<tr>
<td>${escapeHtml(summary.candidateKind)}</td>
<td class="font-monospace" title="${escapeHtml(summary.instructionDataPrefix ?? "")}">${escapeHtml(shortText(summary.instructionDataPrefix, 16))}</td>
<td>${escapeHtml(summary.instructionName ?? "-")}</td>
<td>${escapeHtml(summary.rejectionReason)}</td>
<td>${summary.count}</td>
</tr>`).join("");
}
function renderOnchainCandidates(candidates: Demo3OnchainDexPairCandidate[]): void {
const body = byId<HTMLTableSectionElement>("demo3OnchainCandidateTableBody");
if (candidates.length === 0) {
body.innerHTML = '<tr><td colspan="12" 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(candidate.instructionDataPrefix ?? "")}">${escapeHtml(shortText(candidate.instructionDataPrefix, 14))}</td>
<td class="font-monospace" title="${escapeHtml(verifiedPool ?? "")}">${escapeHtml(shortText(verifiedPool, 14))}</td>
<td class="font-monospace" title="${escapeHtml(candidate.tokenAMint ?? "")}">${escapeHtml(shortText(candidate.tokenAMint, 14))}</td>
<td class="font-monospace" title="${escapeHtml(candidate.tokenBMint ?? "")}">${escapeHtml(shortText(candidate.tokenBMint, 14))}</td>
<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();
try {
validateOnchainRequest(request);
} catch (error) {
setStatus("error", "text-bg-danger");
appendLogLine(`on-chain discovery rejected locally: ${String(error)}`);
return;
}
setStatus("running", "text-bg-warning");
appendLogLine(`on-chain discovery dex='${request.dexCode ?? ""}' program='${request.programId ?? ""}' source='${request.signatureSource ?? "program_id"}:${request.sourceAddresses.join(",")}' target='${targetEventLabel(request.targetEvent)}' pages='${request.maxPages}' order='${request.scanOrder ?? "newest_first"}' before='${request.beforeSignature ?? ""}' until='${request.untilSignature ?? ""}' excludeSwaps='${request.excludeSwaps}' role='${request.httpRole}'`);
try {
const payload = await invoke<Demo3OnchainDexDiscoveryPayload>("demo3_discover_onchain_dex_pairs", { request });
lastResultJson = payload.resultJson;
byId<HTMLTextAreaElement>("demo3JsonTextarea").value = payload.resultJson;
renderOnchainResult(payload.result);
setStatus("ok", "text-bg-success");
appendLogLine(`on-chain discovery completed: candidates='${payload.result.candidateCount}' unique='${payload.result.uniqueSignatureCount}' signatures='${payload.result.fetchedSignatureCount}' uniqueFetched='${payload.result.uniqueFetchedSignatureCount}' pages='${payload.result.fetchedSignaturePageCount}' extracted='${payload.result.extractedCandidateCount}' rejected='${payload.result.targetRejectedCandidateCount}' skippedSwapTx='${payload.result.skippedSwapLogTransactionCount}'`);
} catch (error) {
setStatus("error", "text-bg-danger");
appendLogLine(`on-chain discovery failed: ${String(error)}`);
}
}
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);