v0.1.0-pre.053

This commit is contained in:
2026-07-25 23:57:48 +02:00
parent 930056d422
commit 4e76263f88
23 changed files with 4575 additions and 82 deletions

View File

@@ -0,0 +1,881 @@
// file: kb-app-demo-desktop/frontend/ts/demo_execution_spl.ts
// version: 2
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", "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,
};
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("Lautorité 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 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 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<HTMLButtonElement>("#clearExecutionLogButton").addEventListener("click", () => {
logLines.length = 0;
element<HTMLTextAreaElement>("#executionLogOutput").value = "";
});
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}`);
});
});