Files
khadhroony-bot3/kb-app-demo-desktop/frontend/ts/demo_execution_spl.ts

1144 lines
56 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-app-demo-desktop/frontend/ts/demo_execution_spl.ts
// version: 13
import * as bootstrap from "bootstrap";
import "simplebar";
import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { frontendDebug, frontendError, frontendWarn, installFrontendConsoleBridge } from "./frontend_log";
import type { DemoExecutionDevnetStoreReadinessPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionDevnetStoreReadinessPayload.ts";
import type { DemoExecutionSolanaCoreGeneratedRecipientPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreGeneratedRecipientPayload.ts";
import type { DemoExecutionSolanaCoreOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreOptionsPayload.ts";
import type { DemoExecutionSolanaCoreProfileOption } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreProfileOption.ts";
import type { DemoExecutionSolanaCoreProgressPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreProgressPayload.ts";
import type { DemoExecutionSolanaCoreRequest } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreRequest.ts";
import type { DemoExecutionSolanaCoreSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreSummaryPayload.ts";
import type { DemoExecutionMemoRequest } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionMemoRequest.ts";
import type { DemoExecutionMemoSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionMemoSummaryPayload.ts";
import type { DemoExecutionSplTokenRequest } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoExecutionSplTokenRequest.ts";
import type { DemoExecutionSplTokenSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoExecutionSplTokenSummaryPayload.ts";
import type { DemoSplTokenJournalRequest } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalRequest.ts";
import type { DemoSplTokenJournalPagePayload } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalPagePayload.ts";
import type { DemoSplTokenJournalRow } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalRow.ts";
import type { DemoExecutionSplAtaRequest } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoExecutionSplAtaRequest.ts";
import type { DemoExecutionSplAtaSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoExecutionSplAtaSummaryPayload.ts";
import type { DemoSplAtaDerivationPayload } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationPayload.ts";
import type { DemoSplAtaDerivationRequest } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationRequest.ts";
import type { DemoSplAtaJournalRequest } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRequest.ts";
import type { DemoSplAtaJournalPagePayload } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalPagePayload.ts";
import {
applyStoreBlockMetadata,
createStoreBlockState,
cursorForBlock,
markStoreBlockFiltersDirty,
renderStoreBlockControls,
resetStoreBlockState,
type StoreBlockControls,
} from "./demo_store_block_pagination";
import type { DemoExecutionSplToken2022Request } from "./bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoExecutionSplToken2022Request.ts";
import type { DemoExecutionSplToken2022SummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoExecutionSplToken2022SummaryPayload.ts";
import type { DemoSplToken2022FixturePayload } from "./bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoSplToken2022FixturePayload.ts";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
const logLines: string[] = [];
const maximumLogLines = 1200;
let running = false;
let profileStoreReady = false;
let profileOptions: DemoExecutionSolanaCoreProfileOption[] = [];
let token_2022Fixture: DemoSplToken2022FixturePayload | null = null;
const tokenJournalBlockState = createStoreBlockState();
const ataJournalBlockState = createStoreBlockState();
const tokenJournalBlockControls: StoreBlockControls = {
firstSelector: "#tokenJournalFirstBlockButton",
previousSelector: "#tokenJournalPreviousBlockButton",
nextSelector: "#tokenJournalNextBlockButton",
summarySelector: "#tokenJournalBlockSummary",
};
const ataJournalBlockControls: StoreBlockControls = {
firstSelector: "#ataJournalFirstBlockButton",
previousSelector: "#ataJournalPreviousBlockButton",
nextSelector: "#ataJournalNextBlockButton",
summarySelector: "#ataJournalBlockSummary",
};
type TokenJournalBaseRequest = Omit<DemoSplTokenJournalRequest, "cursor" | "blockIndex">;
type AtaJournalBaseRequest = Omit<DemoSplAtaJournalRequest, "cursor" | "blockIndex">;
let tokenJournalBaseRequest: TokenJournalBaseRequest | null = null;
let ataJournalBaseRequest: AtaJournalBaseRequest | null = null;
function element<T extends HTMLElement>(selector: string): T {
const value = document.querySelector<T>(selector);
if (!value) {
throw new Error(`Missing UI element: ${selector}`);
}
return value;
}
function integerValue(selector: string): number {
const value = Number.parseInt(element<HTMLInputElement>(selector).value, 10);
if (!Number.isFinite(value) || value < 0) {
throw new Error(`Valeur numérique invalide pour ${selector}`);
}
return value;
}
function parsedJson(value: string): unknown {
try {
return JSON.parse(value) as unknown;
} catch {
return { raw: value };
}
}
function writePanel(selector: string, title: string, payload: Record<string, unknown>): void {
element<HTMLTextAreaElement>(selector).value = JSON.stringify({ title, ...payload }, null, 2);
}
function writeJsonPanel(selector: string, title: string, value: string): void {
const parsed = parsedJson(value);
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
writePanel(selector, title, parsed as Record<string, unknown>);
return;
}
writePanel(selector, title, { value: parsed });
}
function writePlanPanel(preflightJson: string | null, planJson: string): void {
const payload: Record<string, unknown> = { plan: parsedJson(planJson) };
if (preflightJson !== null) {
payload.preflight = parsedJson(preflightJson);
}
writePanel("#executionPlanOutput", "Plan exact", payload);
}
function writeExecutionState(status: string, message: string, error: string | null = null): void {
const summary: Record<string, unknown> = { status, message };
if (error !== null) {
summary.error = error;
}
writePanel("#executionSummaryOutput", "Résumé", summary);
writePanel("#executionPlanOutput", "Plan exact", {
status: status === "running" ? "pending" : "unavailable",
reason: status,
message: status === "running" ? "Préparation du plan en cours." : "Aucun plan exécutable na été produit.",
...(error === null ? {} : { error }),
});
writePanel("#executionSimulationOutput", "Simulation exacte", {
status: status === "running" ? "pending" : "not_started",
reason: status,
message: status === "running" ? "Simulation en attente du plan exact." : "La simulation na pas été lancée.",
...(error === null ? {} : { error }),
});
writePanel("#executionDiagnosticsOutput", "Confirmation et replay", {
status: status === "running" ? "pending" : "not_started",
reason: status,
message: status === "running" ? "En attente de la simulation et dun éventuel envoi." : "La confirmation et le replay nont pas été exécutés.",
...(error === null ? {} : { error }),
});
}
function appendLog(payload: DemoExecutionSolanaCoreProgressPayload | { timestamp: string; level: string; stage: string; message: string; signature?: string | null }): void {
const signature = payload.signature ? ` signature=${payload.signature}` : "";
logLines.push(`${payload.timestamp} ${payload.level.toUpperCase()} [${payload.stage}] ${payload.message}${signature}`);
while (logLines.length > maximumLogLines) {
logLines.shift();
}
const output = element<HTMLTextAreaElement>("#executionLogOutput");
output.value = logLines.join("\n");
output.scrollTop = output.scrollHeight;
}
function setRunning(value: boolean): void {
running = value;
const profile = selectedProfile();
const unavailable = value || !profile || !profileStoreReady;
element<HTMLButtonElement>("#simulateExecutionButton").disabled = unavailable;
element<HTMLButtonElement>("#submitExecutionButton").disabled = unavailable || !profile?.sendEnabled;
element<HTMLButtonElement>("#simulateMemoButton").disabled = unavailable;
element<HTMLButtonElement>("#submitMemoButton").disabled = unavailable || !profile?.sendEnabled;
element<HTMLButtonElement>("#simulateTokenButton").disabled = unavailable;
element<HTMLButtonElement>("#submitTokenButton").disabled = unavailable || !profile?.sendEnabled;
element<HTMLButtonElement>("#deriveAtaButton").disabled = unavailable;
element<HTMLButtonElement>("#simulateAtaButton").disabled = unavailable;
element<HTMLButtonElement>("#submitAtaButton").disabled = unavailable || !profile?.sendEnabled;
element<HTMLButtonElement>("#simulateToken2022Button").disabled = unavailable;
element<HTMLButtonElement>("#submitToken2022Button").disabled = unavailable || !profile?.sendEnabled;
element<HTMLButtonElement>("#loadToken2022FixtureButton").disabled = unavailable;
element<HTMLButtonElement>("#generateRecipientButton").disabled = value;
element<HTMLButtonElement>("#cancelExecutionButton").disabled = !value;
const badge = element<HTMLElement>("#executionStatusBadge");
badge.textContent = value ? "Exécution en cours" : "Prêt";
badge.className = value ? "badge text-bg-warning" : "badge text-bg-success";
}
function selectedProfile(): DemoExecutionSolanaCoreProfileOption | null {
const name = element<HTMLSelectElement>("#executionProfileSelect").value;
return profileOptions.find(profile => profile.name === name) ?? null;
}
function updateProfileHelp(): void {
const profile = selectedProfile();
const help = element<HTMLElement>("#executionProfileHelp");
if (!profile) {
help.textContent = "Aucun profil Devnet compatible.";
return;
}
help.textContent = `wallet=${profile.walletAlias}, spendMax=${profile.maxSpendLamports}, airdropMax=${profile.maxAirdropLamports}, send=${profile.sendEnabled ? "enabled" : "disabled"}`;
element<HTMLInputElement>("#executionAirdropInput").max = String(profile.maxAirdropLamports);
element<HTMLInputElement>("#executionLamportsInput").max = String(profile.maxSpendLamports);
setRunning(running);
}
function buildRequest(submit: boolean): DemoExecutionSolanaCoreRequest {
const recipient = element<HTMLInputElement>("#executionRecipientInput").value.trim();
if (recipient.length === 0) {
throw new Error("Le destinataire est obligatoire.");
}
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
recipient,
lamports: integerValue("#executionLamportsInput"),
airdropLamports: integerValue("#executionAirdropInput"),
submit,
operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,
forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked,
materializeAfterDecode: element<HTMLInputElement>("#executionMaterializeInput").checked,
};
}
function displaySummary(summary: DemoExecutionSolanaCoreSummaryPayload): void {
const concise = {
profileName: summary.profileName,
cluster: summary.cluster,
genesisHash: summary.genesisHash,
walletPublicKey: summary.walletPublicKey,
recipient: summary.recipient,
recipientExistedBefore: summary.recipientExistedBefore,
recipientBalanceBeforeLamports: summary.recipientBalanceBeforeLamports,
recipientMinimumBalanceLamports: summary.recipientMinimumBalanceLamports,
balanceBeforeLamports: summary.balanceBeforeLamports,
balanceAfterFundingLamports: summary.balanceAfterFundingLamports,
feeLamports: summary.feeLamports,
simulationSuccess: summary.simulationSuccess,
simulationError: summary.simulationError,
airdropSignature: summary.airdropSignature,
transactionSignature: summary.transactionSignature,
confirmationStatus: summary.confirmationStatus,
canonicalInserted: summary.canonicalInserted,
coreExtracted: summary.coreExtracted,
decodeCompleted: summary.decodeCompleted,
decodeFailedInputs: summary.decodeFailedInputs,
};
writePanel("#executionSummaryOutput", "Résumé", concise);
writePlanPanel(null, summary.planJson);
writeJsonPanel("#executionSimulationOutput", "Simulation exacte", summary.simulationJson);
writeJsonPanel("#executionDiagnosticsOutput", "Confirmation et replay", summary.diagnosticsJson);
}
function buildMemoRequest(submit: boolean): DemoExecutionMemoRequest {
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
message: element<HTMLTextAreaElement>("#memoMessageInput").value,
includeWalletAsMemoSigner: element<HTMLInputElement>("#memoWalletSignerInput").checked,
submit,
operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,
forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked,
};
}
function displayMemoSummary(summary: DemoExecutionMemoSummaryPayload): void {
const concise = {
operation: "spl.memo.v4.add_memo",
profileName: summary.profileName,
cluster: summary.cluster,
genesisHash: summary.genesisHash,
walletPublicKey: summary.walletPublicKey,
balanceLamports: summary.balanceLamports,
feeLamports: summary.feeLamports,
simulationSuccess: summary.simulationSuccess,
simulationError: summary.simulationError,
transactionSignature: summary.transactionSignature,
confirmationStatus: summary.confirmationStatus,
canonicalInserted: summary.canonicalInserted,
coreExtracted: summary.coreExtracted,
decodeReplayed: summary.decodeReplayed,
materialized: summary.materialized,
annotationCount: summary.annotationCount,
idempotenceValidated: summary.idempotenceValidated,
};
writePanel("#executionSummaryOutput", "Résumé", concise);
writePlanPanel(null, summary.planJson);
writeJsonPanel("#executionSimulationOutput", "Simulation exacte", summary.simulationJson);
writeJsonPanel("#executionDiagnosticsOutput", "Confirmation et replay", summary.diagnosticsJson);
}
function requiredText(selector: string, label: string): string {
const value = element<HTMLInputElement>(selector).value.trim();
if (value.length === 0) {
throw new Error(`${label} est obligatoire.`);
}
return value;
}
function optionalText(selector: string): string | null {
const value = element<HTMLInputElement>(selector).value.trim();
return value.length === 0 ? null : value;
}
function buildTokenRequest(submit: boolean): DemoExecutionSplTokenRequest {
const amountRaw = requiredText("#tokenAmountRawInput", "Le montant brut");
if (!/^[0-9]+$/.test(amountRaw)) {
throw new Error("Le montant brut doit être un entier décimal non signé.");
}
const decimals = integerValue("#tokenDecimalsInput");
if (decimals > 255) {
throw new Error("Les decimals doivent être compris entre 0 et 255.");
}
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
source: requiredText("#tokenSourceInput", "Le compte source"),
mint: requiredText("#tokenMintInput", "Le mint"),
destination: requiredText("#tokenDestinationInput", "Le compte destination"),
authority: requiredText("#tokenAuthorityInput", "Lautorité"),
amountRaw,
decimals,
submit,
operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,
forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked,
};
}
function displayTokenSummary(summary: DemoExecutionSplTokenSummaryPayload): void {
const concise = {
operation: summary.operation,
profileName: summary.profileName,
cluster: summary.cluster,
genesisHash: summary.genesisHash,
walletPublicKey: summary.walletPublicKey,
balanceLamports: summary.balanceLamports,
feeLamports: summary.feeLamports,
amountRaw: summary.amountRaw,
decimals: summary.decimals,
readinessStatus: summary.readinessStatus,
simulationSuccess: summary.simulationSuccess,
simulationError: summary.simulationError,
transactionSignature: summary.transactionSignature,
confirmationStatus: summary.confirmationStatus,
canonicalInserted: summary.canonicalInserted,
coreExtracted: summary.coreExtracted,
decodeReplayed: summary.decodeReplayed,
materializationCount: summary.materializationCount,
idempotenceValidated: summary.idempotenceValidated,
};
writePanel("#executionSummaryOutput", "Résumé", concise);
writePlanPanel(summary.readinessJson, summary.planJson);
writeJsonPanel("#executionSimulationOutput", "Simulation exacte", summary.simulationJson);
writeJsonPanel("#executionDiagnosticsOutput", "Confirmation et replay", summary.diagnosticsJson);
}
function ataDerivationRequest(): DemoSplAtaDerivationRequest {
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
mint: requiredText("#ataMintInput", "Le mint ATA"),
tokenProgram: element<HTMLSelectElement>("#ataTokenProgramSelect").value,
};
}
async function deriveAta(): Promise<DemoSplAtaDerivationPayload> {
const payload = await invoke<DemoSplAtaDerivationPayload>("demo_spl_ata_derive", {
request: ataDerivationRequest(),
});
element<HTMLInputElement>("#ataPayerInput").value = payload.payer;
element<HTMLInputElement>("#ataWalletOwnerInput").value = payload.walletOwner;
element<HTMLInputElement>("#ataDerivedInput").value = payload.associatedTokenAccount;
return payload;
}
function displayAtaSummary(summary: DemoExecutionSplAtaSummaryPayload): void {
writePanel("#executionSummaryOutput", "Résumé", {
operation: summary.operation,
profileName: summary.profileName,
walletPublicKey: summary.walletPublicKey,
tokenProgramId: summary.tokenProgramId,
associatedTokenAccount: summary.associatedTokenAccount,
balanceLamports: summary.balanceLamports,
feeLamports: summary.feeLamports,
readinessStatus: summary.readinessStatus,
simulationSuccess: summary.simulationSuccess,
simulationError: summary.simulationError,
transactionSignature: summary.transactionSignature,
confirmationStatus: summary.confirmationStatus,
materializationCount: summary.materializationCount,
idempotenceValidated: summary.idempotenceValidated,
});
writePlanPanel(summary.readinessJson, summary.planJson);
writeJsonPanel("#executionSimulationOutput", "Simulation exacte", summary.simulationJson);
writeJsonPanel("#executionDiagnosticsOutput", "Confirmation et replay", summary.diagnosticsJson);
}
async function executeAta(submit: boolean): Promise<void> {
if (running) {
return;
}
if (submit && !element<HTMLInputElement>("#executionOperatorConfirmedInput").checked) {
const message = "La confirmation opérateur est obligatoire avant signature ATA.";
appendLog({
timestamp: new Date().toISOString(),
level: "error",
stage: "ata_validation",
message,
});
writeExecutionState("error", message, message);
frontendError("kb-app-demo-desktop.frontend.execution_spl", message);
return;
}
try {
const derivation = await deriveAta();
const request: DemoExecutionSplAtaRequest = {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
walletOwner: derivation.walletOwner,
mint: requiredText("#ataMintInput", "Le mint ATA"),
tokenProgram: element<HTMLSelectElement>("#ataTokenProgramSelect").value,
mode: element<HTMLSelectElement>("#ataModeSelect").value,
submit,
operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,
forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked,
};
setRunning(true);
writeExecutionState("running", "Préflight et exécution SPL ATA en cours.");
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: submit ? "ata_submit" : "ata_simulation",
message: `${request.mode} ${request.tokenProgram}, ATA=${derivation.associatedTokenAccount}`,
});
const summary = await invoke<DemoExecutionSplAtaSummaryPayload>("demo_execution_spl_ata_execute", { request });
displayAtaSummary(summary);
appendLog({
timestamp: new Date().toISOString(),
level: summary.simulationSuccess && (!submit || summary.idempotenceValidated) ? "info" : "error",
stage: "ata_completed",
message: `preflight=${summary.readinessStatus}, simulation=${summary.simulationSuccess}, confirmation=${summary.confirmationStatus ?? "none"}, materializations=${summary.materializationCount}, idempotence=${summary.idempotenceValidated}`,
signature: summary.transactionSignature,
});
if (summary.transactionSignature) {
element<HTMLInputElement>("#ataJournalSignatureInput").value = summary.transactionSignature;
element<HTMLInputElement>("#ataJournalMintInput").value = request.mint;
element<HTMLInputElement>("#ataJournalAddressInput").value = summary.associatedTokenAccount;
await startAtaJournalLoad();
}
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeExecutionState("error", message, message);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "ata_failed", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `ATA execution failed: ${message}`);
} finally {
setRunning(false);
}
}
function ataJournalBaseFilters(): AtaJournalBaseRequest {
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
signatureContains: optionalText("#ataJournalSignatureInput"),
mint: optionalText("#ataJournalMintInput"),
associatedTokenAccount: optionalText("#ataJournalAddressInput"),
operation: optionalText("#ataJournalOperationInput"),
};
}
async function startAtaJournalLoad(): Promise<void> {
ataJournalBaseRequest = ataJournalBaseFilters();
resetStoreBlockState(ataJournalBlockState);
await loadAtaJournalBlock(1);
}
async function loadAtaJournalBlock(blockIndex: number): Promise<void> {
if (ataJournalBaseRequest === null) {
markStoreBlockFiltersDirty(ataJournalBlockState, ataJournalBlockControls);
return;
}
const cursor = cursorForBlock(ataJournalBlockState, blockIndex);
if (cursor === undefined) {
return;
}
const button = element<HTMLButtonElement>("#refreshAtaJournalButton");
button.disabled = true;
element<HTMLButtonElement>(ataJournalBlockControls.firstSelector).disabled = true;
element<HTMLButtonElement>(ataJournalBlockControls.previousSelector).disabled = true;
element<HTMLButtonElement>(ataJournalBlockControls.nextSelector).disabled = true;
const request: DemoSplAtaJournalRequest = {
...ataJournalBaseRequest,
cursor,
blockIndex,
};
try {
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", `Invoke demo_spl_ata_journal block=${blockIndex}`);
const payload = await invoke<DemoSplAtaJournalPagePayload>("demo_spl_ata_journal", { request });
applyStoreBlockMetadata(ataJournalBlockState, payload.page, ataJournalBlockControls);
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", `Invoke demo_spl_ata_journal completed block=${payload.page.blockIndex}/${payload.page.totalBlocks} rows=${payload.rows.length}`);
element<HTMLTextAreaElement>("#ataJournalOutput").value = JSON.stringify({
title: "Journal lifecycle ATA",
status: "success",
block: payload.page,
count: payload.rows.length,
rows: payload.rows.map(row => ({
...row,
payloadJson: parsedJson(row.payloadJson),
})),
}, null, 2);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
element<HTMLTextAreaElement>("#ataJournalOutput").value = JSON.stringify({
title: "Journal lifecycle ATA",
status: "error",
message,
error: message,
}, null, 2);
renderStoreBlockControls(ataJournalBlockState, ataJournalBlockControls);
} finally {
button.disabled = false;
}
}
function markAtaJournalFiltersDirty(): void {
ataJournalBaseRequest = null;
markStoreBlockFiltersDirty(ataJournalBlockState, ataJournalBlockControls);
}
async function execute(submit: boolean): Promise<void> {
if (running) {
return;
}
try {
const request = buildRequest(submit);
if (submit && !request.operatorConfirmed) {
const message = "La confirmation opérateur doit être cochée avant signature et envoi.";
writeExecutionState("warning", message);
appendLog({
timestamp: new Date().toISOString(),
level: "warn",
stage: "policy",
message,
});
frontendWarn("kb-app-demo-desktop.frontend.demo_execution_spl", `Execution blocked by policy: ${message}`);
return;
}
setRunning(true);
writeExecutionState("running", "Exécution Solana Core en cours.");
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: submit ? "submit" : "simulation",
message: submit ? "Démarrage du parcours Devnet complet." : "Démarrage de la simulation exacte.",
});
const summary = await invoke<DemoExecutionSolanaCoreSummaryPayload>("demo_execution_solana_core_execute", { request });
displaySummary(summary);
appendLog({
timestamp: new Date().toISOString(),
level: summary.simulationSuccess ? "info" : "error",
stage: "completed",
message: `simulation=${summary.simulationSuccess}, confirmation=${summary.confirmationStatus ?? "none"}, canonical=${summary.canonicalInserted ?? 0}, core=${summary.coreExtracted ?? 0}, decode=${summary.decodeCompleted ?? 0}`,
signature: summary.transactionSignature,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeExecutionState("error", message, message);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "failed", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Execution failed: ${message}`);
} finally {
setRunning(false);
}
}
async function executeMemo(submit: boolean): Promise<void> {
if (running) {
return;
}
try {
const request = buildMemoRequest(submit);
if (submit && !request.operatorConfirmed) {
const message = "La confirmation opérateur doit être cochée avant signature et envoi du Memo.";
writeExecutionState("warning", message);
appendLog({ timestamp: new Date().toISOString(), level: "warn", stage: "memo_policy", message });
frontendWarn("kb-app-demo-desktop.frontend.demo_execution_spl", `Memo execution blocked by policy: ${message}`);
return;
}
setRunning(true);
writeExecutionState("running", "Exécution Memo v4 en cours.");
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: submit ? "memo_submit" : "memo_simulation",
message: submit ? "Démarrage du parcours Memo v4 Devnet complet." : "Démarrage de la simulation Memo v4 exacte.",
});
const summary = await invoke<DemoExecutionMemoSummaryPayload>("demo_execution_spl_memo_execute", { request });
displayMemoSummary(summary);
appendLog({
timestamp: new Date().toISOString(),
level: summary.simulationSuccess && (!submit || summary.materialized) ? "info" : "error",
stage: "memo_completed",
message: `simulation=${summary.simulationSuccess}, confirmation=${summary.confirmationStatus ?? "none"}, annotation=${summary.annotationCount}, idempotence=${summary.idempotenceValidated}`,
signature: summary.transactionSignature,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeExecutionState("error", message, message);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "memo_failed", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Memo execution failed: ${message}`);
} finally {
setRunning(false);
}
}
async function executeToken(submit: boolean): Promise<void> {
if (running) {
return;
}
try {
const request = buildTokenRequest(submit);
if (submit && !request.operatorConfirmed) {
const message = "La confirmation opérateur doit être cochée avant signature et envoi Token.";
writeExecutionState("warning", message);
appendLog({ timestamp: new Date().toISOString(), level: "warn", stage: "spl_token_policy", message });
frontendWarn("kb-app-demo-desktop.frontend.demo_execution_spl", `Token execution blocked by policy: ${message}`);
return;
}
setRunning(true);
writeExecutionState("running", "Préflight et exécution SPL Token en cours.");
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: submit ? "spl_token_submit" : "spl_token_simulation",
message: submit ? "Démarrage du TransferChecked Devnet complet." : "Démarrage du préflight et de la simulation TransferChecked.",
});
const summary = await invoke<DemoExecutionSplTokenSummaryPayload>("demo_execution_spl_token_execute", { request });
displayTokenSummary(summary);
appendLog({
timestamp: new Date().toISOString(),
level: summary.simulationSuccess && (!submit || summary.idempotenceValidated) ? "info" : "error",
stage: "spl_token_completed",
message: `preflight=${summary.readinessStatus}, simulation=${summary.simulationSuccess}, confirmation=${summary.confirmationStatus ?? "none"}, materializations=${summary.materializationCount}, idempotence=${summary.idempotenceValidated}`,
signature: summary.transactionSignature,
});
if (summary.transactionSignature) {
element<HTMLInputElement>("#tokenJournalSignatureInput").value = summary.transactionSignature;
await startTokenJournalLoad();
}
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeExecutionState("error", message, message);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "spl_token_failed", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Token execution failed: ${message}`);
} finally {
setRunning(false);
}
}
function tokenJournalBaseFilters(): TokenJournalBaseRequest {
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
signatureContains: optionalText("#tokenJournalSignatureInput"),
mint: optionalText("#tokenJournalMintInput"),
account: optionalText("#tokenJournalAccountInput"),
family: element<HTMLSelectElement>("#tokenJournalFamilySelect").value || null,
operation: optionalText("#tokenJournalOperationInput"),
};
}
function renderTokenJournal(rows: DemoSplTokenJournalRow[]): void {
const body = element<HTMLTableSectionElement>("#tokenJournalRows");
body.replaceChildren();
if (rows.length === 0) {
const row = document.createElement("tr");
const cell = document.createElement("td");
cell.colSpan = 5;
cell.className = "text-body-secondary";
cell.textContent = "Aucun événement correspondant.";
row.append(cell);
body.append(row);
element<HTMLTextAreaElement>("#tokenJournalDetailOutput").value = JSON.stringify({
title: "Journal métier SPL Token",
status: "empty",
message: "Aucun événement sélectionné.",
}, null, 2);
return;
}
for (const item of rows) {
const row = document.createElement("tr");
row.tabIndex = 0;
row.setAttribute("role", "button");
const values = [
item.slot,
item.operation ?? "—",
item.family,
item.amountRaw ?? "—",
item.signature.length > 20 ? `${item.signature.slice(0, 10)}${item.signature.slice(-8)}` : item.signature,
];
for (const value of values) {
const cell = document.createElement("td");
cell.textContent = value;
row.append(cell);
}
row.title = item.signature;
const select = (): void => {
element<HTMLTextAreaElement>("#tokenJournalDetailOutput").value = JSON.stringify({
title: "Journal métier SPL Token",
status: "success",
row: {
...item,
payloadJson: parsedJson(item.payloadJson),
},
}, null, 2);
};
row.addEventListener("click", select);
row.addEventListener("keydown", event => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
select();
}
});
body.append(row);
}
element<HTMLTextAreaElement>("#tokenJournalDetailOutput").value = JSON.stringify({
title: "Journal métier SPL Token",
status: "success",
row: {
...rows[0],
payloadJson: parsedJson(rows[0].payloadJson),
},
}, null, 2);
}
function token_2022ScenarioAmount(fixture: DemoSplToken2022FixturePayload, scenarioId: string): string {
if (scenarioId === "token_2022_mint_to_checked") {
return fixture.mintAmountRaw;
}
if (scenarioId === "token_2022_transfer_checked") {
return fixture.transferAmountRaw;
}
if (scenarioId === "token_2022_approve_checked") {
return fixture.approveAmountRaw;
}
if (scenarioId === "token_2022_burn_checked") {
return fixture.burnAmountRaw;
}
return "0";
}
function applyToken2022Fixture(): void {
if (!token_2022Fixture) {
return;
}
const scenarioId = element<HTMLSelectElement>("#token_2022ScenarioSelect").value;
element<HTMLInputElement>("#token_2022SourceInput").value = scenarioId === "token_2022_close_destination"
? token_2022Fixture.closeAccount
: token_2022Fixture.source;
element<HTMLInputElement>("#token_2022MintInput").value = token_2022Fixture.mint;
element<HTMLInputElement>("#token_2022DestinationInput").value = scenarioId === "token_2022_close_destination"
? token_2022Fixture.authority
: token_2022Fixture.destination;
element<HTMLInputElement>("#token_2022DelegateInput").value = token_2022Fixture.delegate;
element<HTMLInputElement>("#token_2022AuthorityInput").value = token_2022Fixture.authority;
element<HTMLInputElement>("#token_2022FreezeAuthorityInput").value = token_2022Fixture.freezeAuthority;
element<HTMLInputElement>("#token_2022AmountInput").value = token_2022ScenarioAmount(token_2022Fixture, scenarioId);
element<HTMLInputElement>("#token_2022DecimalsInput").value = String(token_2022Fixture.decimals);
}
async function loadToken2022Fixture(): Promise<void> {
try {
token_2022Fixture = await invoke<DemoSplToken2022FixturePayload>("demo_spl_token_2022_fixture", {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
});
applyToken2022Fixture();
element<HTMLElement>("#token_2022FixtureHelp").textContent = `Fixture Token-2022 chargée — program=${token_2022Fixture.programId}`;
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: "token_2022_fixture",
message: `fixture chargée: mint=${token_2022Fixture.mint}, source=${token_2022Fixture.source}, destination=${token_2022Fixture.destination}`,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
token_2022Fixture = null;
element<HTMLElement>("#token_2022FixtureHelp").textContent = message;
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token_2022_fixture", message });
}
}
function buildToken2022Request(submit: boolean): DemoExecutionSplToken2022Request {
const scenarioId = element<HTMLSelectElement>("#token_2022ScenarioSelect").value;
const source = element<HTMLInputElement>("#token_2022SourceInput").value.trim();
const mint = element<HTMLInputElement>("#token_2022MintInput").value.trim();
const destination = element<HTMLInputElement>("#token_2022DestinationInput").value.trim();
const delegate = element<HTMLInputElement>("#token_2022DelegateInput").value.trim();
const authority = element<HTMLInputElement>("#token_2022AuthorityInput").value.trim();
const freezeAuthority = element<HTMLInputElement>("#token_2022FreezeAuthorityInput").value.trim();
if (source.length === 0) {
throw new Error("Le compte source ou cible Token-2022 est obligatoire.");
}
if (scenarioId !== "token_2022_revoke" && scenarioId !== "token_2022_close_destination" && mint.length === 0) {
throw new Error("Le mint Token-2022 est obligatoire pour ce scénario.");
}
if (scenarioId === "token_2022_transfer_checked" && destination.length === 0) {
throw new Error("Le compte destination Token-2022 est obligatoire.");
}
if (scenarioId === "token_2022_close_destination" && destination.length === 0) {
throw new Error("La destination lamports est obligatoire pour CloseAccount.");
}
if (scenarioId === "token_2022_approve_checked" && delegate.length === 0) {
throw new Error("Le delegate est obligatoire pour ApproveChecked.");
}
if (["token_2022_freeze_account", "token_2022_thaw_account"].includes(scenarioId) && freezeAuthority.length === 0) {
throw new Error("La freeze authority est obligatoire pour FreezeAccount et ThawAccount.");
}
if (!["token_2022_freeze_account", "token_2022_thaw_account"].includes(scenarioId) && authority.length === 0) {
throw new Error("Lautorité Token-2022 est obligatoire.");
}
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
scenarioId,
source,
mint,
destination,
delegate,
authority,
freezeAuthority,
amountRaw: element<HTMLInputElement>("#token_2022AmountInput").value.trim(),
decimals: integerValue("#token_2022DecimalsInput"),
submit,
operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,
forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked,
};
}
function displayToken2022Summary(summary: DemoExecutionSplToken2022SummaryPayload): void {
writePanel("#executionSummaryOutput", "Résumé", {
scenarioId: summary.scenarioId,
operation: summary.operation,
profileName: summary.profileName,
cluster: summary.cluster,
genesisHash: summary.genesisHash,
walletPublicKey: summary.walletPublicKey,
balanceLamports: summary.balanceLamports,
feeLamports: summary.feeLamports,
simulationSuccess: summary.simulationSuccess,
simulationError: summary.simulationError,
transactionSignature: summary.transactionSignature,
confirmationStatus: summary.confirmationStatus,
canonicalInserted: summary.canonicalInserted,
coreExtracted: summary.coreExtracted,
decodeReplayed: summary.decodeReplayed,
materializationCount: summary.materializationCount,
idempotenceValidated: summary.idempotenceValidated,
});
writePlanPanel(summary.preflightJson, summary.planJson);
writeJsonPanel("#executionSimulationOutput", "Simulation exacte", summary.simulationJson);
writeJsonPanel("#executionDiagnosticsOutput", "Confirmation et replay", summary.diagnosticsJson);
}
async function executeToken2022(submit: boolean): Promise<void> {
if (running) {
return;
}
let request: DemoExecutionSplToken2022Request;
try {
request = buildToken2022Request(submit);
if (submit && !request.operatorConfirmed) {
const message = "La confirmation opérateur est obligatoire avant signature Token-2022.";
writeExecutionState("warning", message);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token_2022_failed", message });
return;
}
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeExecutionState("error", message, message);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token_2022_failed", message });
return;
}
setRunning(true);
writeExecutionState("running", "Préflight et exécution Token-2022 en cours.");
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: submit ? "token_2022_submit" : "token_2022_simulation",
message: `${request.scenarioId}, source=${request.source}`,
});
try {
const summary = await invoke<DemoExecutionSplToken2022SummaryPayload>("demo_execution_spl_token_2022_execute", { request });
displayToken2022Summary(summary);
appendLog({
timestamp: new Date().toISOString(),
level: summary.simulationSuccess ? "info" : "error",
stage: "token_2022_completed",
message: `scenario=${summary.scenarioId}, simulation=${summary.simulationSuccess}, confirmation=${summary.confirmationStatus ?? "none"}, materializations=${summary.materializationCount}, idempotence=${summary.idempotenceValidated}`,
signature: summary.transactionSignature,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeExecutionState("error", message, message);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token_2022_failed", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Token-2022 execution failed: ${message}`);
} finally {
setRunning(false);
}
}
async function startTokenJournalLoad(): Promise<void> {
tokenJournalBaseRequest = tokenJournalBaseFilters();
resetStoreBlockState(tokenJournalBlockState);
await loadTokenJournalBlock(1);
}
async function loadTokenJournalBlock(blockIndex: number): Promise<void> {
if (tokenJournalBaseRequest === null) {
markStoreBlockFiltersDirty(tokenJournalBlockState, tokenJournalBlockControls);
return;
}
const cursor = cursorForBlock(tokenJournalBlockState, blockIndex);
if (cursor === undefined) {
return;
}
const button = element<HTMLButtonElement>("#refreshTokenJournalButton");
button.disabled = true;
element<HTMLButtonElement>(tokenJournalBlockControls.firstSelector).disabled = true;
element<HTMLButtonElement>(tokenJournalBlockControls.previousSelector).disabled = true;
element<HTMLButtonElement>(tokenJournalBlockControls.nextSelector).disabled = true;
const request: DemoSplTokenJournalRequest = {
...tokenJournalBaseRequest,
cursor,
blockIndex,
};
try {
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", `Invoke demo_spl_token_journal block=${blockIndex}`);
const payload = await invoke<DemoSplTokenJournalPagePayload>("demo_spl_token_journal", { request });
applyStoreBlockMetadata(tokenJournalBlockState, payload.page, tokenJournalBlockControls);
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", `Invoke demo_spl_token_journal completed block=${payload.page.blockIndex}/${payload.page.totalBlocks} rows=${payload.rows.length}`);
renderTokenJournal(payload.rows);
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: "spl_token_journal",
message: `${payload.rows.length} événement(s) matérialisé(s) chargé(s) dans le bloc ${payload.page.blockIndex}/${payload.page.totalBlocks}.`,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
element<HTMLTextAreaElement>("#tokenJournalDetailOutput").value = JSON.stringify({
title: "Journal métier SPL Token",
status: "error",
message,
error: message,
}, null, 2);
renderStoreBlockControls(tokenJournalBlockState, tokenJournalBlockControls);
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Token journal loading failed: ${message}`);
} finally {
button.disabled = false;
}
}
function markTokenJournalFiltersDirty(): void {
tokenJournalBaseRequest = null;
markStoreBlockFiltersDirty(tokenJournalBlockState, tokenJournalBlockControls);
}
async function generateRecipient(): Promise<void> {
try {
const payload = await invoke<DemoExecutionSolanaCoreGeneratedRecipientPayload>("demo_execution_solana_core_generate_recipient");
element<HTMLInputElement>("#executionRecipientInput").value = payload.publicKey;
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: "recipient",
message: `Adresse destinataire jetable générée : ${payload.publicKey}`,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Recipient generation failed: ${message}`);
}
}
async function cancelExecution(): Promise<void> {
try {
const accepted = await invoke<boolean>("demo_execution_solana_core_cancel");
appendLog({
timestamp: new Date().toISOString(),
level: accepted ? "warn" : "info",
stage: "cancel",
message: accepted ? "Demande dannulation enregistrée." : "Aucune exécution active.",
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Cancellation failed: ${message}`);
}
}
async function prepareSelectedProfile(): Promise<void> {
const profile = selectedProfile();
profileStoreReady = false;
setRunning(running);
if (!profile) {
return;
}
appendLog({ timestamp: new Date().toISOString(), level: "info", stage: "store_prepare", message: `Vérification du store du profil ${profile.name}.` });
try {
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", `Invoke demo_execution_devnet_prepare_profile profile=${profile.name}`);
const readiness = await invoke<DemoExecutionDevnetStoreReadinessPayload>("demo_execution_devnet_prepare_profile", { profileName: profile.name });
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", `Invoke demo_execution_devnet_prepare_profile completed profile=${profile.name} resources=${readiness.availableResourcesAfter}/${readiness.expectedResources}`);
profileStoreReady = true;
appendLog({ timestamp: new Date().toISOString(), level: "info", stage: "store_ready", message: `Store ${readiness.backend} prêt: ${readiness.availableResourcesAfter}/${readiness.expectedResources} ressources, créées=${readiness.createdResources}, autoInit=${readiness.autoInitializeSchema}.` });
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Devnet store preparation failed: ${message}`);
} finally {
setRunning(running);
}
}
async function loadOptions(): Promise<void> {
const options = await invoke<DemoExecutionSolanaCoreOptionsPayload>("demo_execution_solana_core_options");
profileOptions = options.profiles;
const select = element<HTMLSelectElement>("#executionProfileSelect");
select.replaceChildren();
for (const profile of profileOptions) {
const option = document.createElement("option");
option.value = profile.name;
option.textContent = profile.name;
option.selected = profile.name === options.defaultProfileName;
select.append(option);
}
element<HTMLInputElement>("#executionLamportsInput").value = String(options.defaultTransferLamports);
updateProfileHelp();
setRunning(options.running);
await prepareSelectedProfile();
if (profileOptions.length === 0) {
element<HTMLButtonElement>("#simulateExecutionButton").disabled = true;
element<HTMLButtonElement>("#submitExecutionButton").disabled = true;
element<HTMLButtonElement>("#simulateMemoButton").disabled = true;
element<HTMLButtonElement>("#submitMemoButton").disabled = true;
element<HTMLButtonElement>("#simulateTokenButton").disabled = true;
element<HTMLButtonElement>("#submitTokenButton").disabled = true;
element<HTMLButtonElement>("#deriveAtaButton").disabled = true;
element<HTMLButtonElement>("#simulateAtaButton").disabled = true;
element<HTMLButtonElement>("#submitAtaButton").disabled = true;
element<HTMLButtonElement>("#simulateToken2022Button").disabled = true;
element<HTMLButtonElement>("#submitToken2022Button").disabled = true;
element<HTMLButtonElement>("#loadToken2022FixtureButton").disabled = true;
}
}
document.addEventListener("DOMContentLoaded", () => {
installFrontendConsoleBridge("kb-app-demo-desktop.frontend.demo_execution_spl");
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", "execution demo window loaded");
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(item => new bootstrap.Tooltip(item));
element<HTMLSelectElement>("#executionProfileSelect").addEventListener("change", () => {
updateProfileHelp();
markTokenJournalFiltersDirty();
markAtaJournalFiltersDirty();
void prepareSelectedProfile();
});
element<HTMLButtonElement>("#generateRecipientButton").addEventListener("click", () => {
void generateRecipient();
});
element<HTMLButtonElement>("#simulateExecutionButton").addEventListener("click", () => {
void execute(false);
});
element<HTMLButtonElement>("#submitExecutionButton").addEventListener("click", () => {
void execute(true);
});
element<HTMLButtonElement>("#simulateMemoButton").addEventListener("click", () => {
void executeMemo(false);
});
element<HTMLButtonElement>("#submitMemoButton").addEventListener("click", () => {
void executeMemo(true);
});
element<HTMLButtonElement>("#simulateTokenButton").addEventListener("click", () => {
void executeToken(false);
});
element<HTMLButtonElement>("#submitTokenButton").addEventListener("click", () => {
void executeToken(true);
});
element<HTMLButtonElement>("#loadToken2022FixtureButton").addEventListener("click", () => {
void loadToken2022Fixture();
});
element<HTMLSelectElement>("#token_2022ScenarioSelect").addEventListener("change", applyToken2022Fixture);
element<HTMLButtonElement>("#simulateToken2022Button").addEventListener("click", () => {
void executeToken2022(false);
});
element<HTMLButtonElement>("#submitToken2022Button").addEventListener("click", () => {
void executeToken2022(true);
});
element<HTMLButtonElement>("#refreshTokenJournalButton").addEventListener("click", () => {
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", "Token journal load button clicked");
void startTokenJournalLoad();
});
element<HTMLButtonElement>("#tokenJournalFirstBlockButton").addEventListener("click", () => {
void loadTokenJournalBlock(1);
});
element<HTMLButtonElement>("#tokenJournalPreviousBlockButton").addEventListener("click", () => {
void loadTokenJournalBlock(tokenJournalBlockState.currentBlock - 1);
});
element<HTMLButtonElement>("#tokenJournalNextBlockButton").addEventListener("click", () => {
void loadTokenJournalBlock(tokenJournalBlockState.currentBlock + 1);
});
for (const selector of [
"#tokenJournalSignatureInput",
"#tokenJournalMintInput",
"#tokenJournalAccountInput",
"#tokenJournalOperationInput",
]) {
element<HTMLInputElement>(selector).addEventListener("input", markTokenJournalFiltersDirty);
}
element<HTMLSelectElement>("#tokenJournalFamilySelect").addEventListener("change", markTokenJournalFiltersDirty);
element<HTMLButtonElement>("#deriveAtaButton").addEventListener("click", () => {
void deriveAta();
});
element<HTMLButtonElement>("#simulateAtaButton").addEventListener("click", () => {
void executeAta(false);
});
element<HTMLButtonElement>("#submitAtaButton").addEventListener("click", () => {
void executeAta(true);
});
element<HTMLButtonElement>("#refreshAtaJournalButton").addEventListener("click", () => {
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", "ATA journal load button clicked");
void startAtaJournalLoad();
});
element<HTMLButtonElement>("#ataJournalFirstBlockButton").addEventListener("click", () => {
void loadAtaJournalBlock(1);
});
element<HTMLButtonElement>("#ataJournalPreviousBlockButton").addEventListener("click", () => {
void loadAtaJournalBlock(ataJournalBlockState.currentBlock - 1);
});
element<HTMLButtonElement>("#ataJournalNextBlockButton").addEventListener("click", () => {
void loadAtaJournalBlock(ataJournalBlockState.currentBlock + 1);
});
for (const selector of [
"#ataJournalSignatureInput",
"#ataJournalMintInput",
"#ataJournalAddressInput",
"#ataJournalOperationInput",
]) {
element<HTMLInputElement>(selector).addEventListener("input", markAtaJournalFiltersDirty);
}
element<HTMLButtonElement>("#cancelExecutionButton").addEventListener("click", () => {
void cancelExecution();
});
element<HTMLTextAreaElement>("#executionLogOutput").addEventListener("app-log-clear", () => {
logLines.length = 0;
});
void listen<DemoExecutionSolanaCoreProgressPayload>("demo-execution-solana-core-progress", event => {
appendLog(event.payload);
});
void loadOptions().catch(caughtError => {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "options", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Options loading failed: ${message}`);
});
});