881 lines
45 KiB
TypeScript
881 lines
45 KiB
TypeScript
// file: kb-app-demo-desktop/frontend/ts/demo_execution_spl.ts
|
||
// version: 3
|
||
|
||
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 { 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 { 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 { DemoSplAtaJournalRow } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRow.ts";
|
||
import type { DemoExecutionSplToken2022Request } from "./bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022Request.ts";
|
||
import type { DemoExecutionSplToken2022SummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022SummaryPayload.ts";
|
||
import type { DemoSplToken2022FixturePayload } from "./bindings/kb_app_demo_desktop/demo_spl_token2022/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 profileOptions: DemoExecutionSolanaCoreProfileOption[] = [];
|
||
let token2022Fixture: DemoSplToken2022FixturePayload | 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 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();
|
||
element<HTMLButtonElement>("#simulateExecutionButton").disabled = value || !profile;
|
||
element<HTMLButtonElement>("#submitExecutionButton").disabled = value || !profile || !profile.sendEnabled;
|
||
element<HTMLButtonElement>("#simulateMemoButton").disabled = value || !profile;
|
||
element<HTMLButtonElement>("#submitMemoButton").disabled = value || !profile || !profile.sendEnabled;
|
||
element<HTMLButtonElement>("#simulateTokenButton").disabled = value || !profile;
|
||
element<HTMLButtonElement>("#submitTokenButton").disabled = value || !profile || !profile.sendEnabled;
|
||
element<HTMLButtonElement>("#deriveAtaButton").disabled = value || !profile;
|
||
element<HTMLButtonElement>("#simulateAtaButton").disabled = value || !profile;
|
||
element<HTMLButtonElement>("#submitAtaButton").disabled = value || !profile || !profile.sendEnabled;
|
||
element<HTMLButtonElement>("#simulateToken2022Button").disabled = value || !profile;
|
||
element<HTMLButtonElement>("#submitToken2022Button").disabled = value || !profile || !profile.sendEnabled;
|
||
element<HTMLButtonElement>("#loadToken2022FixtureButton").disabled = value || !profile;
|
||
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,
|
||
};
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify(concise, null, 2);
|
||
element<HTMLTextAreaElement>("#executionPlanOutput").value = summary.planJson;
|
||
element<HTMLTextAreaElement>("#executionSimulationOutput").value = summary.simulationJson;
|
||
element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value = 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,
|
||
};
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify(concise, null, 2);
|
||
element<HTMLTextAreaElement>("#executionPlanOutput").value = summary.planJson;
|
||
element<HTMLTextAreaElement>("#executionSimulationOutput").value = summary.simulationJson;
|
||
element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value = 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", "L’autorité"),
|
||
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,
|
||
};
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify(concise, null, 2);
|
||
element<HTMLTextAreaElement>("#executionPlanOutput").value = `PRÉFLIGHT\n${summary.readinessJson}\n\nPLAN\n${summary.planJson}`;
|
||
element<HTMLTextAreaElement>("#executionSimulationOutput").value = summary.simulationJson;
|
||
element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value = 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 {
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({
|
||
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,
|
||
}, null, 2);
|
||
element<HTMLTextAreaElement>("#executionPlanOutput").value = `PRÉFLIGHT\n${summary.readinessJson}\n\nPLAN\n${summary.planJson}`;
|
||
element<HTMLTextAreaElement>("#executionSimulationOutput").value = summary.simulationJson;
|
||
element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value = summary.diagnosticsJson;
|
||
}
|
||
|
||
async function executeAta(submit: boolean): Promise<void> {
|
||
if (running) {
|
||
return;
|
||
}
|
||
try {
|
||
if (submit && !element<HTMLInputElement>("#executionOperatorConfirmedInput").checked) {
|
||
throw new Error("La confirmation opérateur est obligatoire avant signature ATA.");
|
||
}
|
||
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);
|
||
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 loadAtaJournal();
|
||
}
|
||
} catch (caughtError) {
|
||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||
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 ataJournalRequest(): DemoSplAtaJournalRequest {
|
||
return {
|
||
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
|
||
signatureContains: optionalText("#ataJournalSignatureInput"),
|
||
mint: optionalText("#ataJournalMintInput"),
|
||
associatedTokenAccount: optionalText("#ataJournalAddressInput"),
|
||
operation: optionalText("#ataJournalOperationInput"),
|
||
limit: integerValue("#ataJournalLimitInput"),
|
||
};
|
||
}
|
||
|
||
async function loadAtaJournal(): Promise<void> {
|
||
const button = element<HTMLButtonElement>("#refreshAtaJournalButton");
|
||
button.disabled = true;
|
||
try {
|
||
const rows = await invoke<DemoSplAtaJournalRow[]>("demo_spl_ata_journal", { request: ataJournalRequest() });
|
||
element<HTMLTextAreaElement>("#ataJournalOutput").value = JSON.stringify(rows.map(row => ({
|
||
...row,
|
||
payloadJson: JSON.parse(row.payloadJson) as unknown,
|
||
})), null, 2);
|
||
} catch (caughtError) {
|
||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||
element<HTMLTextAreaElement>("#ataJournalOutput").value = JSON.stringify({ error: message }, null, 2);
|
||
} finally {
|
||
button.disabled = false;
|
||
}
|
||
}
|
||
|
||
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.";
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ warning: message }, null, 2);
|
||
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);
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = "Exécution 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);
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ error: message }, null, 2);
|
||
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.";
|
||
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);
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = "Exécution Memo 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);
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ error: message }, null, 2);
|
||
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.";
|
||
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);
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = "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 loadTokenJournal();
|
||
}
|
||
} catch (caughtError) {
|
||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ error: message }, null, 2);
|
||
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 tokenJournalRequest(): DemoSplTokenJournalRequest {
|
||
return {
|
||
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
|
||
signatureContains: optionalText("#tokenJournalSignatureInput"),
|
||
mint: optionalText("#tokenJournalMintInput"),
|
||
account: optionalText("#tokenJournalAccountInput"),
|
||
family: element<HTMLSelectElement>("#tokenJournalFamilySelect").value || null,
|
||
operation: optionalText("#tokenJournalOperationInput"),
|
||
limit: integerValue("#tokenJournalLimitInput"),
|
||
};
|
||
}
|
||
|
||
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 = "Aucun événement sélectionné.";
|
||
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 = item.payloadJson;
|
||
};
|
||
row.addEventListener("click", select);
|
||
row.addEventListener("keydown", event => {
|
||
if (event.key === "Enter" || event.key === " ") {
|
||
event.preventDefault();
|
||
select();
|
||
}
|
||
});
|
||
body.append(row);
|
||
}
|
||
element<HTMLTextAreaElement>("#tokenJournalDetailOutput").value = rows[0].payloadJson;
|
||
}
|
||
|
||
|
||
|
||
function token2022ScenarioAmount(fixture: DemoSplToken2022FixturePayload, scenarioId: string): string {
|
||
if (scenarioId === "token2022_mint_to_checked") {
|
||
return "1";
|
||
}
|
||
if (scenarioId === "token2022_approve_checked") {
|
||
return "2";
|
||
}
|
||
if (scenarioId === "token2022_burn_checked") {
|
||
return "1";
|
||
}
|
||
return fixture.defaultAmountRaw;
|
||
}
|
||
|
||
function applyToken2022Fixture(): void {
|
||
if (!token2022Fixture) {
|
||
return;
|
||
}
|
||
const scenarioId = element<HTMLSelectElement>("#token2022ScenarioSelect").value;
|
||
element<HTMLInputElement>("#token2022SourceInput").value = scenarioId === "token2022_close_destination"
|
||
? token2022Fixture.closeAccount
|
||
: token2022Fixture.source;
|
||
element<HTMLInputElement>("#token2022MintInput").value = token2022Fixture.mint;
|
||
element<HTMLInputElement>("#token2022DestinationInput").value = scenarioId === "token2022_close_destination"
|
||
? token2022Fixture.authority
|
||
: token2022Fixture.destination;
|
||
element<HTMLInputElement>("#token2022DelegateInput").value = token2022Fixture.delegate;
|
||
element<HTMLInputElement>("#token2022AuthorityInput").value = token2022Fixture.authority;
|
||
element<HTMLInputElement>("#token2022FreezeAuthorityInput").value = token2022Fixture.freezeAuthority;
|
||
element<HTMLInputElement>("#token2022AmountInput").value = token2022ScenarioAmount(token2022Fixture, scenarioId);
|
||
element<HTMLInputElement>("#token2022DecimalsInput").value = String(token2022Fixture.decimals);
|
||
}
|
||
|
||
async function loadToken2022Fixture(): Promise<void> {
|
||
try {
|
||
token2022Fixture = await invoke<DemoSplToken2022FixturePayload>("demo_spl_token2022_fixture", {
|
||
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
|
||
});
|
||
applyToken2022Fixture();
|
||
element<HTMLElement>("#token2022FixtureHelp").textContent = `Fixture: ${token2022Fixture.fixturePath} — program=${token2022Fixture.programId}`;
|
||
appendLog({
|
||
timestamp: new Date().toISOString(),
|
||
level: "info",
|
||
stage: "token2022_fixture",
|
||
message: `fixture chargée: mint=${token2022Fixture.mint}, source=${token2022Fixture.source}, destination=${token2022Fixture.destination}`,
|
||
});
|
||
} catch (caughtError) {
|
||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||
token2022Fixture = null;
|
||
element<HTMLElement>("#token2022FixtureHelp").textContent = message;
|
||
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_fixture", message });
|
||
}
|
||
}
|
||
|
||
function buildToken2022Request(submit: boolean): DemoExecutionSplToken2022Request {
|
||
const scenarioId = element<HTMLSelectElement>("#token2022ScenarioSelect").value;
|
||
const source = element<HTMLInputElement>("#token2022SourceInput").value.trim();
|
||
const mint = element<HTMLInputElement>("#token2022MintInput").value.trim();
|
||
const destination = element<HTMLInputElement>("#token2022DestinationInput").value.trim();
|
||
const delegate = element<HTMLInputElement>("#token2022DelegateInput").value.trim();
|
||
const authority = element<HTMLInputElement>("#token2022AuthorityInput").value.trim();
|
||
const freezeAuthority = element<HTMLInputElement>("#token2022FreezeAuthorityInput").value.trim();
|
||
if (source.length === 0) {
|
||
throw new Error("Le compte source ou cible Token-2022 est obligatoire.");
|
||
}
|
||
if (scenarioId !== "token2022_revoke" && scenarioId !== "token2022_close_destination" && mint.length === 0) {
|
||
throw new Error("Le mint Token-2022 est obligatoire pour ce scénario.");
|
||
}
|
||
if (scenarioId === "token2022_transfer_checked" && destination.length === 0) {
|
||
throw new Error("Le compte destination Token-2022 est obligatoire.");
|
||
}
|
||
if (scenarioId === "token2022_close_destination" && destination.length === 0) {
|
||
throw new Error("La destination lamports est obligatoire pour CloseAccount.");
|
||
}
|
||
if (scenarioId === "token2022_approve_checked" && delegate.length === 0) {
|
||
throw new Error("Le delegate est obligatoire pour ApproveChecked.");
|
||
}
|
||
if (["token2022_freeze_account", "token2022_thaw_account"].includes(scenarioId) && freezeAuthority.length === 0) {
|
||
throw new Error("La freeze authority est obligatoire pour FreezeAccount et ThawAccount.");
|
||
}
|
||
if (!["token2022_freeze_account", "token2022_thaw_account"].includes(scenarioId) && authority.length === 0) {
|
||
throw new Error("L’autorité Token-2022 est obligatoire.");
|
||
}
|
||
return {
|
||
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
|
||
scenarioId,
|
||
source,
|
||
mint,
|
||
destination,
|
||
delegate,
|
||
authority,
|
||
freezeAuthority,
|
||
amountRaw: element<HTMLInputElement>("#token2022AmountInput").value.trim(),
|
||
decimals: integerValue("#token2022DecimalsInput"),
|
||
submit,
|
||
operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,
|
||
forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked,
|
||
};
|
||
}
|
||
|
||
function displayToken2022Summary(summary: DemoExecutionSplToken2022SummaryPayload): void {
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({
|
||
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,
|
||
}, null, 2);
|
||
element<HTMLTextAreaElement>("#executionPlanOutput").value = `PRÉFLIGHT\n${summary.preflightJson}\n\nPLAN\n${summary.planJson}`;
|
||
element<HTMLTextAreaElement>("#executionSimulationOutput").value = summary.simulationJson;
|
||
element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value = 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.";
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ warning: message }, null, 2);
|
||
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_failed", message });
|
||
return;
|
||
}
|
||
} catch (caughtError) {
|
||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_failed", message });
|
||
return;
|
||
}
|
||
setRunning(true);
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = "Préflight et exécution Token-2022 en cours...";
|
||
appendLog({
|
||
timestamp: new Date().toISOString(),
|
||
level: "info",
|
||
stage: submit ? "token2022_submit" : "token2022_simulation",
|
||
message: `${request.scenarioId}, source=${request.source}`,
|
||
});
|
||
try {
|
||
const summary = await invoke<DemoExecutionSplToken2022SummaryPayload>("demo_execution_spl_token2022_execute", { request });
|
||
displayToken2022Summary(summary);
|
||
appendLog({
|
||
timestamp: new Date().toISOString(),
|
||
level: summary.simulationSuccess ? "info" : "error",
|
||
stage: "token2022_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);
|
||
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ error: message }, null, 2);
|
||
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_failed", message });
|
||
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Token-2022 execution failed: ${message}`);
|
||
} finally {
|
||
setRunning(false);
|
||
}
|
||
}
|
||
|
||
async function loadTokenJournal(): Promise<void> {
|
||
const button = element<HTMLButtonElement>("#refreshTokenJournalButton");
|
||
button.disabled = true;
|
||
try {
|
||
const rows = await invoke<DemoSplTokenJournalRow[]>("demo_spl_token_journal", { request: tokenJournalRequest() });
|
||
renderTokenJournal(rows);
|
||
appendLog({
|
||
timestamp: new Date().toISOString(),
|
||
level: "info",
|
||
stage: "spl_token_journal",
|
||
message: `${rows.length} événement(s) matérialisé(s) chargé(s).`,
|
||
});
|
||
} catch (caughtError) {
|
||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||
element<HTMLTextAreaElement>("#tokenJournalDetailOutput").value = JSON.stringify({ error: message }, null, 2);
|
||
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Token journal loading failed: ${message}`);
|
||
} finally {
|
||
button.disabled = false;
|
||
}
|
||
}
|
||
|
||
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 d’annulation 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 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);
|
||
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);
|
||
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>("#token2022ScenarioSelect").addEventListener("change", applyToken2022Fixture);
|
||
element<HTMLButtonElement>("#simulateToken2022Button").addEventListener("click", () => {
|
||
void executeToken2022(false);
|
||
});
|
||
element<HTMLButtonElement>("#submitToken2022Button").addEventListener("click", () => {
|
||
void executeToken2022(true);
|
||
});
|
||
element<HTMLButtonElement>("#refreshTokenJournalButton").addEventListener("click", () => {
|
||
void loadTokenJournal();
|
||
});
|
||
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", () => {
|
||
void loadAtaJournal();
|
||
});
|
||
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}`);
|
||
});
|
||
});
|