// file: kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts // version: 10 import * as bootstrap from "bootstrap"; import "simplebar"; import { invoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log.ts"; import { renderJsonViewer } from "./json_viewer.ts"; import type { DemoExecutionMetadataScenarioPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataScenarioPayload.ts"; import type { DemoExecutionMetadataPreparedStepPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataPreparedStepPayload.ts"; import type { DemoExecutionMetadataRequest } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataRequest.ts"; import type { DemoExecutionMetadataSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSummaryPayload.ts"; import type { DemoExecutionDevnetStoreReadinessPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionDevnetStoreReadinessPayload.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"; const storageKey = "kb-app-demo-desktop.metadata.selected-scenario"; const profileStorageKey = "kb-app-demo-desktop.metadata.selected-profile"; (window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap; let scenarios: DemoExecutionMetadataScenarioPayload[] = []; let selectedProfileName: string | null = null; let executionRunning = false; let profileStoreReady = false; let profileOptions: DemoExecutionSolanaCoreProfileOption[] = []; let preparedScenarioId: string | null = null; let preparedStepIndex: number | null = null; const metadataLogLines: string[] = []; const maximumMetadataLogLines = 1200; type MetadataProgressPayload = { timestamp: string; level: string; stage: string; message: string; signature?: string | null }; function appendMetadataLog(payload: MetadataProgressPayload): void { const signature = payload.signature ? ` signature=${payload.signature}` : ""; metadataLogLines.push(`${payload.timestamp} ${payload.level.toUpperCase()} [${payload.stage}] ${payload.message}${signature}`); if (metadataLogLines.length > maximumMetadataLogLines) { metadataLogLines.splice(0, metadataLogLines.length - maximumMetadataLogLines); } const output = element("#metadataExecutionLogOutput"); output.value = metadataLogLines.join("\n"); output.scrollTop = output.scrollHeight; } function titled(title: string, value: unknown): unknown { return { title, value }; } function element(selector: string): T { const value = document.querySelector(selector); if (value === null) { throw new Error(`Élément introuvable : ${selector}`); } return value; } function activeSelect(): HTMLSelectElement { return document.querySelector("#metaplexDevnetCollapse.show") !== null ? element("#metadataDevnetScenarioSelect") : element("#metadataSyntheticScenarioSelect"); } function selectedScenario(): DemoExecutionMetadataScenarioPayload | null { const id = activeSelect().value; return scenarios.find(scenario => scenario.id === id) ?? null; } function fillSelect(selector: string, mode: string): void { const select = element(selector); select.replaceChildren(); for (const scenario of scenarios.filter(candidate => candidate.mode === mode)) { const option = document.createElement("option"); option.value = scenario.id; option.textContent = `${scenario.assetFamily} — ${scenario.label}`; select.append(option); } } function operationName(operationCode: string): string { const parts = operationCode.split("."); return parts.length === 0 ? operationCode : parts[parts.length - 1] ?? operationCode; } function renderScenarioSteps(scenario: DemoExecutionMetadataScenarioPayload): void { const select = element("#metadataStepSelect"); const previousValue = Number.parseInt(select.value, 10); select.replaceChildren(); scenario.operationCodes.forEach((operationCode, index) => { const option = document.createElement("option"); option.value = String(index); option.textContent = `${index + 1}. ${operationName(operationCode)}`; select.append(option); }); if (Number.isInteger(previousValue) && previousValue >= 0 && previousValue < scenario.operationCodes.length) { select.value = String(previousValue); } const index = Number.parseInt(select.value, 10); element("#previousMetadataStepButton").disabled = !Number.isInteger(index) || index <= 0; element("#nextMetadataStepButton").disabled = !Number.isInteger(index) || index >= scenario.operationCodes.length - 1; element("#prepareMetadataScenarioStepButton").textContent = Number.isInteger(index) && scenario.operationCodes[index] !== undefined ? `Préparer l’étape ${index + 1} — ${operationName(scenario.operationCodes[index])}` : "Préparer l’étape courante"; } function renderSelectedScenario(): void { const scenario = selectedScenario(); if (scenario === null) { return; } window.localStorage.setItem(storageKey, scenario.id); const networkMode = scenario.mode !== "synthetic"; renderScenarioSteps(scenario); preparedScenarioId = null; preparedStepIndex = null; element("#metadataOperationJson").value = ""; element("#metadataPreflightReadsJson").value = "[]"; element("#metadataPostconditionReadsJson").value = "[]"; element("#metadataRequirements").innerHTML = `
Parcours
${scenario.label}
Famille
${scenario.assetFamily}
Fixture
${scenario.fixtureKind}
État initial
${scenario.initialState}
État attendu
${scenario.resultingState}
Mode
${scenario.mode}
Profil
${networkMode ? selectedProfileName ?? "non chargé" : "non applicable"}
Collection
${scenario.requiresCollection ? "requise" : "non requise"}
Rule set
${scenario.requiresProgrammableRules ? "requis" : "non requis"}
Validation
${scenario.validationStatus}
`; renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", { scenarioId: scenario.id, operationCodes: scenario.operationCodes, checks: ["program_id", "owner", "pda_and_seeds", "required_accounts", "authorities", "stateful_correlations"], networkExecutionPerformed: false, })); renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", { mode: scenario.mode, policy: "simulation_first", networkExecutionPerformed: false, automaticSubmission: false, operatorConfirmationRequired: true, expectedState: scenario.validationStatus, note: networkMode ? "Renseignez un intent typé puis lancez une simulation RPC réelle." : "Scénario synthétique déterministe.", })); renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", { required: scenario.requiresPostcondition, evidenceKinds: scenario.requiredEvidence, networkEvidenceCollected: false, materializationTargets: scenario.materializationTargets, rule: "Aucune validation réseau n'est déclarée sans simulation RPC réelle, signature éventuelle, slot et postconditions.", })); } async function prepareScenarioStep(): Promise { const profileName = element("#metadataProfileSelect").value; const scenario = selectedScenario(); const stepIndex = Number.parseInt(element("#metadataStepSelect").value, 10); if (profileName.length === 0) { throw new Error("Sélectionnez un profil Devnet."); } if (scenario === null || scenario.mode === "synthetic") { throw new Error("Sélectionnez un scénario Devnet."); } if (!Number.isInteger(stepIndex) || stepIndex < 0) { throw new Error("Sélectionnez une étape valide."); } appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "fixture", message: `Préparation de ${scenario.id} étape ${stepIndex + 1}` }); const prepared = await invoke("demo_execution_metadata_prepare_step", { profileName, scenarioId: scenario.id, stepIndex, previousOperationJson: element("#metadataOperationJson").value.trim().length === 0 ? null : element("#metadataOperationJson").value, }); element("#metadataOperationJson").value = prepared.operationJson; element("#metadataPreflightReadsJson").value = prepared.preflightReadsJson; element("#metadataPostconditionReadsJson").value = prepared.postconditionReadsJson; preparedScenarioId = prepared.scenarioId; preparedStepIndex = prepared.stepIndex; renderJsonViewer("#metadataPreflightOutput", titled("Étape de scénario préparée", { scenarioId: prepared.scenarioId, stepIndex: prepared.stepIndex, operation: prepared.operation, fixtureState: prepared.fixtureState, preflightReads: JSON.parse(prepared.preflightReadsJson), postconditionReads: JSON.parse(prepared.postconditionReadsJson), simulationReady: prepared.simulationReady, submissionReady: prepared.submissionReady, })); appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "fixture", message: `${prepared.label} prête (${prepared.fixtureState})` }); setExecutionRunning(executionRunning); } function moveScenarioStep(offset: number): void { const select = element("#metadataStepSelect"); const current = Number.parseInt(select.value, 10); const next = current + offset; if (Number.isInteger(next) && next >= 0 && next < select.options.length) { select.value = String(next); const scenario = selectedScenario(); if (scenario !== null) { renderScenarioSteps(scenario); } } } function parseJsonText(value: string, label: string): unknown { try { return JSON.parse(value); } catch (caught) { const message = caught instanceof Error ? caught.message : String(caught); throw new Error(`${label} invalide : ${message}`); } } function selectedProfile(): DemoExecutionSolanaCoreProfileOption | null { const name = element("#metadataProfileSelect").value; return profileOptions.find(profile => profile.name === name) ?? null; } function updateProfileHelp(): void { const profile = selectedProfile(); const help = element("#metadataProfileHelp"); if (profile === null) { help.textContent = "Aucun profil Devnet compatible."; return; } help.textContent = `wallet=${profile.walletAlias}, spendMax=${profile.maxSpendLamports}, airdropMax=${profile.maxAirdropLamports}, send=${profile.sendEnabled ? "enabled" : "disabled"}`; } function setExecutionRunning(value: boolean): void { executionRunning = value; const profile = selectedProfile(); const missingOperation = element("#metadataOperationJson").value.trim().length === 0; const unavailable = value || profile === null || !profileStoreReady || missingOperation; element("#simulateMetadataButton").disabled = unavailable; element("#submitMetadataButton").disabled = unavailable || !profile?.sendEnabled; element("#prepareMetadataScenarioStepButton").disabled = value || profile === null || !profileStoreReady; } async function executeMetadata(submit: boolean): Promise { if (executionRunning) { return; } const profileName = element("#metadataProfileSelect").value; if (profileName.length === 0 || !profileStoreReady) { throw new Error("Sélectionnez et préparez un profil Devnet."); } const operationJson = element("#metadataOperationJson").value; const scenario = selectedScenario(); const stepIndex = Number.parseInt(element("#metadataStepSelect").value, 10); if (operationJson.trim().length === 0) { throw new Error("Préparez l’étape courante avant l’exécution."); } if (scenario === null || preparedScenarioId !== scenario.id || preparedStepIndex !== stepIndex) { throw new Error("Préparez de nouveau l’étape après toute modification du scénario ou de l’étape."); } const preflightReadsJson = element("#metadataPreflightReadsJson").value; const postconditionReadsJson = element("#metadataPostconditionReadsJson").value; parseJsonText(operationJson, "Intent Metaplex"); parseJsonText(preflightReadsJson, "Lectures de préflight"); parseJsonText(postconditionReadsJson, "Lectures de postcondition"); const operatorConfirmed = element("#metadataOperatorConfirmed").checked; if (submit && !operatorConfirmed) { throw new Error("La soumission exige la confirmation explicite de l’opérateur."); } const request: DemoExecutionMetadataRequest = { profileName, intentId: `desktop-metaplex-${Date.now()}`, operationJson, preflightReadsJson, postconditionReadsJson, submit, operatorConfirmed, materializeAfterConfirmation: element("#metadataMaterializeInput").checked, }; setExecutionRunning(true); const badge = element("#metadataStatusBadge"); badge.textContent = submit ? "Soumission" : "Simulation"; badge.className = "badge text-bg-warning"; appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: submit ? "submit" : "simulation", message: submit ? "Démarrage du parcours Devnet complet." : "Démarrage de la simulation RPC exacte." }); try { const summary = await invoke("demo_execution_metadata_execute", { request }); renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", JSON.parse(summary.preflightJson))); renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", { profileName: summary.profileName, cluster: summary.cluster, genesisHash: summary.genesisHash, walletPublicKey: summary.walletPublicKey, balanceLamports: summary.balanceLamports, simulationSuccess: summary.simulationSuccess, transactionSignature: summary.transactionSignature, confirmationStatus: summary.confirmationStatus, materializationRequested: summary.materializationRequested, materializedSnapshotCount: summary.materializedSnapshotCount, plan: JSON.parse(summary.planJson), simulation: JSON.parse(summary.simulationJson), })); renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", JSON.parse(summary.evidenceJson))); badge.textContent = summary.transactionSignature === null ? "Simulé" : "Confirmé"; badge.className = summary.simulationSuccess ? "badge text-bg-success" : "badge text-bg-danger"; } finally { setExecutionRunning(false); } } async function loadProfiles(): Promise { const options = await invoke("demo_execution_metadata_options"); profileOptions = options.profiles; const select = element("#metadataProfileSelect"); select.replaceChildren(); for (const profile of options.profiles) { const option = document.createElement("option"); option.value = profile.name; option.textContent = `${profile.name} — ${profile.walletAlias}`; select.append(option); } const restored = window.localStorage.getItem(profileStorageKey); if (restored !== null && Array.from(select.options).some(option => option.value === restored)) { select.value = restored; } else if (options.defaultProfileName !== null) { select.value = options.defaultProfileName; } selectedProfileName = select.value.length === 0 ? null : select.value; updateProfileHelp(); if (options.profiles.length === 0) { const message = "Aucun profil Devnet compatible n’a été retourné par la configuration active."; appendMetadataLog({ timestamp: new Date().toISOString(), level: "error", stage: "profile_options", message }); element("#metadataProfileStatusBadge").textContent = "Aucun profil"; element("#metadataProfileStatusBadge").className = "badge text-bg-danger"; renderSelectedScenario(); return; } appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "profile_options", message: `${options.profiles.length} profil(s) Devnet chargé(s).` }); renderSelectedScenario(); await prepareProfile(); } async function prepareProfile(): Promise { const badge = element("#metadataProfileStatusBadge"); const profileName = element("#metadataProfileSelect").value; if (profileName.length === 0) { badge.textContent = "Aucun profil"; badge.className = "badge text-bg-danger"; return; } profileStoreReady = false; setExecutionRunning(executionRunning); badge.textContent = "Préparation"; badge.className = "badge text-bg-warning"; try { const readiness = await invoke("demo_execution_devnet_prepare_profile", { profileName }); selectedProfileName = profileName; window.localStorage.setItem(profileStorageKey, profileName); renderJsonViewer("#metadataProfileOutput", readiness); profileStoreReady = true; badge.textContent = "Base prête"; badge.className = "badge text-bg-success"; setExecutionRunning(executionRunning); renderSelectedScenario(); } catch (caught) { const message = caught instanceof Error ? caught.message : String(caught); profileStoreReady = false; badge.textContent = "Erreur"; badge.className = "badge text-bg-danger"; setExecutionRunning(executionRunning); frontendError("kb-app-demo-desktop.frontend.execution_metadata", message); } } async function loadScenarios(): Promise { const badge = element("#metadataStatusBadge"); badge.textContent = "Chargement"; badge.className = "badge text-bg-warning"; try { scenarios = await invoke("demo_execution_metadata_scenarios"); fillSelect("#metadataSyntheticScenarioSelect", "synthetic"); fillSelect("#metadataDevnetScenarioSelect", "network_simulation"); const restored = window.localStorage.getItem(storageKey); for (const selector of ["#metadataSyntheticScenarioSelect", "#metadataDevnetScenarioSelect"]) { const select = element(selector); if (restored !== null && Array.from(select.options).some(option => option.value === restored)) { select.value = restored; } } renderSelectedScenario(); badge.textContent = `${scenarios.length} contrats`; badge.className = "badge text-bg-success"; } catch (caught) { const message = caught instanceof Error ? caught.message : String(caught); profileStoreReady = false; badge.textContent = "Erreur"; badge.className = "badge text-bg-danger"; setExecutionRunning(executionRunning); frontendError("kb-app-demo-desktop.frontend.execution_metadata", message); } } document.addEventListener("DOMContentLoaded", () => { installFrontendConsoleBridge("kb-app-demo-desktop.frontend.execution_metadata"); for (const selector of ["#metadataSyntheticScenarioSelect", "#metadataDevnetScenarioSelect"]) { element(selector).addEventListener("change", renderSelectedScenario); } for (const id of ["metaplexSyntheticCollapse", "metaplexDevnetCollapse"]) { element(`#${id}`).addEventListener("shown.bs.collapse", renderSelectedScenario); } element("#metadataProfileSelect").addEventListener("change", event => { const target = event.currentTarget as HTMLSelectElement; selectedProfileName = target.value.length === 0 ? null : target.value; profileStoreReady = false; updateProfileHelp(); setExecutionRunning(executionRunning); renderSelectedScenario(); void prepareProfile(); }); element("#prepareMetadataProfileButton").addEventListener("click", () => void prepareProfile()); element("#refreshMetadataScenariosButton").addEventListener("click", () => void loadScenarios()); element("#metadataStepSelect").addEventListener("change", () => { const scenario = selectedScenario(); if (scenario !== null) { renderScenarioSteps(scenario); } }); element("#previousMetadataStepButton").addEventListener("click", () => moveScenarioStep(-1)); element("#nextMetadataStepButton").addEventListener("click", () => moveScenarioStep(1)); element("#metadataOperationJson").addEventListener("input", () => { preparedScenarioId = null; preparedStepIndex = null; setExecutionRunning(executionRunning); }); element("#prepareMetadataScenarioStepButton").addEventListener("click", () => { void prepareScenarioStep().catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught))); }); element("#metadataExecutionLogOutput").addEventListener("app-log-clear", () => { metadataLogLines.length = 0; }); void listen("demo-execution-solana-core-progress", event => appendMetadataLog(event.payload)); element("#simulateMetadataButton").addEventListener("click", () => { void executeMetadata(false).catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught))); }); element("#submitMetadataButton").addEventListener("click", () => { void executeMetadata(true).catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught))); }); setExecutionRunning(false); frontendDebug("kb-app-demo-desktop.frontend.execution_metadata", "Metadata execution window loaded"); void loadProfiles().catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught))); void loadScenarios(); });