617 lines
39 KiB
TypeScript
617 lines
39 KiB
TypeScript
// file: kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts
|
||
// version: 15
|
||
|
||
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 { DemoExecutionMetadataMetaplexCampaignOptionPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexCampaignOptionPayload.ts";
|
||
import type { DemoExecutionMetadataMetaplexCampaignRequest } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexCampaignRequest.ts";
|
||
import type { DemoExecutionMetadataMetaplexCampaignSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexCampaignSummaryPayload.ts";
|
||
import type { DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload.ts";
|
||
import type { DemoExecutionMetadataProgressPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataProgressPayload.ts";
|
||
import type { DemoExecutionMetadataSolanaProgramCampaignRequest } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSolanaProgramCampaignRequest.ts";
|
||
import type { DemoExecutionMetadataSolanaProgramCampaignSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSolanaProgramCampaignSummaryPayload.ts";
|
||
import type { DemoExecutionMetadataSolanaProgramScenarioPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSolanaProgramScenarioPayload.ts";
|
||
import type { DemoExecutionMetadataToken2022CampaignRequest } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataToken2022CampaignRequest.ts";
|
||
import type { DemoExecutionMetadataToken2022CampaignSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataToken2022CampaignSummaryPayload.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 metaplexCampaignStorageKey = "kb-app-demo-desktop.metadata.metaplex.selected-campaign";
|
||
const solanaProgramMetadataStorageKey = "kb-app-demo-desktop.metadata.solana-program.selected-scenario";
|
||
const profileStorageKey = "kb-app-demo-desktop.metadata.selected-profile";
|
||
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
|
||
let scenarios: DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload[] = [];
|
||
let metaplexCampaigns: DemoExecutionMetadataMetaplexCampaignOptionPayload[] = [];
|
||
let solanaProgramMetadataScenarios: DemoExecutionMetadataSolanaProgramScenarioPayload[] = [];
|
||
let selectedProfileName: string | null = null;
|
||
let executionRunning = false;
|
||
let profileStoreReady = false;
|
||
let profileOptions: DemoExecutionSolanaCoreProfileOption[] = [];
|
||
const metadataLogLines: string[] = [];
|
||
const maximumMetadataLogLines = 1200;
|
||
|
||
type MetadataProgressInput = Omit<DemoExecutionMetadataProgressPayload, "signature"> & { signature?: string | null };
|
||
|
||
function appendMetadataLog(payload: MetadataProgressInput): 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<HTMLTextAreaElement>("#metadataExecutionLogOutput");
|
||
output.value = metadataLogLines.join("\n");
|
||
output.scrollTop = output.scrollHeight;
|
||
}
|
||
|
||
function titled(title: string, value: unknown): unknown { return { title, value }; }
|
||
|
||
function element<T extends HTMLElement>(selector: string): T {
|
||
const value = document.querySelector<T>(selector);
|
||
if (value === null) {
|
||
throw new Error(`Élément introuvable : ${selector}`);
|
||
}
|
||
return value;
|
||
}
|
||
|
||
const profileSelectSelectors = ["#metadataProfileSelect", "#token2022MetadataProfileSelect", "#solanaProgramMetadataProfileSelect"] as const;
|
||
const profileHelpSelectors = ["#metadataProfileHelp", "#token2022MetadataProfileHelp", "#solanaProgramMetadataProfileHelp"] as const;
|
||
const profileStatusSelectors = ["#metadataProfileStatusBadge", "#token2022MetadataProfileStatusBadge", "#solanaProgramMetadataProfileStatusBadge"] as const;
|
||
const profileOutputSelectors = ["#metadataProfileOutput", "#token2022MetadataProfileOutput", "#solanaProgramMetadataProfileOutput"] as const;
|
||
|
||
function synchronizeProfileSelections(profileName: string): void {
|
||
for (const selector of profileSelectSelectors) {
|
||
const select = element<HTMLSelectElement>(selector);
|
||
if (Array.from(select.options).some(option => option.value === profileName)) {
|
||
select.value = profileName;
|
||
}
|
||
}
|
||
selectedProfileName = profileName.length === 0 ? null : profileName;
|
||
}
|
||
|
||
function updateProfileStatus(text: string, className: string): void {
|
||
for (const selector of profileStatusSelectors) {
|
||
const badge = element<HTMLElement>(selector);
|
||
badge.textContent = text;
|
||
badge.className = className;
|
||
}
|
||
}
|
||
|
||
function selectedScenario(): DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload | null {
|
||
const id = element<HTMLSelectElement>("#metadataSyntheticScenarioSelect").value;
|
||
return scenarios.find(scenario => scenario.id === id) ?? null;
|
||
}
|
||
|
||
function selectedMetaplexCampaign(): DemoExecutionMetadataMetaplexCampaignOptionPayload | null {
|
||
const id = element<HTMLSelectElement>("#metadataMetaplexCampaignSelect").value;
|
||
return metaplexCampaigns.find(campaign => campaign.id === id) ?? null;
|
||
}
|
||
|
||
function selectedSolanaProgramMetadataScenario(): DemoExecutionMetadataSolanaProgramScenarioPayload | null {
|
||
const id = element<HTMLSelectElement>("#solanaProgramMetadataScenarioSelect").value;
|
||
return solanaProgramMetadataScenarios.find(scenario => scenario.id === id) ?? null;
|
||
}
|
||
|
||
function renderSelectedScenario(): void {
|
||
const scenario = selectedScenario();
|
||
if (scenario === null) {
|
||
return;
|
||
}
|
||
window.localStorage.setItem(storageKey, scenario.id);
|
||
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Parcours</dt><dd class="col-6">${scenario.label}</dd><dt class="col-6">Famille</dt><dd class="col-6"><code>${scenario.assetFamily}</code></dd><dt class="col-6">Fixture</dt><dd class="col-6"><code>${scenario.fixtureKind}</code></dd><dt class="col-6">État initial</dt><dd class="col-6"><code>${scenario.initialState}</code></dd><dt class="col-6">État attendu</dt><dd class="col-6"><code>${scenario.resultingState}</code></dd><dt class="col-6">Mode</dt><dd class="col-6"><code>${scenario.mode}</code></dd><dt class="col-6">Collection</dt><dd class="col-6">${scenario.requiresCollection ? "requise" : "non requise"}</dd><dt class="col-6">Rule set</dt><dd class="col-6">${scenario.requiresProgrammableRules ? "requis" : "non requis"}</dd><dt class="col-6">Validation</dt><dd class="col-6"><code>${scenario.validationStatus}</code></dd>`;
|
||
renderJsonViewer("#metadataPreflightOutput", titled("Contrat synthétique — opérations", {
|
||
scenarioId: scenario.id,
|
||
operationCodes: scenario.operationCodes,
|
||
fixtureKind: scenario.fixtureKind,
|
||
checks: ["program_id", "owner", "pda_and_seeds", "required_accounts", "authorities", "stateful_correlations"],
|
||
}));
|
||
renderJsonViewer("#metadataSimulationOutput", titled("Contrat synthétique — politique", {
|
||
mode: scenario.mode,
|
||
networkExecutionPerformed: false,
|
||
automaticSubmission: false,
|
||
validationStatus: scenario.validationStatus,
|
||
note: "Ce mode inspecte le contrat. Les exécutions Devnet utilisent exclusivement les campagnes qualifiées de pre.013.",
|
||
}));
|
||
renderJsonViewer("#metadataPostconditionsOutput", titled("Contrat synthétique — preuves", {
|
||
required: scenario.requiresPostcondition,
|
||
evidenceKinds: scenario.requiredEvidence,
|
||
materializationTargets: scenario.materializationTargets,
|
||
networkEvidenceCollected: false,
|
||
}));
|
||
}
|
||
|
||
function renderMetaplexCampaign(): void {
|
||
const campaign = selectedMetaplexCampaign();
|
||
if (campaign === null) {
|
||
return;
|
||
}
|
||
window.localStorage.setItem(metaplexCampaignStorageKey, campaign.id);
|
||
renderJsonViewer("#metadataMetaplexCampaignOutput", titled("Campagne Metaplex qualifiée", campaign));
|
||
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.metaplex_token_metadata</code></dd><dt class="col-6">Campagne</dt><dd class="col-6">${campaign.label}</dd><dt class="col-6">Asset</dt><dd class="col-6"><code>${campaign.assetFamily}</code></dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${selectedProfileName ?? "non chargé"}</code></dd><dt class="col-6">Qualification</dt><dd class="col-6"><code>${campaign.qualification}</code></dd><dt class="col-6">Opérations cibles</dt><dd class="col-6">${campaign.operationNames.length}</dd>`;
|
||
renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", {
|
||
campaignId: campaign.id,
|
||
label: campaign.label,
|
||
assetFamily: campaign.assetFamily,
|
||
setup: campaign.setup,
|
||
targetOperations: campaign.operationNames,
|
||
runner: "qualified_pre_013_specialized_campaign",
|
||
networkExecutionPerformed: false,
|
||
}));
|
||
renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", {
|
||
policy: "specialized_campaign_simulation_first",
|
||
operatorConfirmationRequired: true,
|
||
stopOnFirstFailure: true,
|
||
isolatedUnavailableSubmission: false,
|
||
qualification: campaign.qualification,
|
||
networkExecutionPerformed: false,
|
||
}));
|
||
renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", {
|
||
campaignId: campaign.id,
|
||
qualification: campaign.qualification,
|
||
requiredEvidence: ["rpc_simulation", "confirmed_signature_when_submitted", "canonical_replay", "stateful_postconditions", "materialization", "idempotence"],
|
||
unavailablePolicy: "simulation_only_probe_without_submission",
|
||
networkEvidenceCollected: false,
|
||
}));
|
||
}
|
||
|
||
function selectedProfile(): DemoExecutionSolanaCoreProfileOption | null {
|
||
if (selectedProfileName === null) {
|
||
return null;
|
||
}
|
||
return profileOptions.find(profile => profile.name === selectedProfileName) ?? null;
|
||
}
|
||
|
||
function updateProfileHelp(): void {
|
||
const profile = selectedProfile();
|
||
const text = profile === null
|
||
? "Aucun profil Devnet compatible."
|
||
: `wallet=${profile.walletAlias}, spendMax=${profile.maxSpendLamports}, airdropMax=${profile.maxAirdropLamports}, send=${profile.sendEnabled ? "enabled" : "disabled"}`;
|
||
for (const selector of profileHelpSelectors) {
|
||
element<HTMLElement>(selector).textContent = text;
|
||
}
|
||
}
|
||
|
||
function setExecutionRunning(value: boolean): void {
|
||
executionRunning = value;
|
||
const profile = selectedProfile();
|
||
const metaplexConfirmed = element<HTMLInputElement>("#metadataMetaplexCampaignOperatorConfirmed").checked;
|
||
element<HTMLButtonElement>("#executeMetadataMetaplexCampaignButton").disabled = value || profile === null || !profileStoreReady || !profile.sendEnabled || !metaplexConfirmed || selectedMetaplexCampaign() === null;
|
||
const token2022Confirmed = element<HTMLInputElement>("#token2022MetadataOperatorConfirmed").checked;
|
||
element<HTMLButtonElement>("#executeToken2022MetadataCampaignButton").disabled = value || profile === null || !profileStoreReady || !profile.sendEnabled || !token2022Confirmed;
|
||
const solanaProgramConfirmed = element<HTMLInputElement>("#solanaProgramMetadataOperatorConfirmed").checked;
|
||
element<HTMLButtonElement>("#executeSolanaProgramMetadataCampaignButton").disabled = value || profile === null || !profileStoreReady || !profile.sendEnabled || !solanaProgramConfirmed;
|
||
for (const selector of ["#prepareMetadataProfileButton", "#prepareToken2022MetadataProfileButton", "#prepareSolanaProgramMetadataProfileButton", "#refreshMetadataScenariosButton"]) {
|
||
element<HTMLButtonElement>(selector).disabled = value;
|
||
}
|
||
}
|
||
|
||
async function executeMetaplexCampaign(): Promise<void> {
|
||
if (executionRunning) {
|
||
return;
|
||
}
|
||
const profile = selectedProfile();
|
||
const campaign = selectedMetaplexCampaign();
|
||
if (profile === null || selectedProfileName === null || !profileStoreReady) {
|
||
throw new Error("Sélectionnez et préparez un profil Devnet.");
|
||
}
|
||
if (campaign === null) {
|
||
throw new Error("Sélectionnez une campagne Metaplex qualifiée.");
|
||
}
|
||
if (!profile.sendEnabled) {
|
||
throw new Error("Le profil sélectionné n’autorise pas les soumissions Devnet requises par les fixtures Metaplex.");
|
||
}
|
||
const operatorConfirmed = element<HTMLInputElement>("#metadataMetaplexCampaignOperatorConfirmed").checked;
|
||
if (!operatorConfirmed) {
|
||
throw new Error("La campagne Metaplex exige la confirmation explicite de l’opérateur.");
|
||
}
|
||
const request: DemoExecutionMetadataMetaplexCampaignRequest = {
|
||
profileName: selectedProfileName,
|
||
campaignId: campaign.id,
|
||
operatorConfirmed,
|
||
};
|
||
setExecutionRunning(true);
|
||
const badge = element<HTMLElement>("#metadataStatusBadge");
|
||
badge.textContent = "Campagne Metaplex";
|
||
badge.className = "badge text-bg-warning";
|
||
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "metaplex_campaign", message: `Démarrage de la campagne qualifiée ${campaign.id} — ${campaign.label}.` });
|
||
try {
|
||
const summary = await invoke<DemoExecutionMetadataMetaplexCampaignSummaryPayload>("demo_execution_metadata_metaplex_execute_campaign", { request });
|
||
const executions = JSON.parse(summary.executionsJson) as unknown;
|
||
const postconditions = JSON.parse(summary.postconditionsJson) as unknown;
|
||
renderJsonViewer("#metadataPreflightOutput", titled("Fixture et setup Metaplex", JSON.parse(summary.fixtureJson)));
|
||
renderJsonViewer("#metadataSimulationOutput", titled("Synthèse de campagne Metaplex", {
|
||
profileName: summary.profileName,
|
||
campaignId: summary.campaignId,
|
||
label: summary.label,
|
||
operationCount: summary.operationCount,
|
||
confirmedOperationCount: summary.confirmedOperationCount,
|
||
unavailableOperationCount: summary.unavailableOperationCount,
|
||
completed: summary.completed,
|
||
}));
|
||
renderJsonViewer("#metadataPostconditionsOutput", titled("Exécutions et postconditions Metaplex", { executions, postconditions }));
|
||
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.metaplex_token_metadata</code></dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${summary.profileName}</code></dd><dt class="col-6">Campagne</dt><dd class="col-6">${summary.label}</dd><dt class="col-6">Qualification</dt><dd class="col-6"><code>${campaign.qualification}</code></dd><dt class="col-6">Confirmées</dt><dd class="col-6">${summary.confirmedOperationCount}</dd><dt class="col-6">Indisponibles</dt><dd class="col-6">${summary.unavailableOperationCount}</dd><dt class="col-6">Éléments de preuve</dt><dd class="col-6">${summary.operationCount}</dd>`;
|
||
badge.textContent = summary.completed ? "Campagne qualifiée" : "Campagne incomplète";
|
||
badge.className = summary.completed ? "badge text-bg-success" : "badge text-bg-danger";
|
||
appendMetadataLog({ timestamp: new Date().toISOString(), level: summary.completed ? "info" : "error", stage: "metaplex_campaign", message: `Campagne terminée : confirmed=${summary.confirmedOperationCount}, unavailable=${summary.unavailableOperationCount}, evidence=${summary.operationCount}.` });
|
||
} finally {
|
||
setExecutionRunning(false);
|
||
}
|
||
}
|
||
|
||
function renderToken2022MetadataCampaign(): void {
|
||
const operations = [
|
||
"initialize_token_metadata",
|
||
"update_token_metadata_field",
|
||
"emit_token_metadata",
|
||
"remove_token_metadata_key",
|
||
"update_token_metadata_authority",
|
||
];
|
||
renderJsonViewer("#token2022MetadataCampaignOutput", titled("Campagne Token-2022 Token Metadata", {
|
||
family: "metadata.token_2022",
|
||
operations,
|
||
fixture: "fresh_token_2022_mint_with_self_metadata_pointer",
|
||
transactionCount: 6,
|
||
policy: "simulation_first_then_confirmed_submission",
|
||
desktopValidation: "confirmed_pre_014",
|
||
}));
|
||
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.token_2022</code></dd><dt class="col-6">Programme</dt><dd class="col-6"><code>TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb</code></dd><dt class="col-6">Opérations</dt><dd class="col-6">${operations.length}</dd><dt class="col-6">Transactions</dt><dd class="col-6">6</dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${selectedProfileName ?? "non chargé"}</code></dd><dt class="col-6">Validation desktop</dt><dd class="col-6"><code>confirmed_pre_014</code></dd>`;
|
||
renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", {
|
||
family: "metadata.token_2022",
|
||
operations,
|
||
fixturePreparation: ["fresh_mint", "metadata_pointer_to_self", "rent_budget_for_metadata_growth"],
|
||
desktopValidation: "confirmed_pre_014",
|
||
networkExecutionPerformed: false,
|
||
}));
|
||
renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", {
|
||
policy: "simulation_first",
|
||
submission: "required",
|
||
operatorConfirmationRequired: true,
|
||
stopOnFirstFailure: true,
|
||
fixtureTransactionCount: 1,
|
||
metadataTransactionCount: operations.length,
|
||
desktopValidation: "confirmed_pre_014",
|
||
networkExecutionPerformed: false,
|
||
}));
|
||
renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", {
|
||
requiredEvidence: ["metadata_pointer", "stateful_snapshot", "rpc_simulation", "confirmed_signature", "canonical_replay", "materialization", "idempotence", "emit_return_data"],
|
||
operationCount: operations.length,
|
||
desktopValidation: "confirmed_pre_014",
|
||
networkEvidenceCollected: false,
|
||
}));
|
||
}
|
||
|
||
async function executeToken2022MetadataCampaign(): Promise<void> {
|
||
if (executionRunning) {
|
||
return;
|
||
}
|
||
const profile = selectedProfile();
|
||
if (profile === null || selectedProfileName === null || !profileStoreReady) {
|
||
throw new Error("Sélectionnez et préparez un profil Devnet.");
|
||
}
|
||
if (!profile.sendEnabled) {
|
||
throw new Error("Le profil sélectionné n’autorise pas les soumissions Devnet.");
|
||
}
|
||
const operatorConfirmed = element<HTMLInputElement>("#token2022MetadataOperatorConfirmed").checked;
|
||
if (!operatorConfirmed) {
|
||
throw new Error("La campagne exige la confirmation explicite de la fixture et des cinq mutations.");
|
||
}
|
||
const request: DemoExecutionMetadataToken2022CampaignRequest = {
|
||
profileName: selectedProfileName,
|
||
operatorConfirmed,
|
||
};
|
||
setExecutionRunning(true);
|
||
const badge = element<HTMLElement>("#metadataStatusBadge");
|
||
badge.textContent = "Campagne Token-2022";
|
||
badge.className = "badge text-bg-warning";
|
||
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "token_2022_metadata_campaign", message: "Démarrage : création de la fixture puis cinq opérations Token Metadata." });
|
||
try {
|
||
const summary = await invoke<DemoExecutionMetadataToken2022CampaignSummaryPayload>("demo_execution_metadata_token_2022_execute_campaign", { request });
|
||
renderJsonViewer("#metadataPreflightOutput", titled("Fixture Token-2022 Metadata", JSON.parse(summary.fixtureJson)));
|
||
renderJsonViewer("#metadataSimulationOutput", titled("Synthèse de campagne", {
|
||
profileName: summary.profileName,
|
||
mint: summary.mint,
|
||
initialAuthority: summary.initialAuthority,
|
||
finalAuthority: summary.finalAuthority,
|
||
preparationSignature: summary.preparationSignature,
|
||
stepCount: summary.stepCount,
|
||
confirmedStepCount: summary.confirmedStepCount,
|
||
materializationCount: summary.materializationCount,
|
||
completed: summary.completed,
|
||
}));
|
||
renderJsonViewer("#metadataPostconditionsOutput", titled("Preuves ordonnées des cinq opérations", JSON.parse(summary.stepsJson)));
|
||
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.token_2022</code></dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${summary.profileName}</code></dd><dt class="col-6">Mint</dt><dd class="col-6"><code>${summary.mint}</code></dd><dt class="col-6">Confirmées</dt><dd class="col-6">${summary.confirmedStepCount}/${summary.stepCount}</dd><dt class="col-6">Matérialisations</dt><dd class="col-6">${summary.materializationCount}</dd><dt class="col-6">Autorité finale</dt><dd class="col-6"><code>${summary.finalAuthority}</code></dd>`;
|
||
badge.textContent = summary.completed ? "Campagne confirmée" : "Campagne incomplète";
|
||
badge.className = summary.completed ? "badge text-bg-success" : "badge text-bg-danger";
|
||
appendMetadataLog({ timestamp: new Date().toISOString(), level: summary.completed ? "info" : "error", stage: "token_2022_metadata_campaign", message: `Campagne terminée : confirmed=${summary.confirmedStepCount}/${summary.stepCount}, materializations=${summary.materializationCount}` });
|
||
} finally {
|
||
setExecutionRunning(false);
|
||
}
|
||
}
|
||
|
||
function renderSolanaProgramMetadataScenario(): void {
|
||
const scenario = selectedSolanaProgramMetadataScenario();
|
||
if (scenario === null) {
|
||
return;
|
||
}
|
||
window.localStorage.setItem(solanaProgramMetadataStorageKey, scenario.id);
|
||
renderJsonViewer("#solanaProgramMetadataScenarioOutput", titled("Parcours Solana Program Metadata", {
|
||
scenarioId: scenario.id,
|
||
label: scenario.label,
|
||
fixtureAccount: scenario.fixtureAccount,
|
||
steps: scenario.steps,
|
||
desktopValidation: "confirmed_pre_014",
|
||
}));
|
||
const allSteps = solanaProgramMetadataScenarios.flatMap(candidate => candidate.steps);
|
||
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.solana_program_metadata</code></dd><dt class="col-6">Program ID</dt><dd class="col-6"><code>ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S</code></dd><dt class="col-6">Parcours affiché</dt><dd class="col-6">${scenario.label}</dd><dt class="col-6">Étapes totales</dt><dd class="col-6">${allSteps.length}</dd><dt class="col-6">Transactions</dt><dd class="col-6">11 minimum</dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${selectedProfileName ?? "non chargé"}</code></dd><dt class="col-6">Validation desktop</dt><dd class="col-6"><code>confirmed_pre_014</code></dd>`;
|
||
renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", {
|
||
family: "metadata.solana_program_metadata",
|
||
journeys: solanaProgramMetadataScenarios,
|
||
operationCount: allSteps.length,
|
||
fixtureAccounts: ["buffer", "metadata"],
|
||
prefundingTransactionCount: 2,
|
||
desktopValidation: "confirmed_pre_014",
|
||
}));
|
||
renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", {
|
||
policy: "simulation_first",
|
||
submission: "required",
|
||
operatorConfirmationRequired: true,
|
||
stopOnFirstFailure: true,
|
||
desktopValidation: "confirmed_pre_014",
|
||
networkExecutionPerformed: false,
|
||
}));
|
||
renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", {
|
||
requiredEvidence: ["stateful_preflight", "rpc_simulation", "confirmed_signature", "stateful_postcondition", "materialized_snapshot_or_account_absence"],
|
||
desktopValidation: "confirmed_pre_014",
|
||
networkEvidenceCollected: false,
|
||
}));
|
||
}
|
||
|
||
async function executeSolanaProgramMetadataCampaign(): Promise<void> {
|
||
if (executionRunning) {
|
||
return;
|
||
}
|
||
const profile = selectedProfile();
|
||
if (profile === null || selectedProfileName === null || !profileStoreReady) {
|
||
throw new Error("Sélectionnez et préparez un profil Devnet.");
|
||
}
|
||
if (!profile.sendEnabled) {
|
||
throw new Error("Le profil sélectionné n’autorise pas les soumissions Devnet.");
|
||
}
|
||
const operatorConfirmed = element<HTMLInputElement>("#solanaProgramMetadataOperatorConfirmed").checked;
|
||
if (!operatorConfirmed) {
|
||
throw new Error("La campagne exige la confirmation explicite du préfinancement et des neuf mutations.");
|
||
}
|
||
const request: DemoExecutionMetadataSolanaProgramCampaignRequest = {
|
||
profileName: selectedProfileName,
|
||
operatorConfirmed,
|
||
};
|
||
setExecutionRunning(true);
|
||
const badge = element<HTMLElement>("#metadataStatusBadge");
|
||
badge.textContent = "Campagne ProgM6";
|
||
badge.className = "badge text-bg-warning";
|
||
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "spm_campaign", message: "Démarrage de la campagne complète : 2 préfinancements et 9 opérations." });
|
||
try {
|
||
const summary = await invoke<DemoExecutionMetadataSolanaProgramCampaignSummaryPayload>("demo_execution_metadata_solana_program_execute_campaign", { request });
|
||
renderJsonViewer("#metadataPreflightOutput", titled("Fixture et opérations préparées", JSON.parse(summary.fixtureJson)));
|
||
renderJsonViewer("#metadataSimulationOutput", titled("Synthèse de campagne", {
|
||
profileName: summary.profileName,
|
||
authority: summary.authority,
|
||
describedProgram: summary.describedProgram,
|
||
buffer: summary.buffer,
|
||
bufferPrefundSignature: summary.bufferPrefundSignature,
|
||
metadata: summary.metadata,
|
||
metadataPrefundSignature: summary.metadataPrefundSignature,
|
||
stepCount: summary.stepCount,
|
||
confirmedStepCount: summary.confirmedStepCount,
|
||
materializedSnapshotCount: summary.materializedSnapshotCount,
|
||
completed: summary.completed,
|
||
}));
|
||
renderJsonViewer("#metadataPostconditionsOutput", titled("Preuves ordonnées des neuf opérations", JSON.parse(summary.stepsJson)));
|
||
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.solana_program_metadata</code></dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${summary.profileName}</code></dd><dt class="col-6">Buffer</dt><dd class="col-6"><code>${summary.buffer}</code></dd><dt class="col-6">Metadata</dt><dd class="col-6"><code>${summary.metadata}</code></dd><dt class="col-6">Confirmées</dt><dd class="col-6">${summary.confirmedStepCount}/${summary.stepCount}</dd><dt class="col-6">Snapshots</dt><dd class="col-6">${summary.materializedSnapshotCount}</dd>`;
|
||
badge.textContent = summary.completed ? "Campagne confirmée" : "Campagne incomplète";
|
||
badge.className = summary.completed ? "badge text-bg-success" : "badge text-bg-danger";
|
||
appendMetadataLog({ timestamp: new Date().toISOString(), level: summary.completed ? "info" : "error", stage: "spm_campaign", message: `Campagne terminée : confirmed=${summary.confirmedStepCount}/${summary.stepCount}, snapshots=${summary.materializedSnapshotCount}` });
|
||
} finally {
|
||
setExecutionRunning(false);
|
||
}
|
||
}
|
||
|
||
function renderActiveMetadataSelection(): void {
|
||
if (document.querySelector("#token2022MetadataCollapse.show") !== null) {
|
||
renderToken2022MetadataCampaign();
|
||
return;
|
||
}
|
||
if (document.querySelector("#solanaProgramMetadataCollapse.show") !== null) {
|
||
renderSolanaProgramMetadataScenario();
|
||
return;
|
||
}
|
||
if (document.querySelector("#metaplexDevnetCollapse.show") !== null) {
|
||
renderMetaplexCampaign();
|
||
return;
|
||
}
|
||
renderSelectedScenario();
|
||
}
|
||
|
||
async function loadProfiles(): Promise<void> {
|
||
const options = await invoke<DemoExecutionSolanaCoreOptionsPayload>("demo_execution_metadata_options");
|
||
profileOptions = options.profiles;
|
||
for (const selector of profileSelectSelectors) {
|
||
const select = element<HTMLSelectElement>(selector);
|
||
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);
|
||
const availableNames = options.profiles.map(profile => profile.name);
|
||
const selected = restored !== null && availableNames.includes(restored)
|
||
? restored
|
||
: options.defaultProfileName ?? availableNames[0] ?? "";
|
||
synchronizeProfileSelections(selected);
|
||
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 });
|
||
updateProfileStatus("Aucun profil", "badge text-bg-danger");
|
||
renderActiveMetadataSelection();
|
||
return;
|
||
}
|
||
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "profile_options", message: `${options.profiles.length} profil(s) Devnet chargé(s).` });
|
||
renderActiveMetadataSelection();
|
||
await prepareProfile();
|
||
}
|
||
|
||
async function prepareProfile(): Promise<void> {
|
||
const profileName = selectedProfileName ?? "";
|
||
if (profileName.length === 0) {
|
||
updateProfileStatus("Aucun profil", "badge text-bg-danger");
|
||
return;
|
||
}
|
||
profileStoreReady = false;
|
||
setExecutionRunning(executionRunning);
|
||
updateProfileStatus("Préparation", "badge text-bg-warning");
|
||
try {
|
||
frontendDebug("kb-app-demo-desktop.frontend.execution_metadata", `Invoke demo_execution_devnet_prepare_profile profile=${profileName}`);
|
||
const readiness = await invoke<DemoExecutionDevnetStoreReadinessPayload>("demo_execution_devnet_prepare_profile", { profileName });
|
||
frontendDebug("kb-app-demo-desktop.frontend.execution_metadata", `Invoke demo_execution_devnet_prepare_profile completed profile=${profileName} resources=${readiness.availableResourcesAfter}/${readiness.expectedResources}`);
|
||
synchronizeProfileSelections(profileName);
|
||
window.localStorage.setItem(profileStorageKey, profileName);
|
||
for (const selector of profileOutputSelectors) {
|
||
renderJsonViewer(selector, readiness);
|
||
}
|
||
profileStoreReady = true;
|
||
updateProfileStatus("Base prête", "badge text-bg-success");
|
||
setExecutionRunning(executionRunning);
|
||
renderActiveMetadataSelection();
|
||
} catch (caught) {
|
||
const message = caught instanceof Error ? caught.message : String(caught);
|
||
profileStoreReady = false;
|
||
updateProfileStatus("Erreur", "badge text-bg-danger");
|
||
setExecutionRunning(executionRunning);
|
||
frontendError("kb-app-demo-desktop.frontend.execution_metadata", message);
|
||
}
|
||
}
|
||
|
||
async function loadScenarios(): Promise<void> {
|
||
const badge = element<HTMLElement>("#metadataStatusBadge");
|
||
badge.textContent = "Chargement";
|
||
badge.className = "badge text-bg-warning";
|
||
try {
|
||
scenarios = await invoke<DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload[]>("demo_execution_metadata_metaplex_token_metadata_scenarios");
|
||
metaplexCampaigns = await invoke<DemoExecutionMetadataMetaplexCampaignOptionPayload[]>("demo_execution_metadata_metaplex_campaigns");
|
||
solanaProgramMetadataScenarios = await invoke<DemoExecutionMetadataSolanaProgramScenarioPayload[]>("demo_execution_metadata_solana_program_scenarios");
|
||
const syntheticSelect = element<HTMLSelectElement>("#metadataSyntheticScenarioSelect");
|
||
syntheticSelect.replaceChildren();
|
||
for (const scenario of scenarios.filter(candidate => candidate.mode === "synthetic")) {
|
||
const option = document.createElement("option");
|
||
option.value = scenario.id;
|
||
option.textContent = `${scenario.assetFamily} — ${scenario.label}`;
|
||
syntheticSelect.append(option);
|
||
}
|
||
const restored = window.localStorage.getItem(storageKey);
|
||
if (restored !== null && Array.from(syntheticSelect.options).some(option => option.value === restored)) {
|
||
syntheticSelect.value = restored;
|
||
}
|
||
const metaplexSelect = element<HTMLSelectElement>("#metadataMetaplexCampaignSelect");
|
||
metaplexSelect.replaceChildren();
|
||
for (const campaign of metaplexCampaigns) {
|
||
const option = document.createElement("option");
|
||
option.value = campaign.id;
|
||
option.textContent = `${campaign.assetFamily} — ${campaign.label}`;
|
||
metaplexSelect.append(option);
|
||
}
|
||
const restoredMetaplex = window.localStorage.getItem(metaplexCampaignStorageKey);
|
||
if (restoredMetaplex !== null && Array.from(metaplexSelect.options).some(option => option.value === restoredMetaplex)) {
|
||
metaplexSelect.value = restoredMetaplex;
|
||
}
|
||
const solanaSelect = element<HTMLSelectElement>("#solanaProgramMetadataScenarioSelect");
|
||
solanaSelect.replaceChildren();
|
||
for (const scenario of solanaProgramMetadataScenarios) {
|
||
const option = document.createElement("option");
|
||
option.value = scenario.id;
|
||
option.textContent = `${scenario.fixtureAccount} — ${scenario.label}`;
|
||
solanaSelect.append(option);
|
||
}
|
||
const restoredSolana = window.localStorage.getItem(solanaProgramMetadataStorageKey);
|
||
if (restoredSolana !== null && Array.from(solanaSelect.options).some(option => option.value === restoredSolana)) {
|
||
solanaSelect.value = restoredSolana;
|
||
}
|
||
renderActiveMetadataSelection();
|
||
const syntheticCount = scenarios.filter(candidate => candidate.mode === "synthetic").length;
|
||
badge.textContent = `${syntheticCount + metaplexCampaigns.length + solanaProgramMetadataScenarios.length + 1} contrats`;
|
||
badge.className = "badge text-bg-success";
|
||
setExecutionRunning(executionRunning);
|
||
} 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");
|
||
element<HTMLSelectElement>("#metadataSyntheticScenarioSelect").addEventListener("change", renderSelectedScenario);
|
||
element<HTMLElement>("#metaplexSyntheticCollapse").addEventListener("shown.bs.collapse", renderSelectedScenario);
|
||
element<HTMLElement>("#metaplexDevnetCollapse").addEventListener("shown.bs.collapse", renderMetaplexCampaign);
|
||
element<HTMLSelectElement>("#metadataMetaplexCampaignSelect").addEventListener("change", () => {
|
||
renderMetaplexCampaign();
|
||
setExecutionRunning(executionRunning);
|
||
});
|
||
element<HTMLElement>("#token2022MetadataCollapse").addEventListener("shown.bs.collapse", renderToken2022MetadataCampaign);
|
||
element<HTMLElement>("#solanaProgramMetadataCollapse").addEventListener("shown.bs.collapse", renderSolanaProgramMetadataScenario);
|
||
element<HTMLSelectElement>("#solanaProgramMetadataScenarioSelect").addEventListener("change", renderSolanaProgramMetadataScenario);
|
||
for (const selector of profileSelectSelectors) {
|
||
element<HTMLSelectElement>(selector).addEventListener("change", event => {
|
||
const target = event.currentTarget as HTMLSelectElement;
|
||
synchronizeProfileSelections(target.value);
|
||
profileStoreReady = false;
|
||
updateProfileHelp();
|
||
setExecutionRunning(executionRunning);
|
||
renderActiveMetadataSelection();
|
||
void prepareProfile();
|
||
});
|
||
}
|
||
element<HTMLButtonElement>("#prepareMetadataProfileButton").addEventListener("click", () => {
|
||
frontendDebug("kb-app-demo-desktop.frontend.execution_metadata", "Metaplex profile load button clicked");
|
||
void prepareProfile();
|
||
});
|
||
element<HTMLButtonElement>("#prepareToken2022MetadataProfileButton").addEventListener("click", () => {
|
||
frontendDebug("kb-app-demo-desktop.frontend.execution_metadata", "Token-2022 Metadata profile load button clicked");
|
||
void prepareProfile();
|
||
});
|
||
element<HTMLButtonElement>("#prepareSolanaProgramMetadataProfileButton").addEventListener("click", () => {
|
||
frontendDebug("kb-app-demo-desktop.frontend.execution_metadata", "Solana Program Metadata profile load button clicked");
|
||
void prepareProfile();
|
||
});
|
||
element<HTMLButtonElement>("#refreshMetadataScenariosButton").addEventListener("click", () => void loadScenarios());
|
||
element<HTMLTextAreaElement>("#metadataExecutionLogOutput").addEventListener("app-log-clear", () => { metadataLogLines.length = 0; });
|
||
void listen<DemoExecutionMetadataProgressPayload>("demo-execution-metadata-progress", event => appendMetadataLog(event.payload));
|
||
element<HTMLInputElement>("#metadataMetaplexCampaignOperatorConfirmed").addEventListener("change", () => setExecutionRunning(executionRunning));
|
||
element<HTMLButtonElement>("#executeMetadataMetaplexCampaignButton").addEventListener("click", () => {
|
||
void executeMetaplexCampaign().catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught)));
|
||
});
|
||
element<HTMLInputElement>("#token2022MetadataOperatorConfirmed").addEventListener("change", () => setExecutionRunning(executionRunning));
|
||
element<HTMLButtonElement>("#executeToken2022MetadataCampaignButton").addEventListener("click", () => {
|
||
void executeToken2022MetadataCampaign().catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught)));
|
||
});
|
||
element<HTMLInputElement>("#solanaProgramMetadataOperatorConfirmed").addEventListener("change", () => setExecutionRunning(executionRunning));
|
||
element<HTMLButtonElement>("#executeSolanaProgramMetadataCampaignButton").addEventListener("click", () => {
|
||
void executeSolanaProgramMetadataCampaign().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();
|
||
});
|