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