Files
khadhroony-bot3/kb-app-demo-desktop/frontend/ts/demo_wallet.ts

856 lines
38 KiB
TypeScript

// file: kb-app-demo-desktop/frontend/ts/demo_wallet.ts
// version: 5
import * as bootstrap from "bootstrap";
import "simplebar";
import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log.ts";
import { renderJsonViewer } from "./json_viewer.ts";
import type { DemoWalletBalancePayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletBalancePayload.ts";
import type { DemoWalletCreateRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletCreateRequest.ts";
import type { DemoWalletExportRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExportRequest.ts";
import type { DemoWalletExecutionProfilePayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExecutionProfilePayload.ts";
import type { DemoWalletExecutionSelectionRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExecutionSelectionRequest.ts";
import type { DemoWalletIdentityPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletIdentityPayload.ts";
import type { DemoWalletImportRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletImportRequest.ts";
import type { DemoWalletInventoryPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletInventoryPayload.ts";
import type { DemoWalletOnchainProfilePayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletOnchainProfilePayload.ts";
import type { DemoWalletRpcExecutionPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletRpcExecutionPayload.ts";
import type { DemoWalletRpcRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletRpcRequest.ts";
import type { DemoWalletTransferFormatPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletTransferFormatPayload.ts";
import type { DemoWalletTransferInspectionPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletTransferInspectionPayload.ts";
import type { DemoWalletTransferInspectionRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletTransferInspectionRequest.ts";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
interface ParsedTokenAccount {
program: string;
mint: string;
tokenAccount: string;
uiAmount: string;
rawAmount: string;
decimals: number;
}
interface ParsedSignatureInfo {
signature: string;
slot: number;
err: unknown | null;
memo: string | null;
blockTime: number | null;
confirmationStatus: string | null;
}
let walletInventory: DemoWalletInventoryPayload | null = null;
let inspectedTransferPublicKey = "";
function setText(selector: string, value: string): void {
const element = document.querySelector<HTMLElement>(selector);
if (element) {
element.textContent = value;
}
}
function inputValue(selector: string): string {
return document.querySelector<HTMLInputElement>(selector)?.value.trim() ?? "";
}
function selectedOnchainProfile(): string {
return document.querySelector<HTMLSelectElement>("#walletOnchainProfileSelect")?.value.trim() ?? "";
}
function selectedOnchainProfilePayload(): DemoWalletOnchainProfilePayload | null {
const profile = selectedOnchainProfile();
if (!walletInventory || profile.length === 0) {
return null;
}
return walletInventory.onchain_profiles.find(candidate => candidate.name === profile) ?? null;
}
function updateOnchainClusterLabel(): void {
const selected = selectedOnchainProfilePayload();
setText("#walletOnchainSelectedProfile", selected?.name ?? "—");
setText("#walletOnchainCluster", selected?.cluster ?? "—");
}
function renderOnchainProfiles(profiles: ReadonlyArray<DemoWalletOnchainProfilePayload>, activeProfile: string): void {
const select = document.querySelector<HTMLSelectElement>("#walletOnchainProfileSelect");
if (!select) {
return;
}
const previous = select.value;
select.replaceChildren();
for (const profile of profiles) {
const option = document.createElement("option");
option.value = profile.name;
option.textContent = `${profile.name}${profile.cluster}${profile.active ? " (actif)" : ""}`;
select.appendChild(option);
}
if (profiles.some(profile => profile.name === previous)) {
select.value = previous;
} else if (profiles.some(profile => profile.name === activeProfile)) {
select.value = activeProfile;
} else if (profiles.length > 0) {
select.value = profiles[0].name;
}
updateOnchainClusterLabel();
}
function selectedExecutionProfilePayload(): DemoWalletExecutionProfilePayload | null {
const profile = document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect")?.value.trim() ?? "";
if (!walletInventory || profile.length === 0) {
return null;
}
return walletInventory.execution_profiles.find(candidate => candidate.name === profile) ?? null;
}
function renderExecutionProfiles(profiles: ReadonlyArray<DemoWalletExecutionProfilePayload>): void {
const select = document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect");
if (!select) {
return;
}
const previous = select.value;
select.replaceChildren();
for (const profile of profiles) {
const option = document.createElement("option");
option.value = profile.name;
option.textContent = profile.name;
select.appendChild(option);
}
if (profiles.some(profile => profile.name === previous)) {
select.value = previous;
} else if (profiles.length > 0) {
select.value = profiles[0].name;
}
}
function renderExecutionWalletAliases(wallets: ReadonlyArray<DemoWalletIdentityPayload>): void {
const select = document.querySelector<HTMLSelectElement>("#walletExecutionAliasSelect");
if (!select) {
return;
}
select.replaceChildren();
const configuredOption = document.createElement("option");
configuredOption.value = "";
configuredOption.textContent = "Utiliser la configuration du profil";
select.appendChild(configuredOption);
for (const wallet of wallets) {
const option = document.createElement("option");
option.value = wallet.alias;
option.textContent = `${wallet.alias}${wallet.public_key}`;
select.appendChild(option);
}
}
function updateExecutionSelectionDisplay(): void {
const profile = selectedExecutionProfilePayload();
const aliasSelect = document.querySelector<HTMLSelectElement>("#walletExecutionAliasSelect");
if (!profile) {
setText("#walletExecutionConfiguredAlias", "—");
setText("#walletExecutionRuntimeAlias", "—");
setText("#walletExecutionEffectiveAlias", "temporaire");
if (aliasSelect) {
aliasSelect.value = "";
}
return;
}
setText("#walletExecutionConfiguredAlias", profile.configured_alias ?? "—");
setText("#walletExecutionRuntimeAlias", profile.runtime_alias ?? "—");
setText("#walletExecutionEffectiveAlias", profile.effective_alias ?? "temporaire");
if (aliasSelect) {
const requested = profile.runtime_alias ?? "";
if (Array.from(aliasSelect.options).some(option => option.value === requested)) {
aliasSelect.value = requested;
} else {
aliasSelect.value = "";
}
}
}
async function applyExecutionWalletSelection(): Promise<void> {
const profile = document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect")?.value.trim() ?? "";
const aliasText = document.querySelector<HTMLSelectElement>("#walletExecutionAliasSelect")?.value.trim() ?? "";
if (profile.length === 0) {
setText("#walletExecutionSelectionStatus", "Profil Devnet requis");
return;
}
const request: DemoWalletExecutionSelectionRequest = {
profile,
alias: aliasText.length === 0 ? null : aliasText,
};
setText("#walletExecutionSelectionStatus", "Validation...");
try {
const payload = await invoke<DemoWalletInventoryPayload>("demo_wallet_select_execution_wallet", { request });
applyWalletInventoryPayload(payload);
const effective = payload.execution_profiles.find(candidate => candidate.name === profile)?.effective_alias ?? null;
setText("#walletExecutionSelectionStatus", effective ? `Persistant : ${effective}` : "Configuration du profil");
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", `execution wallet selection updated for ${profile}`);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#walletExecutionSelectionStatus", `Erreur : ${message}`);
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `Execution wallet selection failed: ${message}`);
}
}
async function executeWalletRpc(profile: string, method: string, params: unknown[]): Promise<DemoWalletRpcExecutionPayload> {
const request: DemoWalletRpcRequest = {
profile,
method,
params_json: JSON.stringify(params),
};
return await invoke<DemoWalletRpcExecutionPayload>("demo_wallet_rpc_execute", { request });
}
function parseJsonResponse(response: DemoWalletRpcExecutionPayload): unknown {
return JSON.parse(response.response_json) as unknown;
}
function renderTransferFormats(formats: ReadonlyArray<DemoWalletTransferFormatPayload>): void {
for (const selector of ["#walletImportFormatSelect", "#walletExportFormatSelect"]) {
const select = document.querySelector<HTMLSelectElement>(selector);
if (!select) {
continue;
}
const previous = select.value;
select.replaceChildren();
for (const format of formats) {
const option = document.createElement("option");
option.value = format.code;
option.textContent = `${format.label} (.${format.default_extension})`;
option.dataset.defaultExtension = format.default_extension;
select.appendChild(option);
}
if (formats.some(format => format.code === previous)) {
select.value = previous;
}
}
}
function renderExportWalletAliases(wallets: ReadonlyArray<DemoWalletIdentityPayload>): void {
const select = document.querySelector<HTMLSelectElement>("#walletExportAliasSelect");
if (!select) {
return;
}
const previous = select.value;
select.replaceChildren();
if (wallets.length === 0) {
const option = document.createElement("option");
option.value = "";
option.textContent = "Aucun wallet natif";
select.appendChild(option);
return;
}
for (const wallet of wallets) {
const option = document.createElement("option");
option.value = wallet.alias;
option.textContent = `${wallet.alias}${wallet.public_key}`;
select.appendChild(option);
}
if (wallets.some(wallet => wallet.alias === previous)) {
select.value = previous;
}
}
function selectedTransferFormat(selector: string): string {
return document.querySelector<HTMLSelectElement>(selector)?.value.trim() ?? "";
}
function selectedExportFormatPayload(): DemoWalletTransferFormatPayload | null {
const code = selectedTransferFormat("#walletExportFormatSelect");
if (!walletInventory || code.length === 0) {
return null;
}
return walletInventory.transfer_formats.find(format => format.code === code) ?? null;
}
function updateExportFileName(force: boolean): void {
const input = document.querySelector<HTMLInputElement>("#walletExportFileNameInput");
const alias = document.querySelector<HTMLSelectElement>("#walletExportAliasSelect")?.value.trim() ?? "";
const format = selectedExportFormatPayload();
if (!input || alias.length === 0 || !format) {
return;
}
const generated = `${alias}.${format.default_extension}`;
if (force || input.value.trim().length === 0 || input.dataset.autoGenerated === "true") {
input.value = generated;
input.dataset.autoGenerated = "true";
}
}
function setTransferPublicKey(publicKey: string): void {
inspectedTransferPublicKey = publicKey;
setText("#walletTransferPublicKey", publicKey.length > 0 ? publicKey : "—");
const exploreButton = document.querySelector<HTMLButtonElement>("#exploreWalletTransferButton");
if (exploreButton) {
exploreButton.disabled = publicKey.length === 0;
}
}
function renderWalletRows(wallets: ReadonlyArray<DemoWalletIdentityPayload>): void {
const body = document.querySelector<HTMLTableSectionElement>("#walletInventoryTableBody");
if (!body) {
return;
}
body.replaceChildren();
if (wallets.length === 0) {
const row = document.createElement("tr");
const cell = document.createElement("td");
cell.colSpan = 5;
cell.className = "text-body-secondary text-center py-3";
cell.textContent = "Aucun wallet natif trouvé.";
row.appendChild(cell);
body.appendChild(row);
return;
}
for (const wallet of wallets) {
const row = document.createElement("tr");
const aliasCell = document.createElement("td");
const publicKeyCell = document.createElement("td");
const formatCell = document.createElement("td");
const selectedCell = document.createElement("td");
const actionCell = document.createElement("td");
const exploreButton = document.createElement("button");
aliasCell.textContent = wallet.alias;
publicKeyCell.textContent = wallet.public_key;
publicKeyCell.className = "font-monospace text-break";
formatCell.textContent = `v${wallet.format_version}`;
const executionSelected = selectedExecutionProfilePayload()?.effective_alias === wallet.alias;
selectedCell.textContent = executionSelected ? "Oui" : "Non";
exploreButton.type = "button";
exploreButton.className = "btn btn-sm btn-outline-primary";
exploreButton.textContent = "Explorer";
exploreButton.addEventListener("click", () => {
const input = document.querySelector<HTMLInputElement>("#walletAddressInput");
if (input) {
input.value = wallet.public_key;
}
document.querySelector<HTMLElement>("#walletAddressInput")?.scrollIntoView({ behavior: "smooth", block: "center" });
});
actionCell.appendChild(exploreButton);
if (executionSelected) {
row.classList.add("table-primary");
}
row.append(aliasCell, publicKeyCell, formatCell, selectedCell, actionCell);
body.appendChild(row);
}
}
function applyWalletInventoryPayload(payload: DemoWalletInventoryPayload): void {
walletInventory = payload;
setText("#walletProfile", payload.profile);
setText("#walletCluster", payload.cluster);
renderOnchainProfiles(payload.onchain_profiles, payload.profile);
renderExecutionProfiles(payload.execution_profiles);
renderTransferFormats(payload.transfer_formats);
renderExportWalletAliases(payload.wallets);
renderExecutionWalletAliases(payload.wallets);
setText("#walletCreateSecretStatus", payload.create_password_configured ? "configuré" : "absent");
setText("#walletSelectedAlias", payload.selected_alias ?? "—");
setText("#walletInventoryCount", String(payload.wallets.length));
setText("#walletExportDirectory", `${payload.export_directory}/`);
setText("#walletInventoryStatus", "OK");
renderWalletRows(payload.wallets);
updateExecutionSelectionDisplay();
updateExportFileName(false);
}
async function refreshWalletInventory(): Promise<void> {
setText("#walletInventoryStatus", "Chargement...");
try {
const payload = await invoke<DemoWalletInventoryPayload>("demo_wallet_inventory");
applyWalletInventoryPayload(payload);
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", "wallet inventory refreshed");
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#walletInventoryStatus", `Erreur : ${message}`);
setText("#walletInventoryCount", "0");
renderWalletRows([]);
renderExportWalletAliases([]);
renderExecutionWalletAliases([]);
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `Wallet inventory loading failed: ${message}`);
}
}
async function createWallet(): Promise<void> {
const alias = inputValue("#createWalletAliasInput");
if (alias.length === 0) {
setText("#walletCreateStatus", "Alias requis");
return;
}
const request: DemoWalletCreateRequest = { alias };
setText("#walletCreateStatus", "Création...");
try {
const created = await invoke<DemoWalletIdentityPayload>("demo_wallet_create_native", { request });
setText("#walletCreateStatus", `Créé : ${created.alias}`);
const addressInput = document.querySelector<HTMLInputElement>("#walletAddressInput");
if (addressInput) {
addressInput.value = created.public_key;
}
const aliasInput = document.querySelector<HTMLInputElement>("#createWalletAliasInput");
if (aliasInput) {
aliasInput.value = "";
}
await refreshWalletInventory();
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", `native wallet created: ${created.alias}`);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#walletCreateStatus", `Erreur : ${message}`);
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `Native wallet creation failed: ${message}`);
}
}
async function inspectTransferFile(): Promise<void> {
const sourcePath = inputValue("#walletImportSourcePathInput");
const format = selectedTransferFormat("#walletImportFormatSelect");
if (sourcePath.length === 0 || format.length === 0) {
setText("#walletTransferStatus", "Chemin source et format requis");
setTransferPublicKey("");
return;
}
const request: DemoWalletTransferInspectionRequest = { source_path: sourcePath, format };
setText("#walletTransferStatus", "Inspection...");
setTransferPublicKey("");
try {
const payload = await invoke<DemoWalletTransferInspectionPayload>("demo_wallet_inspect_transfer_file", { request });
setTransferPublicKey(payload.public_key);
setText("#walletTransferStatus", `Keypair valide — ${payload.format}`);
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", "external transfer keypair inspected");
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#walletTransferStatus", `Erreur : ${message}`);
setTransferPublicKey("");
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `External transfer inspection failed: ${message}`);
}
}
async function importTransferFile(): Promise<void> {
const sourcePath = inputValue("#walletImportSourcePathInput");
const format = selectedTransferFormat("#walletImportFormatSelect");
const alias = inputValue("#walletImportAliasInput");
if (sourcePath.length === 0 || format.length === 0 || alias.length === 0) {
setText("#walletTransferStatus", "Chemin source, format et alias requis");
return;
}
const request: DemoWalletImportRequest = { alias, source_path: sourcePath, format };
setText("#walletTransferStatus", "Import...");
try {
const payload = await invoke<DemoWalletIdentityPayload>("demo_wallet_import_file", { request });
setTransferPublicKey(payload.public_key);
setText("#walletTransferStatus", `Importé : ${payload.alias}.kswallet`);
const addressInput = document.querySelector<HTMLInputElement>("#walletAddressInput");
if (addressInput) {
addressInput.value = payload.public_key;
}
await refreshWalletInventory();
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", `external keypair imported: ${payload.alias}`);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#walletTransferStatus", `Erreur : ${message}`);
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `External keypair import failed: ${message}`);
}
}
async function exportWallet(): Promise<void> {
const alias = document.querySelector<HTMLSelectElement>("#walletExportAliasSelect")?.value.trim() ?? "";
const format = selectedTransferFormat("#walletExportFormatSelect");
const fileName = inputValue("#walletExportFileNameInput");
if (alias.length === 0 || format.length === 0 || fileName.length === 0) {
setText("#walletExportStatus", "Wallet, format et nom de fichier requis");
return;
}
const request: DemoWalletExportRequest = { alias, file_name: fileName, format };
setText("#walletExportStatus", "Export...");
try {
const exportedPath = await invoke<string>("demo_wallet_export_file", { request });
setText("#walletExportStatus", `Exporté : ${exportedPath}`);
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", `native wallet exported: ${alias}`);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#walletExportStatus", `Erreur : ${message}`);
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `Native wallet export failed: ${message}`);
}
}
function exploreInspectedTransfer(): void {
if (inspectedTransferPublicKey.length === 0) {
return;
}
const addressInput = document.querySelector<HTMLInputElement>("#walletAddressInput");
if (addressInput) {
addressInput.value = inspectedTransferPublicKey;
}
document.querySelector<HTMLElement>("#walletAddressInput")?.scrollIntoView({ behavior: "smooth", block: "center" });
}
function parseTokenAccounts(value: unknown, program: string): ParsedTokenAccount[] {
if (typeof value !== "object" || value === null || !("value" in value)) {
return [];
}
const rows = (value as { value?: unknown }).value;
if (!Array.isArray(rows)) {
return [];
}
const parsed: ParsedTokenAccount[] = [];
for (const row of rows) {
if (typeof row !== "object" || row === null) {
continue;
}
const keyed = row as { pubkey?: unknown; account?: { data?: unknown } };
if (typeof keyed.pubkey !== "string" || typeof keyed.account?.data !== "object" || keyed.account.data === null) {
continue;
}
const data = keyed.account.data as { parsed?: { info?: unknown } };
const info = data.parsed?.info;
if (typeof info !== "object" || info === null) {
continue;
}
const typedInfo = info as { mint?: unknown; tokenAmount?: unknown };
const amount = typedInfo.tokenAmount;
if (typeof typedInfo.mint !== "string" || typeof amount !== "object" || amount === null) {
continue;
}
const typedAmount = amount as { amount?: unknown; decimals?: unknown; uiAmountString?: unknown };
if (typeof typedAmount.amount !== "string" || typeof typedAmount.decimals !== "number" || typeof typedAmount.uiAmountString !== "string") {
continue;
}
parsed.push({
program,
mint: typedInfo.mint,
tokenAccount: keyed.pubkey,
uiAmount: typedAmount.uiAmountString,
rawAmount: typedAmount.amount,
decimals: typedAmount.decimals,
});
}
return parsed;
}
function renderTokenRows(tokens: ReadonlyArray<ParsedTokenAccount>): void {
const body = document.querySelector<HTMLTableSectionElement>("#walletTokensTableBody");
if (!body) {
return;
}
body.replaceChildren();
if (tokens.length === 0) {
const row = document.createElement("tr");
const cell = document.createElement("td");
cell.colSpan = 5;
cell.className = "text-body-secondary text-center py-3";
cell.textContent = "Aucun compte token trouvé.";
row.appendChild(cell);
body.appendChild(row);
return;
}
for (const token of tokens) {
const row = document.createElement("tr");
for (const value of [token.program, token.mint, token.tokenAccount, token.uiAmount, token.rawAmount]) {
const cell = document.createElement("td");
cell.textContent = value;
if (value === token.mint || value === token.tokenAccount) {
cell.className = "font-monospace text-break";
}
row.appendChild(cell);
}
body.appendChild(row);
}
}
function parseSignatureRows(value: unknown): ParsedSignatureInfo[] {
if (!Array.isArray(value)) {
return [];
}
const rows: ParsedSignatureInfo[] = [];
for (const item of value) {
if (typeof item !== "object" || item === null) {
continue;
}
const typed = item as Record<string, unknown>;
if (typeof typed.signature !== "string" || typeof typed.slot !== "number") {
continue;
}
rows.push({
signature: typed.signature,
slot: typed.slot,
err: typed.err ?? null,
memo: typeof typed.memo === "string" ? typed.memo : null,
blockTime: typeof typed.blockTime === "number" ? typed.blockTime : null,
confirmationStatus: typeof typed.confirmationStatus === "string" ? typed.confirmationStatus : null,
});
}
return rows;
}
function formatBlockTime(blockTime: number | null): string {
if (blockTime === null) {
return "—";
}
return new Date(blockTime * 1000).toLocaleString();
}
async function loadTransactionDetails(profile: string, signature: string): Promise<void> {
renderJsonViewer("#walletTransactionJson", { status: "loading", signature });
try {
const response = await executeWalletRpc(profile, "getTransaction", [
signature,
{ commitment: "confirmed", encoding: "jsonParsed", maxSupportedTransactionVersion: 0 },
]);
renderJsonViewer("#walletTransactionJson", parseJsonResponse(response));
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
renderJsonViewer("#walletTransactionJson", { status: "error", message, signature });
}
}
function renderSignatureRows(rows: ReadonlyArray<ParsedSignatureInfo>, profile: string): void {
const body = document.querySelector<HTMLTableSectionElement>("#walletTransactionsTableBody");
if (!body) {
return;
}
body.replaceChildren();
if (rows.length === 0) {
const row = document.createElement("tr");
const cell = document.createElement("td");
cell.colSpan = 6;
cell.className = "text-body-secondary text-center py-3";
cell.textContent = "Aucune transaction trouvée.";
row.appendChild(cell);
body.appendChild(row);
return;
}
for (const item of rows) {
const row = document.createElement("tr");
const signatureCell = document.createElement("td");
const slotCell = document.createElement("td");
const timeCell = document.createElement("td");
const statusCell = document.createElement("td");
const confirmationCell = document.createElement("td");
const actionCell = document.createElement("td");
const detailsButton = document.createElement("button");
signatureCell.textContent = item.signature;
signatureCell.className = "font-monospace text-break";
slotCell.textContent = String(item.slot);
timeCell.textContent = formatBlockTime(item.blockTime);
statusCell.textContent = item.err === null ? "OK" : "Erreur";
confirmationCell.textContent = item.confirmationStatus ?? "—";
detailsButton.type = "button";
detailsButton.className = "btn btn-sm btn-outline-primary";
detailsButton.textContent = "Détails";
detailsButton.addEventListener("click", () => {
void loadTransactionDetails(profile, item.signature);
});
actionCell.appendChild(detailsButton);
row.append(signatureCell, slotCell, timeCell, statusCell, confirmationCell, actionCell);
body.appendChild(row);
}
}
async function loadWalletOnchain(): Promise<void> {
const profile = selectedOnchainProfile();
const address = inputValue("#walletAddressInput");
if (profile.length === 0) {
setText("#walletOnchainStatus", "Profil RPC requis");
return;
}
if (address.length === 0) {
setText("#walletOnchainStatus", "Adresse requise");
return;
}
const requestedLimit = Number.parseInt(inputValue("#walletSignatureLimitInput"), 10);
const signatureLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(100, requestedLimit)) : 20;
setText("#walletOnchainStatus", "Chargement...");
setText("#walletTokenStatus", "Chargement...");
setText("#walletTransactionStatus", "Chargement...");
setText("#walletSolBalance", "—");
setText("#walletLamportBalance", "—");
setText("#walletTokenCount", "—");
setText("#walletTransactionCount", "—");
renderTokenRows([]);
renderSignatureRows([], profile);
renderJsonViewer("#walletTransactionJson", { status: "idle", message: "Sélectionner une transaction." });
const operations: Promise<void>[] = [];
operations.push((async () => {
try {
const balance = await invoke<DemoWalletBalancePayload>("demo_wallet_balance", { profile, address });
setText("#walletSolBalance", balance.sol);
setText("#walletLamportBalance", balance.lamports);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#walletSolBalance", "Erreur");
setText("#walletLamportBalance", message);
}
})());
operations.push((async () => {
try {
if (!walletInventory) {
throw new Error("Inventaire wallet non chargé.");
}
const [classic, token2022] = await Promise.all([
executeWalletRpc(profile, "getTokenAccountsByOwner", [
address,
{ programId: walletInventory.token_program_id },
{ encoding: "jsonParsed", commitment: "confirmed" },
]),
executeWalletRpc(profile, "getTokenAccountsByOwner", [
address,
{ programId: walletInventory.token_2022_program_id },
{ encoding: "jsonParsed", commitment: "confirmed" },
]),
]);
const tokens = [
...parseTokenAccounts(parseJsonResponse(classic), "SPL Token"),
...parseTokenAccounts(parseJsonResponse(token2022), "Token-2022"),
];
tokens.sort((left, right) => left.mint.localeCompare(right.mint) || left.tokenAccount.localeCompare(right.tokenAccount));
setText("#walletTokenCount", String(tokens.length));
setText("#walletTokenStatus", "OK");
renderTokenRows(tokens);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#walletTokenCount", "0");
setText("#walletTokenStatus", `Erreur : ${message}`);
renderTokenRows([]);
}
})());
operations.push((async () => {
try {
const history = await executeWalletRpc(profile, "getSignaturesForAddress", [
address,
{ commitment: "confirmed", limit: signatureLimit },
]);
const rows = parseSignatureRows(parseJsonResponse(history));
setText("#walletTransactionCount", String(rows.length));
setText("#walletTransactionStatus", "OK");
renderSignatureRows(rows, profile);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#walletTransactionCount", "0");
setText("#walletTransactionStatus", `Erreur : ${message}`);
renderSignatureRows([], profile);
}
})());
await Promise.all(operations);
const selected = selectedOnchainProfilePayload();
setText("#walletOnchainStatus", `Terminé — ${selected?.name ?? profile} / ${selected?.cluster ?? "cluster inconnu"}`);
}
async function inspectWalletFile(): Promise<void> {
const path = inputValue("#walletExternalPathInput");
if (path.length === 0) {
setText("#walletInspectionStatus", "Chemin requis");
renderJsonViewer("#walletInspectionJson", { status: "error", message: "Saisir un chemin vers un fichier .kswallet." });
return;
}
setText("#walletInspectionStatus", "Inspection...");
renderJsonViewer("#walletInspectionJson", { status: "loading", message: "Inspection du wallet natif." });
try {
const payload = await invoke<DemoWalletIdentityPayload>("demo_wallet_inspect_file", { path });
setText("#walletInspectionStatus", "OK");
renderJsonViewer("#walletInspectionJson", payload);
const addressInput = document.querySelector<HTMLInputElement>("#walletAddressInput");
if (addressInput) {
addressInput.value = payload.public_key;
}
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", "external wallet inspected");
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#walletInspectionStatus", `Erreur : ${message}`);
renderJsonViewer("#walletInspectionJson", { status: "error", message });
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `External wallet inspection failed: ${message}`);
}
}
function clearWalletInspection(): void {
const pathInput = document.querySelector<HTMLInputElement>("#walletExternalPathInput");
if (pathInput) {
pathInput.value = "";
}
setText("#walletInspectionStatus", "En attente");
renderJsonViewer("#walletInspectionJson", { status: "idle", message: "Aucun fichier inspecté." });
}
document.addEventListener("DOMContentLoaded", () => {
installFrontendConsoleBridge("kb-app-demo-desktop.frontend.demo_wallet");
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", "wallet demo window loaded");
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
document.querySelector<HTMLButtonElement>("#refreshWalletInventoryButton")?.addEventListener("click", () => {
void refreshWalletInventory();
});
document.querySelector<HTMLButtonElement>("#applyWalletExecutionSelectionButton")?.addEventListener("click", () => {
void applyExecutionWalletSelection();
});
document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect")?.addEventListener("change", () => {
updateExecutionSelectionDisplay();
if (walletInventory) {
renderWalletRows(walletInventory.wallets);
}
setText("#walletExecutionSelectionStatus", "En attente");
});
document.querySelector<HTMLButtonElement>("#createWalletButton")?.addEventListener("click", () => {
void createWallet();
});
document.querySelector<HTMLInputElement>("#createWalletAliasInput")?.addEventListener("keydown", event => {
if (event.key === "Enter") {
event.preventDefault();
void createWallet();
}
});
document.querySelector<HTMLButtonElement>("#inspectWalletTransferButton")?.addEventListener("click", () => {
void inspectTransferFile();
});
document.querySelector<HTMLButtonElement>("#importWalletTransferButton")?.addEventListener("click", () => {
void importTransferFile();
});
document.querySelector<HTMLButtonElement>("#exploreWalletTransferButton")?.addEventListener("click", () => {
exploreInspectedTransfer();
});
document.querySelector<HTMLButtonElement>("#exportWalletTransferButton")?.addEventListener("click", () => {
void exportWallet();
});
document.querySelector<HTMLSelectElement>("#walletExportAliasSelect")?.addEventListener("change", () => {
updateExportFileName(false);
});
document.querySelector<HTMLSelectElement>("#walletExportFormatSelect")?.addEventListener("change", () => {
updateExportFileName(false);
});
document.querySelector<HTMLInputElement>("#walletExportFileNameInput")?.addEventListener("input", event => {
const input = event.currentTarget as HTMLInputElement;
input.dataset.autoGenerated = "false";
});
document.querySelector<HTMLInputElement>("#walletImportSourcePathInput")?.addEventListener("input", () => {
setTransferPublicKey("");
setText("#walletTransferStatus", "En attente");
});
document.querySelector<HTMLSelectElement>("#walletImportFormatSelect")?.addEventListener("change", () => {
setTransferPublicKey("");
setText("#walletTransferStatus", "En attente");
});
document.querySelector<HTMLSelectElement>("#walletOnchainProfileSelect")?.addEventListener("change", () => {
updateOnchainClusterLabel();
});
document.querySelector<HTMLButtonElement>("#loadWalletOnchainButton")?.addEventListener("click", () => {
void loadWalletOnchain();
});
document.querySelector<HTMLInputElement>("#walletAddressInput")?.addEventListener("keydown", event => {
if (event.key === "Enter") {
event.preventDefault();
void loadWalletOnchain();
}
});
document.querySelector<HTMLButtonElement>("#inspectWalletFileButton")?.addEventListener("click", () => {
void inspectWalletFile();
});
document.querySelector<HTMLButtonElement>("#clearWalletInspectionButton")?.addEventListener("click", () => {
clearWalletInspection();
});
document.querySelector<HTMLInputElement>("#walletExternalPathInput")?.addEventListener("keydown", event => {
if (event.key === "Enter") {
event.preventDefault();
void inspectWalletFile();
}
});
void refreshWalletInventory();
});