v0.1.0-pre.053
This commit is contained in:
881
kb-app-demo-desktop/frontend/ts/demo_execution_spl.ts
Normal file
881
kb-app-demo-desktop/frontend/ts/demo_execution_spl.ts
Normal 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", "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<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}`);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user