v0.1.0-pre.053

This commit is contained in:
2026-07-25 23:57:48 +02:00
parent 930056d422
commit 4e76263f88
23 changed files with 4575 additions and 82 deletions

View File

@@ -1,5 +1,5 @@
# file: kb-app-demo-desktop/Cargo.toml
# version: 3
# version: 4
[package]
name = "kb-app-demo-desktop"
@@ -25,15 +25,18 @@ chrono.workspace = true
fs2.workspace = true
kb-config = { path = "../kb-config" }
kb-core = { path = "../kb-core" }
kb-lib = { path = "../kb-lib" }
kb-logging = { path = "../kb-logging" }
kb-lib = { path = "../kb-lib" }
kb-program-ids = { path = "../kb-program-ids" }
kb-store = { path = "../kb-store" }
kb-wallet = { path = "../kb-wallet" }
kb-pipeline = { path = "../kb-pipeline" }
kb-pipeline-demo-scenarios = { path = "../kb-pipeline-demo-scenarios" }
kb-onchain-transport = { path = "../kb-onchain-transport" }
serde.workspace = true
serde_json.workspace = true
spl-associated-token-account-interface.workspace = true
solana-pubkey.workspace = true
rustls.workspace = true
tauri.workspace = true
tauri-plugin-tracing.workspace = true

View File

@@ -14,7 +14,9 @@
"demo_sql_replay_candidates",
"demo_config",
"demo_core_extraction",
"demo_decode_replay"
"demo_decode_replay",
"demo_execution_solana_core",
"demo_execution_spl"
],
"permissions": [
"core:default",

View File

@@ -0,0 +1,73 @@
<!-- file: kb-app-demo-desktop/frontend/demo_execution_solana_core.html -->
<!-- version: 6 -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Exécution Solana Core Devnet</title>
<link href="sass/main.scss" rel="stylesheet"/>
</head>
<body class="app-body">
<header class="app-header bg-dark text-light">
<nav class="container-fluid h-100 d-flex align-items-center justify-content-between px-4">
<div>
<h1 class="h4 mb-0">Exécution Solana Core Devnet</h1><small class="text-light-emphasis">System Program uniquement — simulation-first et replay post-exécution</small>
</div>
<div class="d-flex align-items-center gap-2"><span class="badge text-bg-success" id="executionStatusBadge">Prêt</span><button class="btn btn-sm btn-outline-warning" disabled="" id="cancelExecutionButton" type="button">Annuler</button></div>
</nav>
</header>
<main class="app-main">
<div class="osb-scrollable pt-1 pb-4" data-simplebar="">
<div class="container-fluid py-4 px-4">
<div class="alert alert-warning">Cette fenêtre est strictement réservée aux opérations Solana Core. Les opérations SPL sont disponibles dans « Exécution SPL Devnet ».</div>
<div class="row g-4">
<div class="col-12 col-xl-5">
<div class="card shadow-sm border-0"><div class="card-body"><div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3"><h2 class="h5 mb-0">System Program</h2><span class="badge text-bg-primary">solana_native_classifier</span></div>
<div class="mb-3"><label class="form-label" for="executionProfileSelect">Profil Devnet</label><select class="form-select" id="executionProfileSelect"></select>
<div class="form-text" id="executionProfileHelp"></div>
</div>
<div class="mb-3"><label class="form-label" for="executionRecipientInput">Destinataire</label>
<div class="input-group"><input autocomplete="off" class="form-control font-monospace" id="executionRecipientInput" type="text"/><button class="btn btn-outline-secondary" id="generateRecipientButton" type="button">Générer</button></div>
</div>
<div class="row g-3">
<div class="col-6"><label class="form-label" for="executionLamportsInput">Lamports</label><input class="form-control" id="executionLamportsInput" min="1" step="1" type="number"/></div>
<div class="col-6"><label class="form-label" for="executionAirdropInput">Airdrop maximal</label><input class="form-control" id="executionAirdropInput" min="0" step="1" type="number" value="0"/></div>
</div>
<hr/>
<div class="form-check form-switch mb-2"><input class="form-check-input" id="executionOperatorConfirmedInput" type="checkbox"/><label class="form-check-label" for="executionOperatorConfirmedInput">Confirmation opérateur explicite</label></div>
<div class="form-check form-switch mb-2"><input class="form-check-input" id="executionForceReplayInput" type="checkbox"/><label class="form-check-label" for="executionForceReplayInput">Forcer le replay post-exécution</label></div>
<div class="form-check form-switch mb-3"><input class="form-check-input" id="executionMaterializeInput" type="checkbox"/><label class="form-check-label" for="executionMaterializeInput">Matérialiser après décodage</label></div>
<div class="d-grid gap-2 d-md-flex"><button class="btn btn-outline-primary flex-fill" id="simulateExecutionButton" type="button">Simuler uniquement</button><button class="btn btn-danger flex-fill" id="submitExecutionButton" type="button">Signer et envoyer</button></div>
</div></div>
<div class="card shadow-sm border-0 mt-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<h2 class="h5 mb-0">Journal</h2><button class="btn btn-sm btn-outline-secondary" id="clearExecutionLogButton" type="button">Effacer</button>
</div><textarea class="form-control font-monospace" id="executionLogOutput" readonly="" rows="18"></textarea>
</div>
</div>
</div>
<div class="col-12 col-xl-7">
<div class="card shadow-sm border-0">
<div class="card-body">
<h2 class="h5 mb-3">Résumé</h2><textarea class="form-control font-monospace" id="executionSummaryOutput" readonly="" rows="14">Aucun résultat.</textarea>
</div>
</div>
<div class="card shadow-sm border-0 mt-4">
<div class="card-body">
<h2 class="h5">Plan exact</h2><textarea class="form-control font-monospace" id="executionPlanOutput" readonly="" rows="12">Aucun plan.</textarea>
<h2 class="h5 mt-3">Simulation exacte</h2><textarea class="form-control font-monospace" id="executionSimulationOutput" readonly="" rows="12">Aucune simulation.</textarea>
<h2 class="h5 mt-3">Confirmation et replay</h2><textarea class="form-control font-monospace" id="executionDiagnosticsOutput" readonly="" rows="12">Aucun diagnostic.</textarea>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<script defer="" src="ts/demo_execution_solana_core.ts" type="module"></script>
</body>
</html>

View File

@@ -0,0 +1,433 @@
<!-- file: kb-app-demo-desktop/frontend/demo_execution_spl.html -->
<!-- version: 8 -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Exécution SPL Devnet</title>
<link href="sass/main.scss" rel="stylesheet" />
</head>
<body class="app-body">
<header class="app-header bg-dark text-light">
<nav class="container-fluid h-100 d-flex align-items-center justify-content-between px-4">
<div class="d-flex align-items-center gap-3">
<div class="navbar-brand d-flex align-items-center mb-0">
<img alt="Logo" src="imgs/logo.png" class="app-logo" />
<span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3</span>
</div>
<div>
<h1 class="h4 mb-0">Exécution SPL Devnet</h1>
<small class="text-light-emphasis">Memo, SPL Token classique, Token-2022 et ATA — simulation-first et replay post-exécution</small>
</div>
</div>
<div class="d-flex align-items-center gap-2">
<span class="badge text-bg-success" id="executionStatusBadge">Prêt</span>
<button class="btn btn-sm btn-outline-warning" disabled="" id="cancelExecutionButton" type="button">Annuler</button>
</div>
</nav>
</header>
<main class="app-main">
<div class="osb-scrollable pt-1 pb-4" data-simplebar="">
<div class="container-fluid py-4 px-4">
<div class="alert alert-warning" role="alert">
Cette fenêtre est limitée à Devnet. La clé privée du wallet source reste dans le backend Rust et nest jamais transmise à la WebView.
</div>
<div class="row g-4" id="splExecutionWorkspace">
<div class="col-12 col-xl-5" id="splExecutionControls">
<div class="accordion shadow-sm" id="walletPreparationAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="walletPreparationHeading"><button aria-controls="walletPreparationCollapse" aria-expanded="true" class="accordion-button" data-bs-target="#walletPreparationCollapse" data-bs-toggle="collapse" type="button"><span class="d-flex flex-wrap align-items-center justify-content-between gap-2 w-100 me-2"><span>Préparation commune du wallet Devnet</span><span class="badge text-bg-info">Pré-requis</span></span></button></h2>
<div aria-labelledby="walletPreparationHeading" class="accordion-collapse collapse show" id="walletPreparationCollapse">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label" for="executionProfileSelect">Profil Devnet</label>
<select class="form-select" id="executionProfileSelect"></select>
<div class="form-text" id="executionProfileHelp"></div>
</div>
<div class="mb-3">
<label class="form-label" for="executionRecipientInput">Destinataire</label>
<div class="input-group">
<input autocomplete="off" class="form-control font-monospace" id="executionRecipientInput" type="text" />
<button class="btn btn-outline-secondary" id="generateRecipientButton" type="button">Générer</button>
</div>
<div class="form-text">Une adresse générée ici est jetable : sa clé privée nest ni conservée ni exposée.</div>
</div>
<div class="row g-3">
<div class="col-12 col-md-6">
<label class="form-label" for="executionLamportsInput">Lamports transférés</label>
<input class="form-control" id="executionLamportsInput" min="1" step="1" type="number" />
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="executionAirdropInput">Airdrop maximal</label>
<input class="form-control" id="executionAirdropInput" min="0" step="1" type="number" value="0" />
</div>
</div>
<hr />
<div class="form-check form-switch mb-2">
<input class="form-check-input" id="executionOperatorConfirmedInput" type="checkbox" />
<label class="form-check-label" for="executionOperatorConfirmedInput">Confirmation opérateur explicite</label>
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" id="executionForceReplayInput" type="checkbox" />
<label class="form-check-label" for="executionForceReplayInput">Forcer le replay post-exécution</label>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" id="executionMaterializeInput" type="checkbox" />
<label class="form-check-label" for="executionMaterializeInput">Matérialiser après décodage</label>
</div>
<div class="d-grid gap-2 d-md-flex">
<button class="btn btn-outline-primary flex-fill" id="simulateExecutionButton" type="button">Simuler uniquement</button>
<button class="btn btn-danger flex-fill" id="submitExecutionButton" type="button">Signer et envoyer</button>
</div>
</div>
</div>
</div>
</div>
<div class="d-flex align-items-center justify-content-between mt-4 mb-2">
<h2 class="h5 mb-0">Exécutions par décodeur</h2><small class="text-body-secondary">Ouvrir une seule famille à la fois</small>
</div>
<div class="accordion shadow-sm mt-4" id="decoderExecutionAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="splMemoDecoderHeading"><button aria-controls="splMemoDecoderCollapse" aria-expanded="false" class="accordion-button collapsed" data-bs-target="#splMemoDecoderCollapse" data-bs-toggle="collapse" type="button"><span class="d-flex flex-wrap align-items-center justify-content-between gap-2 w-100 me-2"><span>Décodeur spl_memo — Memo v4</span><span class="badge text-bg-primary">Exécutable</span></span></button></h2>
<div aria-labelledby="splMemoDecoderHeading" class="accordion-collapse collapse" data-bs-parent="#decoderExecutionAccordion" id="splMemoDecoderCollapse">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label" for="memoMessageInput">Annotation UTF-8 exacte</label>
<textarea class="form-control" id="memoMessageInput" maxlength="566" rows="5">khadhroony-bot2 Memo v4 validation</textarea>
<div class="form-text">566 octets UTF-8 maximum. La matérialisation `transaction_annotation` et le second replay didempotence sont obligatoires après envoi.</div>
</div>
<div class="form-check form-switch mb-3">
<input checked="" class="form-check-input" id="memoWalletSignerInput" type="checkbox" />
<label class="form-check-label" for="memoWalletSignerInput">Inclure le wallet comme signer Memo readonly</label>
</div>
<div class="alert alert-secondary py-2 small" role="note">
Ce parcours réutilise le profil, la confirmation opérateur et loption de force replay du panneau System ci-dessus. Aucune clé privée ne traverse la WebView.
</div>
<div class="d-grid gap-2 d-md-flex">
<button class="btn btn-outline-info flex-fill" id="simulateMemoButton" type="button">Simuler Memo</button>
<button class="btn btn-danger flex-fill" id="submitMemoButton" type="button">Signer et envoyer Memo</button>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="splTokenDecoderHeading"><button aria-controls="splTokenDecoderCollapse" aria-expanded="false" class="accordion-button collapsed" data-bs-target="#splTokenDecoderCollapse" data-bs-toggle="collapse" type="button"><span class="d-flex flex-wrap align-items-center justify-content-between gap-2 w-100 me-2"><span>Décodeur spl_token — TransferChecked</span><span class="badge text-bg-primary">Exécutable</span></span></button></h2>
<div aria-labelledby="splTokenDecoderHeading" class="accordion-collapse collapse" data-bs-parent="#decoderExecutionAccordion" id="splTokenDecoderCollapse">
<div class="accordion-body">
<div class="alert alert-secondary py-2 small" role="note">
Le mint et les decimals sont explicites. Le préflight vérifie les deux comptes, le solde et lautorité avant toute simulation. Lenvoi réutilise le profil et la confirmation opérateur communs.
</div>
<div class="mb-3">
<label class="form-label" for="tokenSourceInput">Compte source</label>
<input autocomplete="off" class="form-control font-monospace" id="tokenSourceInput" type="text" />
</div>
<div class="mb-3">
<label class="form-label" for="tokenMintInput">Mint</label>
<input autocomplete="off" class="form-control font-monospace" id="tokenMintInput" type="text" />
</div>
<div class="mb-3">
<label class="form-label" for="tokenDestinationInput">Compte destination</label>
<input autocomplete="off" class="form-control font-monospace" id="tokenDestinationInput" type="text" />
</div>
<div class="mb-3">
<label class="form-label" for="tokenAuthorityInput">Autorité simple</label>
<input autocomplete="off" class="form-control font-monospace" id="tokenAuthorityInput" type="text" />
<div class="form-text">Doit correspondre au wallet persistant du profil pour un envoi.</div>
</div>
<div class="row g-3 mb-3">
<div class="col-12 col-md-7">
<label class="form-label" for="tokenAmountRawInput">Montant brut exact</label>
<input class="form-control font-monospace" id="tokenAmountRawInput" inputmode="numeric" type="text" value="1" />
</div>
<div class="col-12 col-md-5">
<label class="form-label" for="tokenDecimalsInput">Decimals</label>
<input class="form-control" id="tokenDecimalsInput" max="255" min="0" step="1" type="number" value="9" />
</div>
</div>
<div class="d-grid gap-2 d-md-flex">
<button class="btn btn-outline-primary flex-fill" id="simulateTokenButton" type="button">Préflight et simuler</button>
<button class="btn btn-danger flex-fill" id="submitTokenButton" type="button">Signer et envoyer Token</button>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="splAtaDecoderHeading"><button aria-controls="splAtaDecoderCollapse" aria-expanded="false" class="accordion-button collapsed" data-bs-target="#splAtaDecoderCollapse" data-bs-toggle="collapse" type="button"><span class="d-flex flex-wrap align-items-center justify-content-between gap-2 w-100 me-2"><span>Décodeur spl_associated_token_account — Create / CreateIdempotent</span><span class="badge text-bg-primary">Exécutable</span></span></button></h2>
<div aria-labelledby="splAtaDecoderHeading" class="accordion-collapse collapse" data-bs-parent="#decoderExecutionAccordion" id="splAtaDecoderCollapse">
<div class="accordion-body">
<div class="row g-3 mb-3">
<div class="col-12 col-md-6">
<label class="form-label" for="ataTokenProgramSelect">Token Program cible</label>
<select class="form-select" id="ataTokenProgramSelect">
<option value="classic">SPL Token classique</option>
<option value="token_2022">Token-2022</option>
</select>
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="ataModeSelect">Mode</label>
<select class="form-select" id="ataModeSelect">
<option value="create_idempotent">CreateIdempotent</option>
<option value="create">Create</option>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label" for="ataMintInput">Mint</label>
<input autocomplete="off" class="form-control font-monospace" id="ataMintInput" type="text" />
</div>
<div class="mb-3">
<label class="form-label" for="ataPayerInput">Payer readonly</label>
<input class="form-control font-monospace" id="ataPayerInput" readonly="" type="text" />
</div>
<div class="mb-3">
<label class="form-label" for="ataWalletOwnerInput">Wallet owner readonly</label>
<input class="form-control font-monospace" id="ataWalletOwnerInput" readonly="" type="text" />
</div>
<div class="mb-3">
<label class="form-label" for="ataDerivedInput">ATA dérivé readonly</label>
<input class="form-control font-monospace" id="ataDerivedInput" readonly="" type="text" />
<div class="form-text">Actualisé depuis le backend avec les seeds wallet, Token Program et mint.</div>
</div>
<div class="d-grid gap-2 d-md-flex">
<button class="btn btn-outline-secondary flex-fill" id="deriveAtaButton" type="button">Dériver</button>
<button class="btn btn-outline-primary flex-fill" id="simulateAtaButton" type="button">Préflight et simuler</button>
<button class="btn btn-danger flex-fill" id="submitAtaButton" type="button">Confirmer et envoyer</button>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="splToken2022DecoderHeading"><button aria-controls="splToken2022DecoderCollapse" aria-expanded="false" class="accordion-button collapsed" data-bs-target="#splToken2022DecoderCollapse" data-bs-toggle="collapse" type="button"><span class="d-flex flex-wrap align-items-center justify-content-between gap-2 w-100 me-2"><span>Exécution spl_token_2022 — scénarios publics</span><span class="badge text-bg-success">Exécutable</span></span></button></h2>
<div aria-labelledby="splToken2022DecoderHeading" class="accordion-collapse collapse" data-bs-parent="#decoderExecutionAccordion" id="splToken2022DecoderCollapse">
<div class="accordion-body">
<div class="alert alert-info">Chaque opération est simulée sur le message exact avant toute signature. Les scénarios sont indépendants et réutilisent la fixture chargée dans les deux terminaux.</div>
<div class="d-grid mb-3"><button class="btn btn-outline-secondary" id="loadToken2022FixtureButton" type="button">Charger la fixture Token-2022</button></div>
<div class="form-text mb-3" id="token2022FixtureHelp">Aucune fixture chargée.</div>
<div class="mb-3"><label class="form-label" for="token2022ScenarioSelect">Scénario</label><select class="form-select" id="token2022ScenarioSelect">
<option value="token_2022_mint_to_checked">MintToChecked</option>
<option value="token_2022_transfer_checked">TransferChecked</option>
<option value="token_2022_approve_checked">ApproveChecked</option>
<option value="token_2022_revoke">Revoke</option>
<option value="token_2022_burn_checked">BurnChecked</option>
<option value="token_2022_freeze_account">FreezeAccount</option>
<option value="token_2022_thaw_account">ThawAccount</option>
<option value="token_2022_close_destination">CloseAccount</option>
</select></div>
<div class="mb-3"><label class="form-label" for="token2022SourceInput">Compte source / compte cible</label><input class="form-control font-monospace" id="token2022SourceInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token2022MintInput">Mint Token-2022</label><input class="form-control font-monospace" id="token2022MintInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token2022DestinationInput">Compte destination / destination lamports</label><input class="form-control font-monospace" id="token2022DestinationInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token2022DelegateInput">Delegate</label><input class="form-control font-monospace" id="token2022DelegateInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token2022AuthorityInput">Autorité</label><input class="form-control font-monospace" id="token2022AuthorityInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token2022FreezeAuthorityInput">Freeze authority</label><input class="form-control font-monospace" id="token2022FreezeAuthorityInput" type="text" /></div>
<div class="row g-3 mb-3">
<div class="col-8"><label class="form-label" for="token2022AmountInput">Montant brut</label><input class="form-control" id="token2022AmountInput" type="text" value="1" /></div>
<div class="col-4"><label class="form-label" for="token2022DecimalsInput">Décimales</label><input class="form-control" id="token2022DecimalsInput" max="255" min="0" type="number" value="9" /></div>
</div>
<div class="d-grid gap-2 d-md-flex">
<button class="btn btn-outline-primary flex-fill" id="simulateToken2022Button" type="button">Préflight et simuler</button>
<button class="btn btn-danger flex-fill" id="submitToken2022Button" type="button">Confirmer et envoyer</button>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="splElgamalRegistryDecoderHeading"><button aria-controls="splElgamalRegistryDecoderCollapse" aria-expanded="false" class="accordion-button collapsed" data-bs-target="#splElgamalRegistryDecoderCollapse" data-bs-toggle="collapse" type="button"><span class="d-flex flex-wrap align-items-center justify-content-between gap-2 w-100 me-2"><span>Décodeur spl_elgamal_registry — ElGamal Registry</span><span class="badge text-bg-primary">Plan exact</span></span></button></h2>
<div aria-labelledby="splElgamalRegistryDecoderHeading" class="accordion-collapse collapse" data-bs-parent="#decoderExecutionAccordion" id="splElgamalRegistryDecoderCollapse">
<div class="accordion-body">
<div class="alert alert-info">Première démo : dériver le Registry PDA et construire le plan exact avec un Proof Context State existant.</div>
<div class="mb-3"><label class="form-label" for="elgamalFeePayerInput">Fee payer</label><input class="form-control font-monospace" id="elgamalFeePayerInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="elgamalOwnerInput">Owner du registre</label><input class="form-control font-monospace" id="elgamalOwnerInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="elgamalOperationSelect">Opération</label><select class="form-select" id="elgamalOperationSelect">
<option value="create_registry">CreateRegistry</option>
<option value="update_registry">UpdateRegistry</option>
</select></div>
<div class="mb-3"><label class="form-label" for="elgamalProofContextInput">Proof Context State account</label><input class="form-control font-monospace" id="elgamalProofContextInput" type="text" /></div><button class="btn btn-outline-primary w-100" id="buildElgamalRegistryPlanButton" type="button">Construire le plan Registry</button>
</div>
</div>
</div>
</div>
<div class="alert alert-secondary mt-4 mb-0">
<strong>Scénarios avec preuve.</strong> CreateRegistry, UpdateRegistry et ConfigureAccountWithRegistry restent dans le panneau ElGamal/Confidential et exigent un Proof Context State valide. Ils ne sont pas présentés comme des scénarios publics exécutables.
</div>
</div>
<div class="col-12 col-xl-7" id="splExecutionResults">
<div class="d-flex align-items-center justify-content-between mb-2">
<h2 class="h5 mb-0">Résultats détaillés</h2><small class="text-body-secondary">Plan, simulation, confirmation et journaux métier</small>
</div>
<div class="accordion mt-4" id="executionDetailsAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" data-bs-target="#executionPlanCollapse" data-bs-toggle="collapse" type="button">Plan exact</button>
</h2>
<div class="accordion-collapse collapse" data-bs-parent="#executionDetailsAccordion" id="executionPlanCollapse">
<div class="accordion-body">
<textarea class="form-control font-monospace" id="executionPlanOutput" readonly="" rows="16">Aucun plan.</textarea>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" data-bs-target="#executionSimulationCollapse" data-bs-toggle="collapse" type="button">Simulation exacte</button>
</h2>
<div class="accordion-collapse collapse" data-bs-parent="#executionDetailsAccordion" id="executionSimulationCollapse">
<div class="accordion-body">
<textarea class="form-control font-monospace" id="executionSimulationOutput" readonly="" rows="16">Aucune simulation.</textarea>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" data-bs-target="#executionDiagnosticsCollapse" data-bs-toggle="collapse" type="button">Confirmation et replay</button>
</h2>
<div class="accordion-collapse collapse" data-bs-parent="#executionDetailsAccordion" id="executionDiagnosticsCollapse">
<div class="accordion-body">
<textarea class="form-control font-monospace" id="executionDiagnosticsOutput" readonly="" rows="16">Aucun diagnostic.</textarea>
</div>
</div>
</div>
</div>
<div class="accordion shadow-sm mt-4" id="splBusinessJournalsAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="splTokenJournalHeading"><button aria-controls="splTokenJournalCollapse" aria-expanded="false" class="accordion-button collapsed" data-bs-target="#splTokenJournalCollapse" data-bs-toggle="collapse" type="button">Journal métier SPL Token</button></h2>
<div aria-labelledby="splTokenJournalHeading" class="accordion-collapse collapse" data-bs-parent="#splBusinessJournalsAccordion" id="splTokenJournalCollapse">
<div class="accordion-body p-0" id="splTokenJournalHost">
<div class="card border-0 rounded-0">
<div class="card-body">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
<div>
<h2 class="h5 mb-0">Journal métier SPL Token</h2>
<div class="form-text">Événements commités uniquement, sans solde final reconstruit ni OHLC.</div>
</div>
<button class="btn btn-sm btn-outline-primary" id="refreshTokenJournalButton" type="button">Actualiser</button>
</div>
<div class="row g-2 mb-3">
<div class="col-12 col-lg-6">
<label class="form-label" for="tokenJournalSignatureInput">Signature contient</label>
<input class="form-control font-monospace" id="tokenJournalSignatureInput" type="text" />
</div>
<div class="col-12 col-lg-6">
<label class="form-label" for="tokenJournalMintInput">Mint exact</label>
<input class="form-control font-monospace" id="tokenJournalMintInput" type="text" />
</div>
<div class="col-12 col-lg-6">
<label class="form-label" for="tokenJournalAccountInput">Compte exact</label>
<input class="form-control font-monospace" id="tokenJournalAccountInput" type="text" />
</div>
<div class="col-6 col-lg-3">
<label class="form-label" for="tokenJournalFamilySelect">Famille</label>
<select class="form-select" id="tokenJournalFamilySelect">
<option value="">Toutes</option>
<option value="token_account">token_account</option>
<option value="admin">admin</option>
<option value="risk">risk</option>
</select>
</div>
<div class="col-6 col-lg-3">
<label class="form-label" for="tokenJournalLimitInput">Limite</label>
<input class="form-control" id="tokenJournalLimitInput" max="500" min="1" step="1" type="number" value="100" />
</div>
<div class="col-12">
<label class="form-label" for="tokenJournalOperationInput">Opération exacte</label>
<input class="form-control font-monospace" id="tokenJournalOperationInput" placeholder="transfer_checked" type="text" />
</div>
</div>
<div class="table-responsive">
<table class="table table-sm table-hover align-middle">
<thead>
<tr>
<th>Slot</th>
<th>Opération</th>
<th>Famille</th>
<th>Montant brut</th>
<th>Signature</th>
</tr>
</thead>
<tbody id="tokenJournalRows">
<tr>
<td class="text-body-secondary" colspan="5">Aucune lecture.</td>
</tr>
</tbody>
</table>
</div>
<label class="form-label" for="tokenJournalDetailOutput">Détail JSON de la ligne sélectionnée</label>
<textarea class="form-control font-monospace" id="tokenJournalDetailOutput" readonly="" rows="14">Aucun événement sélectionné.</textarea>
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="ataLifecycleJournalHeading"><button aria-controls="ataLifecycleJournalCollapse" aria-expanded="false" class="accordion-button collapsed" data-bs-target="#ataLifecycleJournalCollapse" data-bs-toggle="collapse" type="button">Journal lifecycle ATA</button></h2>
<div aria-labelledby="ataLifecycleJournalHeading" class="accordion-collapse collapse" data-bs-parent="#splBusinessJournalsAccordion" id="ataLifecycleJournalCollapse">
<div class="accordion-body p-0" id="ataLifecycleJournalHost">
<div class="card border-0 rounded-0">
<div class="card-body">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
<div>
<h2 class="h5 mb-0">Journal lifecycle ATA</h2>
<div class="form-text">Création, réutilisation idempotente et récupération nested, sans duplication des CPI Token.</div>
</div>
<button class="btn btn-sm btn-outline-primary" id="refreshAtaJournalButton" type="button">Actualiser</button>
</div>
<div class="row g-2 mb-3">
<div class="col-12 col-lg-6">
<label class="form-label" for="ataJournalSignatureInput">Signature contient</label>
<input class="form-control font-monospace" id="ataJournalSignatureInput" type="text" />
</div>
<div class="col-12 col-lg-6">
<label class="form-label" for="ataJournalMintInput">Mint exact</label>
<input class="form-control font-monospace" id="ataJournalMintInput" type="text" />
</div>
<div class="col-12 col-lg-6">
<label class="form-label" for="ataJournalAddressInput">ATA exact</label>
<input class="form-control font-monospace" id="ataJournalAddressInput" type="text" />
</div>
<div class="col-8 col-lg-4">
<label class="form-label" for="ataJournalOperationInput">Opération</label>
<input class="form-control font-monospace" id="ataJournalOperationInput" type="text" />
</div>
<div class="col-4 col-lg-2">
<label class="form-label" for="ataJournalLimitInput">Limite</label>
<input class="form-control" id="ataJournalLimitInput" max="500" min="1" type="number" value="100" />
</div>
</div>
<textarea class="form-control font-monospace" id="ataJournalOutput" readonly="" rows="14">Aucune lecture.</textarea>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0 mt-4">
<div class="card-body">
<h2 class="h5 mb-3">Résumé</h2>
<textarea class="form-control font-monospace" id="executionSummaryOutput" readonly="" rows="14">Aucun résultat.</textarea>
</div>
</div>
<div class="card shadow-sm border-0 mt-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<h2 class="h5 mb-0">Journal</h2>
<button class="btn btn-sm btn-outline-secondary" id="clearExecutionLogButton" type="button">Effacer</button>
</div>
<textarea class="form-control font-monospace" id="executionLogOutput" readonly="" rows="18"></textarea>
</div>
</div>
</div>
</div>
</main>
<footer class="app-footer bg-dark text-light">
<div class="container h-100 d-flex align-items-center">
<div class="row flex-grow-1 align-items-center">
<div class="col-12 text-center text-small my-1 my-md-0">© 2026 SASEDEV</div>
</div>
</div>
</footer>
<script defer="" src="ts/demo_execution_spl.ts" type="module"></script>
</body>
</html>

View File

@@ -14,10 +14,10 @@
<header class="app-header">
<nav class="navbar navbar-expand-lg h-100 py-0 bg-light text-dark">
<div class="container my-0">
<a class="navbar-brand d-flex align-items-center" href="main.html">
<div class="navbar-brand d-flex align-items-center">
<img alt="Logo" src="imgs/logo.png" class="app-logo" />
<span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3</span>
</span>
</div>
<div class="ms-auto d-flex align-items-center gap-2">
<span class="badge text-bg-primary">SQL diagnostics</span>
</div>
@@ -29,7 +29,7 @@
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
<div class="container py-4">
<div class="row g-4">
<div class="col-12">
<div class="col-12 col-xl-6">
<div class="card shadow-sm border-0">
<div class="card-body">
<div class="d-flex flex-wrap gap-2 justify-content-between align-items-center mb-3">
@@ -61,7 +61,7 @@
</div>
</div>
<div class="col-12">
<div class="col-12 col-xl-6">
<div class="card shadow-sm border-0">
<div class="card-body">
<h2 class="h5 card-title mb-3">Tables raw/core connues</h2>

View File

@@ -14,10 +14,10 @@
<header class="app-header">
<nav class="navbar navbar-expand-lg h-100 py-0 bg-light text-dark">
<div class="container my-0">
<a class="navbar-brand d-flex align-items-center" href="main.html">
<div class="navbar-brand d-flex align-items-center">
<img alt="Logo" src="imgs/logo.png" class="app-logo" />
<span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3</span>
</span>
</div>
<div class="ms-auto d-flex align-items-center gap-2">
<span class="badge text-bg-primary">PostgreSQL core</span>
</div>
@@ -29,7 +29,7 @@
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
<div class="container py-4">
<div class="row g-4">
<div class="col-12">
<div class="col-12 col-xl-6">
<div class="card shadow-sm border-0">
<div class="card-body">
<div class="d-flex flex-wrap gap-2 justify-content-between align-items-center mb-3">
@@ -48,7 +48,7 @@
</div>
</div>
<div class="col-12">
<div class="col-12 col-xl-6">
<div class="card shadow-sm border-0">
<div class="card-body">
<h2 class="h5 card-title mb-3">Tables core</h2>

View File

@@ -14,10 +14,10 @@
<header class="app-header">
<nav class="navbar navbar-expand-lg h-100 py-0 bg-light text-dark">
<div class="container my-0">
<a class="navbar-brand d-flex align-items-center" href="main.html">
<div class="navbar-brand d-flex align-items-center">
<img alt="Logo" src="imgs/logo.png" class="app-logo" />
<span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3</span>
</span>
</div>
<div class="ms-auto d-flex align-items-center gap-2">
<span class="badge text-bg-primary">PostgreSQL acquisition</span>
</div>
@@ -29,7 +29,7 @@
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
<div class="container py-4">
<div class="row g-4">
<div class="col-12">
<div class="col-12 col-xl-6">
<div class="card shadow-sm border-0">
<div class="card-body">
<div class="d-flex flex-wrap gap-2 justify-content-between align-items-center mb-3">
@@ -48,7 +48,7 @@
</div>
</div>
<div class="col-12">
<div class="col-12 col-xl-6">
<div class="card shadow-sm border-0">
<div class="card-body">
<h2 class="h5 card-title mb-3">Transactions canoniques et observations</h2>

View File

@@ -14,10 +14,10 @@
<header class="app-header">
<nav class="navbar navbar-expand-lg h-100 py-0 bg-light text-dark">
<div class="container-fluid px-4 my-0">
<a class="navbar-brand d-flex align-items-center" href="main.html">
<div class="navbar-brand d-flex align-items-center">
<img alt="Logo" src="imgs/logo.png" class="app-logo" />
<span class="ps-2 fs-4 fw-bold text-primary font-logo">Candidats de replay SQL</span>
</span>
</div>
<div class="ms-auto d-flex align-items-center gap-2">
<span id="replayCandidateProfileBadge" class="badge text-bg-info">Profil —</span>
<span id="replayCandidateStatusBadge" class="badge text-bg-secondary">Initialisation</span>

View File

@@ -30,6 +30,8 @@
<li><hr class="dropdown-divider"></li>
<li><a id="openDemoCoreExtractionLink" class="dropdown-item" href="#">Extraction core</a></li>
<li><a id="openDemoDecodeReplayLink" class="dropdown-item" href="#">Décodage contextualisé</a></li>
<li><a id="openDemoExecutionSolanaCoreLink" class="dropdown-item" href="#">Exécution Solana Core</a></li>
<li><a id="openDemoExecutionSplLink" class="dropdown-item" href="#">Exécution SPL</a></li>
<li><hr class="dropdown-divider"></li>
<li><a id="openDemoSqlDiagLink" class="dropdown-item" href="#">SQL diagnostics</a></li>
<li><a id="openDemoSqlPgRawLink" class="dropdown-item" href="#">SQL PostgreSQL raw</a></li>

View File

@@ -0,0 +1,31 @@
// file: kb-app-demo-desktop/frontend/ts/demo_execution_solana_core.ts
// version: 6
import * as bootstrap from "bootstrap";
import "simplebar";
import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { frontendDebug, frontendError, frontendWarn, installFrontendConsoleBridge } from "./frontend_log";
import type { DemoExecutionSolanaCoreGeneratedRecipientPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreGeneratedRecipientPayload.ts";
import type { DemoExecutionSolanaCoreOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreOptionsPayload.ts";
import type { DemoExecutionSolanaCoreProfileOption } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreProfileOption.ts";
import type { DemoExecutionSolanaCoreProgressPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreProgressPayload.ts";
import type { DemoExecutionSolanaCoreRequest } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreRequest.ts";
import type { DemoExecutionSolanaCoreSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreSummaryPayload.ts";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
const logLines: string[] = [];
let running = false;
let profileOptions: DemoExecutionSolanaCoreProfileOption[] = [];
function element<T extends HTMLElement>(selector: string): T { const value=document.querySelector<T>(selector); if(!value){throw new Error(`Missing UI element: ${selector}`);} return value; }
function integerValue(selector: string): number { const value=Number.parseInt(element<HTMLInputElement>(selector).value,10); if(!Number.isFinite(value)||value<0){throw new Error(`Valeur numérique invalide pour ${selector}`);} return value; }
function selectedProfile(): DemoExecutionSolanaCoreProfileOption|null { const name=element<HTMLSelectElement>("#executionProfileSelect").value; return profileOptions.find(profile=>profile.name===name)??null; }
function appendLog(payload: DemoExecutionSolanaCoreProgressPayload|{timestamp:string;level:string;stage:string;message:string;signature?:string|null}):void { const signature=payload.signature?` signature=${payload.signature}`:""; logLines.push(`${payload.timestamp} ${payload.level.toUpperCase()} [${payload.stage}] ${payload.message}${signature}`); const output=element<HTMLTextAreaElement>("#executionLogOutput"); output.value=logLines.join("\n"); output.scrollTop=output.scrollHeight; }
function setRunning(value:boolean):void { running=value; const profile=selectedProfile(); element<HTMLButtonElement>("#simulateExecutionButton").disabled=value||!profile; element<HTMLButtonElement>("#submitExecutionButton").disabled=value||!profile||!profile.sendEnabled; element<HTMLButtonElement>("#generateRecipientButton").disabled=value; element<HTMLButtonElement>("#cancelExecutionButton").disabled=!value; const badge=element<HTMLElement>("#executionStatusBadge"); badge.textContent=value?"Exécution en cours":"Prêt"; badge.className=value?"badge text-bg-warning":"badge text-bg-success"; }
function updateProfileHelp():void { const profile=selectedProfile(); const help=element<HTMLElement>("#executionProfileHelp"); if(!profile){help.textContent="Aucun profil Devnet compatible.";return;} help.textContent=`wallet=${profile.walletAlias}, spendMax=${profile.maxSpendLamports}, airdropMax=${profile.maxAirdropLamports}, send=${profile.sendEnabled?"enabled":"disabled"}`; element<HTMLInputElement>("#executionAirdropInput").max=String(profile.maxAirdropLamports); element<HTMLInputElement>("#executionLamportsInput").max=String(profile.maxSpendLamports); setRunning(running); }
function buildRequest(submit:boolean):DemoExecutionSolanaCoreRequest { const recipient=element<HTMLInputElement>("#executionRecipientInput").value.trim(); if(recipient.length===0){throw new Error("Le destinataire est obligatoire.");} return {profileName:element<HTMLSelectElement>("#executionProfileSelect").value,recipient,lamports:integerValue("#executionLamportsInput"),airdropLamports:integerValue("#executionAirdropInput"),submit,operatorConfirmed:element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,forcePostValidationReplay:element<HTMLInputElement>("#executionForceReplayInput").checked,materializeAfterDecode:element<HTMLInputElement>("#executionMaterializeInput").checked}; }
function displaySummary(summary:DemoExecutionSolanaCoreSummaryPayload):void { element<HTMLTextAreaElement>("#executionSummaryOutput").value=JSON.stringify(summary,null,2); element<HTMLTextAreaElement>("#executionPlanOutput").value=summary.planJson; element<HTMLTextAreaElement>("#executionSimulationOutput").value=summary.simulationJson; element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value=summary.diagnosticsJson; }
async function execute(submit:boolean):Promise<void>{if(running){return;}try{const request=buildRequest(submit);if(submit&&!request.operatorConfirmed){frontendWarn("kb-app-demo-desktop.frontend.demo_execution_solana_core","Submission blocked: operator confirmation missing");return;}setRunning(true);const summary=await invoke<DemoExecutionSolanaCoreSummaryPayload>("demo_execution_solana_core_execute",{request});displaySummary(summary);}catch(caughtError){const message=caughtError instanceof Error?caughtError.message:String(caughtError);frontendError("kb-app-demo-desktop.frontend.demo_execution_solana_core",`Execution failed: ${message}`);}finally{setRunning(false);}}
async function loadOptions():Promise<void>{const options=await invoke<DemoExecutionSolanaCoreOptionsPayload>("demo_execution_solana_core_options");profileOptions=options.profiles;const select=element<HTMLSelectElement>("#executionProfileSelect");select.replaceChildren();for(const profile of profileOptions){const option=document.createElement("option");option.value=profile.name;option.textContent=profile.name;option.selected=profile.name===options.defaultProfileName;select.append(option);}element<HTMLInputElement>("#executionLamportsInput").value=String(options.defaultTransferLamports);updateProfileHelp();setRunning(options.running);}
document.addEventListener("DOMContentLoaded",async()=>{installFrontendConsoleBridge("kb-app-demo-desktop.frontend.demo_execution_solana_core");frontendDebug("kb-app-demo-desktop.frontend.demo_execution_solana_core","Solana Core execution window loaded");await listen<DemoExecutionSolanaCoreProgressPayload>("demo-execution-solana-core-progress",event=>appendLog(event.payload));element<HTMLSelectElement>("#executionProfileSelect").addEventListener("change",updateProfileHelp);element<HTMLButtonElement>("#generateRecipientButton").addEventListener("click",async()=>{const payload=await invoke<DemoExecutionSolanaCoreGeneratedRecipientPayload>("demo_execution_solana_core_generate_recipient");element<HTMLInputElement>("#executionRecipientInput").value=payload.publicKey;});element<HTMLButtonElement>("#simulateExecutionButton").addEventListener("click",()=>void execute(false));element<HTMLButtonElement>("#submitExecutionButton").addEventListener("click",()=>void execute(true));element<HTMLButtonElement>("#cancelExecutionButton").addEventListener("click",()=>void invoke("demo_execution_solana_core_cancel"));element<HTMLButtonElement>("#clearExecutionLogButton").addEventListener("click",()=>{logLines.length=0;element<HTMLTextAreaElement>("#executionLogOutput").value="";});try{await loadOptions();}catch(caughtError){const message=caughtError instanceof Error?caughtError.message:String(caughtError);frontendError("kb-app-demo-desktop.frontend.demo_execution_solana_core",`Options loading failed: ${message}`);}});

View File

@@ -0,0 +1,881 @@
// file: kb-app-demo-desktop/frontend/ts/demo_execution_spl.ts
// version: 2
import * as bootstrap from "bootstrap";
import "simplebar";
import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { frontendDebug, frontendError, frontendWarn, installFrontendConsoleBridge } from "./frontend_log";
import type { DemoExecutionSolanaCoreGeneratedRecipientPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreGeneratedRecipientPayload.ts";
import type { DemoExecutionSolanaCoreOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreOptionsPayload.ts";
import type { DemoExecutionSolanaCoreProfileOption } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreProfileOption.ts";
import type { DemoExecutionSolanaCoreProgressPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreProgressPayload.ts";
import type { DemoExecutionSolanaCoreRequest } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreRequest.ts";
import type { DemoExecutionSolanaCoreSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreSummaryPayload.ts";
import type { DemoExecutionMemoRequest } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionMemoRequest.ts";
import type { DemoExecutionMemoSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionMemoSummaryPayload.ts";
import type { DemoExecutionSplTokenRequest } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoExecutionSplTokenRequest.ts";
import type { DemoExecutionSplTokenSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoExecutionSplTokenSummaryPayload.ts";
import type { DemoSplTokenJournalRequest } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalRequest.ts";
import type { DemoSplTokenJournalRow } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalRow.ts";
import type { DemoExecutionSplAtaRequest } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoExecutionSplAtaRequest.ts";
import type { DemoExecutionSplAtaSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoExecutionSplAtaSummaryPayload.ts";
import type { DemoSplAtaDerivationPayload } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationPayload.ts";
import type { DemoSplAtaDerivationRequest } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationRequest.ts";
import type { DemoSplAtaJournalRequest } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRequest.ts";
import type { DemoSplAtaJournalRow } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRow.ts";
import type { DemoExecutionSplToken2022Request } from "./bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022Request.ts";
import type { DemoExecutionSplToken2022SummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022SummaryPayload.ts";
import type { DemoSplToken2022FixturePayload } from "./bindings/kb_app_demo_desktop/demo_spl_token2022/DemoSplToken2022FixturePayload.ts";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
const logLines: string[] = [];
const maximumLogLines = 1200;
let running = false;
let profileOptions: DemoExecutionSolanaCoreProfileOption[] = [];
let token2022Fixture: DemoSplToken2022FixturePayload | null = null;
function element<T extends HTMLElement>(selector: string): T {
const value = document.querySelector<T>(selector);
if (!value) {
throw new Error(`Missing UI element: ${selector}`);
}
return value;
}
function integerValue(selector: string): number {
const value = Number.parseInt(element<HTMLInputElement>(selector).value, 10);
if (!Number.isFinite(value) || value < 0) {
throw new Error(`Valeur numérique invalide pour ${selector}`);
}
return value;
}
function appendLog(payload: DemoExecutionSolanaCoreProgressPayload | { timestamp: string; level: string; stage: string; message: string; signature?: string | null }): void {
const signature = payload.signature ? ` signature=${payload.signature}` : "";
logLines.push(`${payload.timestamp} ${payload.level.toUpperCase()} [${payload.stage}] ${payload.message}${signature}`);
while (logLines.length > maximumLogLines) {
logLines.shift();
}
const output = element<HTMLTextAreaElement>("#executionLogOutput");
output.value = logLines.join("\n");
output.scrollTop = output.scrollHeight;
}
function setRunning(value: boolean): void {
running = value;
const profile = selectedProfile();
element<HTMLButtonElement>("#simulateExecutionButton").disabled = value || !profile;
element<HTMLButtonElement>("#submitExecutionButton").disabled = value || !profile || !profile.sendEnabled;
element<HTMLButtonElement>("#simulateMemoButton").disabled = value || !profile;
element<HTMLButtonElement>("#submitMemoButton").disabled = value || !profile || !profile.sendEnabled;
element<HTMLButtonElement>("#simulateTokenButton").disabled = value || !profile;
element<HTMLButtonElement>("#submitTokenButton").disabled = value || !profile || !profile.sendEnabled;
element<HTMLButtonElement>("#deriveAtaButton").disabled = value || !profile;
element<HTMLButtonElement>("#simulateAtaButton").disabled = value || !profile;
element<HTMLButtonElement>("#submitAtaButton").disabled = value || !profile || !profile.sendEnabled;
element<HTMLButtonElement>("#simulateToken2022Button").disabled = value || !profile;
element<HTMLButtonElement>("#submitToken2022Button").disabled = value || !profile || !profile.sendEnabled;
element<HTMLButtonElement>("#loadToken2022FixtureButton").disabled = value || !profile;
element<HTMLButtonElement>("#generateRecipientButton").disabled = value;
element<HTMLButtonElement>("#cancelExecutionButton").disabled = !value;
const badge = element<HTMLElement>("#executionStatusBadge");
badge.textContent = value ? "Exécution en cours" : "Prêt";
badge.className = value ? "badge text-bg-warning" : "badge text-bg-success";
}
function selectedProfile(): DemoExecutionSolanaCoreProfileOption | null {
const name = element<HTMLSelectElement>("#executionProfileSelect").value;
return profileOptions.find(profile => profile.name === name) ?? null;
}
function updateProfileHelp(): void {
const profile = selectedProfile();
const help = element<HTMLElement>("#executionProfileHelp");
if (!profile) {
help.textContent = "Aucun profil Devnet compatible.";
return;
}
help.textContent = `wallet=${profile.walletAlias}, spendMax=${profile.maxSpendLamports}, airdropMax=${profile.maxAirdropLamports}, send=${profile.sendEnabled ? "enabled" : "disabled"}`;
element<HTMLInputElement>("#executionAirdropInput").max = String(profile.maxAirdropLamports);
element<HTMLInputElement>("#executionLamportsInput").max = String(profile.maxSpendLamports);
setRunning(running);
}
function buildRequest(submit: boolean): DemoExecutionSolanaCoreRequest {
const recipient = element<HTMLInputElement>("#executionRecipientInput").value.trim();
if (recipient.length === 0) {
throw new Error("Le destinataire est obligatoire.");
}
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
recipient,
lamports: integerValue("#executionLamportsInput"),
airdropLamports: integerValue("#executionAirdropInput"),
submit,
operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,
forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked,
materializeAfterDecode: element<HTMLInputElement>("#executionMaterializeInput").checked,
};
}
function displaySummary(summary: DemoExecutionSolanaCoreSummaryPayload): void {
const concise = {
profileName: summary.profileName,
cluster: summary.cluster,
genesisHash: summary.genesisHash,
walletPublicKey: summary.walletPublicKey,
recipient: summary.recipient,
recipientExistedBefore: summary.recipientExistedBefore,
recipientBalanceBeforeLamports: summary.recipientBalanceBeforeLamports,
recipientMinimumBalanceLamports: summary.recipientMinimumBalanceLamports,
balanceBeforeLamports: summary.balanceBeforeLamports,
balanceAfterFundingLamports: summary.balanceAfterFundingLamports,
feeLamports: summary.feeLamports,
simulationSuccess: summary.simulationSuccess,
simulationError: summary.simulationError,
airdropSignature: summary.airdropSignature,
transactionSignature: summary.transactionSignature,
confirmationStatus: summary.confirmationStatus,
canonicalInserted: summary.canonicalInserted,
coreExtracted: summary.coreExtracted,
decodeCompleted: summary.decodeCompleted,
decodeFailedInputs: summary.decodeFailedInputs,
};
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify(concise, null, 2);
element<HTMLTextAreaElement>("#executionPlanOutput").value = summary.planJson;
element<HTMLTextAreaElement>("#executionSimulationOutput").value = summary.simulationJson;
element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value = summary.diagnosticsJson;
}
function buildMemoRequest(submit: boolean): DemoExecutionMemoRequest {
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
message: element<HTMLTextAreaElement>("#memoMessageInput").value,
includeWalletAsMemoSigner: element<HTMLInputElement>("#memoWalletSignerInput").checked,
submit,
operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,
forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked,
};
}
function displayMemoSummary(summary: DemoExecutionMemoSummaryPayload): void {
const concise = {
operation: "spl_memo_v4.add_memo",
profileName: summary.profileName,
cluster: summary.cluster,
genesisHash: summary.genesisHash,
walletPublicKey: summary.walletPublicKey,
balanceLamports: summary.balanceLamports,
feeLamports: summary.feeLamports,
simulationSuccess: summary.simulationSuccess,
simulationError: summary.simulationError,
transactionSignature: summary.transactionSignature,
confirmationStatus: summary.confirmationStatus,
canonicalInserted: summary.canonicalInserted,
coreExtracted: summary.coreExtracted,
decodeReplayed: summary.decodeReplayed,
materialized: summary.materialized,
annotationCount: summary.annotationCount,
idempotenceValidated: summary.idempotenceValidated,
};
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify(concise, null, 2);
element<HTMLTextAreaElement>("#executionPlanOutput").value = summary.planJson;
element<HTMLTextAreaElement>("#executionSimulationOutput").value = summary.simulationJson;
element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value = summary.diagnosticsJson;
}
function requiredText(selector: string, label: string): string {
const value = element<HTMLInputElement>(selector).value.trim();
if (value.length === 0) {
throw new Error(`${label} est obligatoire.`);
}
return value;
}
function optionalText(selector: string): string | null {
const value = element<HTMLInputElement>(selector).value.trim();
return value.length === 0 ? null : value;
}
function buildTokenRequest(submit: boolean): DemoExecutionSplTokenRequest {
const amountRaw = requiredText("#tokenAmountRawInput", "Le montant brut");
if (!/^[0-9]+$/.test(amountRaw)) {
throw new Error("Le montant brut doit être un entier décimal non signé.");
}
const decimals = integerValue("#tokenDecimalsInput");
if (decimals > 255) {
throw new Error("Les decimals doivent être compris entre 0 et 255.");
}
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
source: requiredText("#tokenSourceInput", "Le compte source"),
mint: requiredText("#tokenMintInput", "Le mint"),
destination: requiredText("#tokenDestinationInput", "Le compte destination"),
authority: requiredText("#tokenAuthorityInput", "Lautorité"),
amountRaw,
decimals,
submit,
operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,
forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked,
};
}
function displayTokenSummary(summary: DemoExecutionSplTokenSummaryPayload): void {
const concise = {
operation: summary.operation,
profileName: summary.profileName,
cluster: summary.cluster,
genesisHash: summary.genesisHash,
walletPublicKey: summary.walletPublicKey,
balanceLamports: summary.balanceLamports,
feeLamports: summary.feeLamports,
amountRaw: summary.amountRaw,
decimals: summary.decimals,
readinessStatus: summary.readinessStatus,
simulationSuccess: summary.simulationSuccess,
simulationError: summary.simulationError,
transactionSignature: summary.transactionSignature,
confirmationStatus: summary.confirmationStatus,
canonicalInserted: summary.canonicalInserted,
coreExtracted: summary.coreExtracted,
decodeReplayed: summary.decodeReplayed,
materializationCount: summary.materializationCount,
idempotenceValidated: summary.idempotenceValidated,
};
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify(concise, null, 2);
element<HTMLTextAreaElement>("#executionPlanOutput").value = `PRÉFLIGHT\n${summary.readinessJson}\n\nPLAN\n${summary.planJson}`;
element<HTMLTextAreaElement>("#executionSimulationOutput").value = summary.simulationJson;
element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value = summary.diagnosticsJson;
}
function ataDerivationRequest(): DemoSplAtaDerivationRequest {
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
mint: requiredText("#ataMintInput", "Le mint ATA"),
tokenProgram: element<HTMLSelectElement>("#ataTokenProgramSelect").value,
};
}
async function deriveAta(): Promise<DemoSplAtaDerivationPayload> {
const payload = await invoke<DemoSplAtaDerivationPayload>("demo_spl_ata_derive", {
request: ataDerivationRequest(),
});
element<HTMLInputElement>("#ataPayerInput").value = payload.payer;
element<HTMLInputElement>("#ataWalletOwnerInput").value = payload.walletOwner;
element<HTMLInputElement>("#ataDerivedInput").value = payload.associatedTokenAccount;
return payload;
}
function displayAtaSummary(summary: DemoExecutionSplAtaSummaryPayload): void {
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({
operation: summary.operation,
profileName: summary.profileName,
walletPublicKey: summary.walletPublicKey,
tokenProgramId: summary.tokenProgramId,
associatedTokenAccount: summary.associatedTokenAccount,
balanceLamports: summary.balanceLamports,
feeLamports: summary.feeLamports,
readinessStatus: summary.readinessStatus,
simulationSuccess: summary.simulationSuccess,
simulationError: summary.simulationError,
transactionSignature: summary.transactionSignature,
confirmationStatus: summary.confirmationStatus,
materializationCount: summary.materializationCount,
idempotenceValidated: summary.idempotenceValidated,
}, null, 2);
element<HTMLTextAreaElement>("#executionPlanOutput").value = `PRÉFLIGHT\n${summary.readinessJson}\n\nPLAN\n${summary.planJson}`;
element<HTMLTextAreaElement>("#executionSimulationOutput").value = summary.simulationJson;
element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value = summary.diagnosticsJson;
}
async function executeAta(submit: boolean): Promise<void> {
if (running) {
return;
}
try {
if (submit && !element<HTMLInputElement>("#executionOperatorConfirmedInput").checked) {
throw new Error("La confirmation opérateur est obligatoire avant signature ATA.");
}
const derivation = await deriveAta();
const request: DemoExecutionSplAtaRequest = {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
walletOwner: derivation.walletOwner,
mint: requiredText("#ataMintInput", "Le mint ATA"),
tokenProgram: element<HTMLSelectElement>("#ataTokenProgramSelect").value,
mode: element<HTMLSelectElement>("#ataModeSelect").value,
submit,
operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,
forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked,
};
setRunning(true);
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: submit ? "ata_submit" : "ata_simulation",
message: `${request.mode} ${request.tokenProgram}, ATA=${derivation.associatedTokenAccount}`,
});
const summary = await invoke<DemoExecutionSplAtaSummaryPayload>("demo_execution_spl_ata_execute", { request });
displayAtaSummary(summary);
appendLog({
timestamp: new Date().toISOString(),
level: summary.simulationSuccess && (!submit || summary.idempotenceValidated) ? "info" : "error",
stage: "ata_completed",
message: `preflight=${summary.readinessStatus}, simulation=${summary.simulationSuccess}, confirmation=${summary.confirmationStatus ?? "none"}, materializations=${summary.materializationCount}, idempotence=${summary.idempotenceValidated}`,
signature: summary.transactionSignature,
});
if (summary.transactionSignature) {
element<HTMLInputElement>("#ataJournalSignatureInput").value = summary.transactionSignature;
element<HTMLInputElement>("#ataJournalMintInput").value = request.mint;
element<HTMLInputElement>("#ataJournalAddressInput").value = summary.associatedTokenAccount;
await loadAtaJournal();
}
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "ata_failed", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `ATA execution failed: ${message}`);
} finally {
setRunning(false);
}
}
function ataJournalRequest(): DemoSplAtaJournalRequest {
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
signatureContains: optionalText("#ataJournalSignatureInput"),
mint: optionalText("#ataJournalMintInput"),
associatedTokenAccount: optionalText("#ataJournalAddressInput"),
operation: optionalText("#ataJournalOperationInput"),
limit: integerValue("#ataJournalLimitInput"),
};
}
async function loadAtaJournal(): Promise<void> {
const button = element<HTMLButtonElement>("#refreshAtaJournalButton");
button.disabled = true;
try {
const rows = await invoke<DemoSplAtaJournalRow[]>("demo_spl_ata_journal", { request: ataJournalRequest() });
element<HTMLTextAreaElement>("#ataJournalOutput").value = JSON.stringify(rows.map(row => ({
...row,
payloadJson: JSON.parse(row.payloadJson) as unknown,
})), null, 2);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
element<HTMLTextAreaElement>("#ataJournalOutput").value = JSON.stringify({ error: message }, null, 2);
} finally {
button.disabled = false;
}
}
async function execute(submit: boolean): Promise<void> {
if (running) {
return;
}
try {
const request = buildRequest(submit);
if (submit && !request.operatorConfirmed) {
const message = "La confirmation opérateur doit être cochée avant signature et envoi.";
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ warning: message }, null, 2);
appendLog({
timestamp: new Date().toISOString(),
level: "warn",
stage: "policy",
message,
});
frontendWarn("kb-app-demo-desktop.frontend.demo_execution_spl", `Execution blocked by policy: ${message}`);
return;
}
setRunning(true);
element<HTMLTextAreaElement>("#executionSummaryOutput").value = "Exécution en cours...";
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: submit ? "submit" : "simulation",
message: submit ? "Démarrage du parcours Devnet complet." : "Démarrage de la simulation exacte.",
});
const summary = await invoke<DemoExecutionSolanaCoreSummaryPayload>("demo_execution_solana_core_execute", { request });
displaySummary(summary);
appendLog({
timestamp: new Date().toISOString(),
level: summary.simulationSuccess ? "info" : "error",
stage: "completed",
message: `simulation=${summary.simulationSuccess}, confirmation=${summary.confirmationStatus ?? "none"}, canonical=${summary.canonicalInserted ?? 0}, core=${summary.coreExtracted ?? 0}, decode=${summary.decodeCompleted ?? 0}`,
signature: summary.transactionSignature,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ error: message }, null, 2);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "failed", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Execution failed: ${message}`);
} finally {
setRunning(false);
}
}
async function executeMemo(submit: boolean): Promise<void> {
if (running) {
return;
}
try {
const request = buildMemoRequest(submit);
if (submit && !request.operatorConfirmed) {
const message = "La confirmation opérateur doit être cochée avant signature et envoi du Memo.";
appendLog({ timestamp: new Date().toISOString(), level: "warn", stage: "memo_policy", message });
frontendWarn("kb-app-demo-desktop.frontend.demo_execution_spl", `Memo execution blocked by policy: ${message}`);
return;
}
setRunning(true);
element<HTMLTextAreaElement>("#executionSummaryOutput").value = "Exécution Memo en cours...";
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: submit ? "memo_submit" : "memo_simulation",
message: submit ? "Démarrage du parcours Memo v4 Devnet complet." : "Démarrage de la simulation Memo v4 exacte.",
});
const summary = await invoke<DemoExecutionMemoSummaryPayload>("demo_execution_spl_memo_execute", { request });
displayMemoSummary(summary);
appendLog({
timestamp: new Date().toISOString(),
level: summary.simulationSuccess && (!submit || summary.materialized) ? "info" : "error",
stage: "memo_completed",
message: `simulation=${summary.simulationSuccess}, confirmation=${summary.confirmationStatus ?? "none"}, annotation=${summary.annotationCount}, idempotence=${summary.idempotenceValidated}`,
signature: summary.transactionSignature,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ error: message }, null, 2);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "memo_failed", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Memo execution failed: ${message}`);
} finally {
setRunning(false);
}
}
async function executeToken(submit: boolean): Promise<void> {
if (running) {
return;
}
try {
const request = buildTokenRequest(submit);
if (submit && !request.operatorConfirmed) {
const message = "La confirmation opérateur doit être cochée avant signature et envoi Token.";
appendLog({ timestamp: new Date().toISOString(), level: "warn", stage: "spl_token_policy", message });
frontendWarn("kb-app-demo-desktop.frontend.demo_execution_spl", `Token execution blocked by policy: ${message}`);
return;
}
setRunning(true);
element<HTMLTextAreaElement>("#executionSummaryOutput").value = "Préflight et exécution SPL Token en cours...";
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: submit ? "spl_token_submit" : "spl_token_simulation",
message: submit ? "Démarrage du TransferChecked Devnet complet." : "Démarrage du préflight et de la simulation TransferChecked.",
});
const summary = await invoke<DemoExecutionSplTokenSummaryPayload>("demo_execution_spl_token_execute", { request });
displayTokenSummary(summary);
appendLog({
timestamp: new Date().toISOString(),
level: summary.simulationSuccess && (!submit || summary.idempotenceValidated) ? "info" : "error",
stage: "spl_token_completed",
message: `preflight=${summary.readinessStatus}, simulation=${summary.simulationSuccess}, confirmation=${summary.confirmationStatus ?? "none"}, materializations=${summary.materializationCount}, idempotence=${summary.idempotenceValidated}`,
signature: summary.transactionSignature,
});
if (summary.transactionSignature) {
element<HTMLInputElement>("#tokenJournalSignatureInput").value = summary.transactionSignature;
await loadTokenJournal();
}
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ error: message }, null, 2);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "spl_token_failed", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Token execution failed: ${message}`);
} finally {
setRunning(false);
}
}
function tokenJournalRequest(): DemoSplTokenJournalRequest {
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
signatureContains: optionalText("#tokenJournalSignatureInput"),
mint: optionalText("#tokenJournalMintInput"),
account: optionalText("#tokenJournalAccountInput"),
family: element<HTMLSelectElement>("#tokenJournalFamilySelect").value || null,
operation: optionalText("#tokenJournalOperationInput"),
limit: integerValue("#tokenJournalLimitInput"),
};
}
function renderTokenJournal(rows: DemoSplTokenJournalRow[]): void {
const body = element<HTMLTableSectionElement>("#tokenJournalRows");
body.replaceChildren();
if (rows.length === 0) {
const row = document.createElement("tr");
const cell = document.createElement("td");
cell.colSpan = 5;
cell.className = "text-body-secondary";
cell.textContent = "Aucun événement correspondant.";
row.append(cell);
body.append(row);
element<HTMLTextAreaElement>("#tokenJournalDetailOutput").value = "Aucun événement sélectionné.";
return;
}
for (const item of rows) {
const row = document.createElement("tr");
row.tabIndex = 0;
row.setAttribute("role", "button");
const values = [
item.slot,
item.operation ?? "—",
item.family,
item.amountRaw ?? "—",
item.signature.length > 20 ? `${item.signature.slice(0, 10)}${item.signature.slice(-8)}` : item.signature,
];
for (const value of values) {
const cell = document.createElement("td");
cell.textContent = value;
row.append(cell);
}
row.title = item.signature;
const select = (): void => {
element<HTMLTextAreaElement>("#tokenJournalDetailOutput").value = item.payloadJson;
};
row.addEventListener("click", select);
row.addEventListener("keydown", event => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
select();
}
});
body.append(row);
}
element<HTMLTextAreaElement>("#tokenJournalDetailOutput").value = rows[0].payloadJson;
}
function token2022ScenarioAmount(fixture: DemoSplToken2022FixturePayload, scenarioId: string): string {
if (scenarioId === "token2022_mint_to_checked") {
return "1";
}
if (scenarioId === "token2022_approve_checked") {
return "2";
}
if (scenarioId === "token2022_burn_checked") {
return "1";
}
return fixture.defaultAmountRaw;
}
function applyToken2022Fixture(): void {
if (!token2022Fixture) {
return;
}
const scenarioId = element<HTMLSelectElement>("#token2022ScenarioSelect").value;
element<HTMLInputElement>("#token2022SourceInput").value = scenarioId === "token2022_close_destination"
? token2022Fixture.closeAccount
: token2022Fixture.source;
element<HTMLInputElement>("#token2022MintInput").value = token2022Fixture.mint;
element<HTMLInputElement>("#token2022DestinationInput").value = scenarioId === "token2022_close_destination"
? token2022Fixture.authority
: token2022Fixture.destination;
element<HTMLInputElement>("#token2022DelegateInput").value = token2022Fixture.delegate;
element<HTMLInputElement>("#token2022AuthorityInput").value = token2022Fixture.authority;
element<HTMLInputElement>("#token2022FreezeAuthorityInput").value = token2022Fixture.freezeAuthority;
element<HTMLInputElement>("#token2022AmountInput").value = token2022ScenarioAmount(token2022Fixture, scenarioId);
element<HTMLInputElement>("#token2022DecimalsInput").value = String(token2022Fixture.decimals);
}
async function loadToken2022Fixture(): Promise<void> {
try {
token2022Fixture = await invoke<DemoSplToken2022FixturePayload>("demo_spl_token2022_fixture", {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
});
applyToken2022Fixture();
element<HTMLElement>("#token2022FixtureHelp").textContent = `Fixture: ${token2022Fixture.fixturePath} — program=${token2022Fixture.programId}`;
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: "token2022_fixture",
message: `fixture chargée: mint=${token2022Fixture.mint}, source=${token2022Fixture.source}, destination=${token2022Fixture.destination}`,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
token2022Fixture = null;
element<HTMLElement>("#token2022FixtureHelp").textContent = message;
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_fixture", message });
}
}
function buildToken2022Request(submit: boolean): DemoExecutionSplToken2022Request {
const scenarioId = element<HTMLSelectElement>("#token2022ScenarioSelect").value;
const source = element<HTMLInputElement>("#token2022SourceInput").value.trim();
const mint = element<HTMLInputElement>("#token2022MintInput").value.trim();
const destination = element<HTMLInputElement>("#token2022DestinationInput").value.trim();
const delegate = element<HTMLInputElement>("#token2022DelegateInput").value.trim();
const authority = element<HTMLInputElement>("#token2022AuthorityInput").value.trim();
const freezeAuthority = element<HTMLInputElement>("#token2022FreezeAuthorityInput").value.trim();
if (source.length === 0) {
throw new Error("Le compte source ou cible Token-2022 est obligatoire.");
}
if (scenarioId !== "token2022_revoke" && scenarioId !== "token2022_close_destination" && mint.length === 0) {
throw new Error("Le mint Token-2022 est obligatoire pour ce scénario.");
}
if (scenarioId === "token2022_transfer_checked" && destination.length === 0) {
throw new Error("Le compte destination Token-2022 est obligatoire.");
}
if (scenarioId === "token2022_close_destination" && destination.length === 0) {
throw new Error("La destination lamports est obligatoire pour CloseAccount.");
}
if (scenarioId === "token2022_approve_checked" && delegate.length === 0) {
throw new Error("Le delegate est obligatoire pour ApproveChecked.");
}
if (["token2022_freeze_account", "token2022_thaw_account"].includes(scenarioId) && freezeAuthority.length === 0) {
throw new Error("La freeze authority est obligatoire pour FreezeAccount et ThawAccount.");
}
if (!["token2022_freeze_account", "token2022_thaw_account"].includes(scenarioId) && authority.length === 0) {
throw new Error("Lautorité Token-2022 est obligatoire.");
}
return {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
scenarioId,
source,
mint,
destination,
delegate,
authority,
freezeAuthority,
amountRaw: element<HTMLInputElement>("#token2022AmountInput").value.trim(),
decimals: integerValue("#token2022DecimalsInput"),
submit,
operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,
forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked,
};
}
function displayToken2022Summary(summary: DemoExecutionSplToken2022SummaryPayload): void {
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({
scenarioId: summary.scenarioId,
operation: summary.operation,
profileName: summary.profileName,
cluster: summary.cluster,
genesisHash: summary.genesisHash,
walletPublicKey: summary.walletPublicKey,
balanceLamports: summary.balanceLamports,
feeLamports: summary.feeLamports,
simulationSuccess: summary.simulationSuccess,
simulationError: summary.simulationError,
transactionSignature: summary.transactionSignature,
confirmationStatus: summary.confirmationStatus,
canonicalInserted: summary.canonicalInserted,
coreExtracted: summary.coreExtracted,
decodeReplayed: summary.decodeReplayed,
materializationCount: summary.materializationCount,
idempotenceValidated: summary.idempotenceValidated,
}, null, 2);
element<HTMLTextAreaElement>("#executionPlanOutput").value = `PRÉFLIGHT\n${summary.preflightJson}\n\nPLAN\n${summary.planJson}`;
element<HTMLTextAreaElement>("#executionSimulationOutput").value = summary.simulationJson;
element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value = summary.diagnosticsJson;
}
async function executeToken2022(submit: boolean): Promise<void> {
if (running) {
return;
}
let request: DemoExecutionSplToken2022Request;
try {
request = buildToken2022Request(submit);
if (submit && !request.operatorConfirmed) {
const message = "La confirmation opérateur est obligatoire avant signature Token-2022.";
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ warning: message }, null, 2);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_failed", message });
return;
}
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_failed", message });
return;
}
setRunning(true);
element<HTMLTextAreaElement>("#executionSummaryOutput").value = "Préflight et exécution Token-2022 en cours...";
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: submit ? "token2022_submit" : "token2022_simulation",
message: `${request.scenarioId}, source=${request.source}`,
});
try {
const summary = await invoke<DemoExecutionSplToken2022SummaryPayload>("demo_execution_spl_token2022_execute", { request });
displayToken2022Summary(summary);
appendLog({
timestamp: new Date().toISOString(),
level: summary.simulationSuccess ? "info" : "error",
stage: "token2022_completed",
message: `scenario=${summary.scenarioId}, simulation=${summary.simulationSuccess}, confirmation=${summary.confirmationStatus ?? "none"}, materializations=${summary.materializationCount}, idempotence=${summary.idempotenceValidated}`,
signature: summary.transactionSignature,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ error: message }, null, 2);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_failed", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Token-2022 execution failed: ${message}`);
} finally {
setRunning(false);
}
}
async function loadTokenJournal(): Promise<void> {
const button = element<HTMLButtonElement>("#refreshTokenJournalButton");
button.disabled = true;
try {
const rows = await invoke<DemoSplTokenJournalRow[]>("demo_spl_token_journal", { request: tokenJournalRequest() });
renderTokenJournal(rows);
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: "spl_token_journal",
message: `${rows.length} événement(s) matérialisé(s) chargé(s).`,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
element<HTMLTextAreaElement>("#tokenJournalDetailOutput").value = JSON.stringify({ error: message }, null, 2);
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Token journal loading failed: ${message}`);
} finally {
button.disabled = false;
}
}
async function generateRecipient(): Promise<void> {
try {
const payload = await invoke<DemoExecutionSolanaCoreGeneratedRecipientPayload>("demo_execution_solana_core_generate_recipient");
element<HTMLInputElement>("#executionRecipientInput").value = payload.publicKey;
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: "recipient",
message: `Adresse destinataire jetable générée : ${payload.publicKey}`,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Recipient generation failed: ${message}`);
}
}
async function cancelExecution(): Promise<void> {
try {
const accepted = await invoke<boolean>("demo_execution_solana_core_cancel");
appendLog({
timestamp: new Date().toISOString(),
level: accepted ? "warn" : "info",
stage: "cancel",
message: accepted ? "Demande dannulation enregistrée." : "Aucune exécution active.",
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Cancellation failed: ${message}`);
}
}
async function loadOptions(): Promise<void> {
const options = await invoke<DemoExecutionSolanaCoreOptionsPayload>("demo_execution_solana_core_options");
profileOptions = options.profiles;
const select = element<HTMLSelectElement>("#executionProfileSelect");
select.replaceChildren();
for (const profile of profileOptions) {
const option = document.createElement("option");
option.value = profile.name;
option.textContent = profile.name;
option.selected = profile.name === options.defaultProfileName;
select.append(option);
}
element<HTMLInputElement>("#executionLamportsInput").value = String(options.defaultTransferLamports);
updateProfileHelp();
setRunning(options.running);
if (profileOptions.length === 0) {
element<HTMLButtonElement>("#simulateExecutionButton").disabled = true;
element<HTMLButtonElement>("#submitExecutionButton").disabled = true;
element<HTMLButtonElement>("#simulateMemoButton").disabled = true;
element<HTMLButtonElement>("#submitMemoButton").disabled = true;
element<HTMLButtonElement>("#simulateTokenButton").disabled = true;
element<HTMLButtonElement>("#submitTokenButton").disabled = true;
element<HTMLButtonElement>("#deriveAtaButton").disabled = true;
element<HTMLButtonElement>("#simulateAtaButton").disabled = true;
element<HTMLButtonElement>("#submitAtaButton").disabled = true;
element<HTMLButtonElement>("#simulateToken2022Button").disabled = true;
element<HTMLButtonElement>("#submitToken2022Button").disabled = true;
element<HTMLButtonElement>("#loadToken2022FixtureButton").disabled = true;
}
}
document.addEventListener("DOMContentLoaded", () => {
installFrontendConsoleBridge("kb-app-demo-desktop.frontend.demo_execution_spl");
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", "execution demo window loaded");
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(item => new bootstrap.Tooltip(item));
element<HTMLSelectElement>("#executionProfileSelect").addEventListener("change", updateProfileHelp);
element<HTMLButtonElement>("#generateRecipientButton").addEventListener("click", () => {
void generateRecipient();
});
element<HTMLButtonElement>("#simulateExecutionButton").addEventListener("click", () => {
void execute(false);
});
element<HTMLButtonElement>("#submitExecutionButton").addEventListener("click", () => {
void execute(true);
});
element<HTMLButtonElement>("#simulateMemoButton").addEventListener("click", () => {
void executeMemo(false);
});
element<HTMLButtonElement>("#submitMemoButton").addEventListener("click", () => {
void executeMemo(true);
});
element<HTMLButtonElement>("#simulateTokenButton").addEventListener("click", () => {
void executeToken(false);
});
element<HTMLButtonElement>("#submitTokenButton").addEventListener("click", () => {
void executeToken(true);
});
element<HTMLButtonElement>("#loadToken2022FixtureButton").addEventListener("click", () => {
void loadToken2022Fixture();
});
element<HTMLSelectElement>("#token2022ScenarioSelect").addEventListener("change", applyToken2022Fixture);
element<HTMLButtonElement>("#simulateToken2022Button").addEventListener("click", () => {
void executeToken2022(false);
});
element<HTMLButtonElement>("#submitToken2022Button").addEventListener("click", () => {
void executeToken2022(true);
});
element<HTMLButtonElement>("#refreshTokenJournalButton").addEventListener("click", () => {
void loadTokenJournal();
});
element<HTMLButtonElement>("#deriveAtaButton").addEventListener("click", () => {
void deriveAta();
});
element<HTMLButtonElement>("#simulateAtaButton").addEventListener("click", () => {
void executeAta(false);
});
element<HTMLButtonElement>("#submitAtaButton").addEventListener("click", () => {
void executeAta(true);
});
element<HTMLButtonElement>("#refreshAtaJournalButton").addEventListener("click", () => {
void loadAtaJournal();
});
element<HTMLButtonElement>("#cancelExecutionButton").addEventListener("click", () => {
void cancelExecution();
});
element<HTMLButtonElement>("#clearExecutionLogButton").addEventListener("click", () => {
logLines.length = 0;
element<HTMLTextAreaElement>("#executionLogOutput").value = "";
});
void listen<DemoExecutionSolanaCoreProgressPayload>("demo-execution-solana-core-progress", event => {
appendLog(event.payload);
});
void loadOptions().catch(caughtError => {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "options", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Options loading failed: ${message}`);
});
});

View File

@@ -78,6 +78,8 @@ if (openDemoBackfillLink) {
const pipelineDemoLinks: ReadonlyArray<readonly [string, string]> = [
["openDemoCoreExtractionLink", "open_demo_core_extraction_window"],
["openDemoDecodeReplayLink", "open_demo_decode_replay_window"],
["openDemoExecutionSolanaCoreLink", "open_demo_execution_solana_core_window"],
["openDemoExecutionSplLink", "open_demo_execution_spl_window"],
];
for (const [elementId, command] of pipelineDemoLinks) {
const link = document.querySelector<HTMLAnchorElement>(`#${elementId}`);

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/app_state.rs
// version: 5
// version: 6
//! Shared Tauri application state and startup initialization.
@@ -21,6 +21,8 @@ pub(crate) struct AppState {
demo_decode_replay_running: std::sync::atomic::AtomicBool,
demo_decode_replay_cancel_requested: std::sync::atomic::AtomicBool,
demo_decode_replay_campaign_id: std::sync::Mutex<std::option::Option<std::string::String>>,
demo_execution_solana_core_running: std::sync::atomic::AtomicBool,
demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool,
}
impl crate::AppState {
@@ -60,6 +62,8 @@ impl crate::AppState {
demo_decode_replay_running: std::sync::atomic::AtomicBool::new(false),
demo_decode_replay_cancel_requested: std::sync::atomic::AtomicBool::new(false),
demo_decode_replay_campaign_id: std::sync::Mutex::new(std::option::Option::None),
demo_execution_solana_core_running: std::sync::atomic::AtomicBool::new(false),
demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool::new(false),
});
}
@@ -152,6 +156,18 @@ impl crate::AppState {
return &self.demo_decode_replay_campaign_id;
}
/// Returns the shared execution running flag used by Solana and SPL windows.
pub(crate) fn demo_execution_solana_core_running(&self) -> &std::sync::atomic::AtomicBool {
return &self.demo_execution_solana_core_running;
}
/// Returns the shared execution cancellation flag used by Solana and SPL windows.
pub(crate) fn demo_execution_solana_core_cancel_requested(
&self,
) -> &std::sync::atomic::AtomicBool {
return &self.demo_execution_solana_core_cancel_requested;
}
/// Returns the number of logging routes held by the logging guard.
pub(crate) fn logging_route_count(&self) -> usize {
let lock_result = self.logging_guard.lock();

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_decode_replay.rs
// version: 24
// version: 25
//! Tauri commands and UI payloads for contextual instruction decode replay.
@@ -370,6 +370,7 @@ pub(crate) fn available_decoders()
-> std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> {
return std::vec![
std::sync::Arc::new(kb_lib::DcSolanaCoreDecoder),
std::sync::Arc::new(kb_lib::DcMetadataMetaplexTokenMetadataDecoder),
std::sync::Arc::new(kb_lib::DcSplAssociatedTokenAccountDecoder,),
std::sync::Arc::new(kb_lib::DcSplElgamalRegistryDecoder),
std::sync::Arc::new(kb_lib::DcSplMemoDecoder),
@@ -716,9 +717,9 @@ mod tests {
}
#[test]
fn native_memo_token_2022_elgamal_classic_token_and_ata_decoders_are_registered() {
fn native_memo_token2022_elgamal_classic_token_and_ata_decoders_are_registered() {
let decoders = crate::available_decoders();
assert_eq!(decoders.len(), 6);
assert_eq!(decoders.len(), 7);
let names = decoders
.iter()
.map(|decoder| return decoder.identity().name)
@@ -726,12 +727,13 @@ mod tests {
assert_eq!(
names,
std::vec![
"kb_decoder_metadata_metaplex_token_metadata".to_string(),
"solana_native_classifier".to_string(),
"spl_associated_token_account".to_string(),
"spl_elgamal_registry".to_string(),
"spl_memo".to_string(),
"spl_token".to_string(),
"spl_token_2022".to_string(),
"spl_token2022".to_string(),
]
);
}

View File

@@ -0,0 +1,669 @@
// file: kb-app-demo-desktop/src/demo_execution_solana_core.rs
// version: 5
//! Tauri adapter for bounded Solana Core execution on Devnet.
use tauri::Emitter; // rust-rules: trait-import
use ts_rs::TS; // rust-rules: derive-import
/// One Devnet execution profile exposed to the demo window.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreProfileOption.ts"
)]
pub(crate) struct DemoExecutionSolanaCoreProfileOption {
/// Stable profile name.
pub(crate) name: std::string::String,
/// Non-secret temporary wallet alias.
pub(crate) wallet_alias: std::string::String,
/// Maximum Devnet spend allowed by the profile.
#[ts(type = "number")]
pub(crate) max_spend_lamports: u64,
/// Maximum faucet request allowed by the profile.
#[ts(type = "number")]
pub(crate) max_airdrop_lamports: u64,
/// Whether signed Devnet submission is enabled.
pub(crate) send_enabled: bool,
/// Whether explicit operator confirmation is required.
pub(crate) require_operator_confirmation: bool,
}
/// Initial options shown by the Solana Core execution demo.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreOptionsPayload.ts"
)]
pub(crate) struct DemoExecutionSolanaCoreOptionsPayload {
/// Devnet profiles compatible with the execution laboratory.
pub(crate) profiles: std::vec::Vec<crate::DemoExecutionSolanaCoreProfileOption>,
/// Suggested profile name.
pub(crate) default_profile_name: std::option::Option<std::string::String>,
/// Default transfer amount suitable for a new zero-data account on Devnet.
#[ts(type = "number")]
pub(crate) default_transfer_lamports: u64,
/// Whether one execution is currently running.
pub(crate) running: bool,
}
/// Request sent by the execution demo.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreRequest.ts"
)]
pub(crate) struct DemoExecutionSolanaCoreRequest {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Recipient public key.
pub(crate) recipient: std::string::String,
/// Lamports transferred by the System instruction.
#[ts(type = "number")]
pub(crate) lamports: u64,
/// Optional bounded faucet request when the source wallet is underfunded.
#[ts(type = "number")]
pub(crate) airdrop_lamports: u64,
/// Whether the transaction should be signed and submitted after simulation.
pub(crate) submit: bool,
/// Explicit operator confirmation for signed submission.
pub(crate) operator_confirmed: bool,
/// Whether post-execution core/decode outputs should be force-replayed.
pub(crate) force_post_validation_replay: bool,
/// Whether compatible materializers should run after decode replay.
pub(crate) materialize_after_decode: bool,
}
/// Request sent by the Memo v4 Devnet execution panel.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionMemoRequest.ts"
)]
pub(crate) struct DemoExecutionMemoRequest {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Exact UTF-8 Memo payload.
pub(crate) message: std::string::String,
/// Whether the wallet is supplied as a readonly Memo signer account.
pub(crate) include_wallet_as_memo_signer: bool,
/// Whether the transaction should be signed and submitted after simulation.
pub(crate) submit: bool,
/// Explicit operator confirmation for signed submission.
pub(crate) operator_confirmed: bool,
/// Whether post-execution core and first decode outputs should be force-replayed.
pub(crate) force_post_validation_replay: bool,
}
/// Public key generated for a disposable Devnet recipient.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreGeneratedRecipientPayload.ts"
)]
pub(crate) struct DemoExecutionSolanaCoreGeneratedRecipientPayload {
/// Base58 public key. The private key is not persisted or exposed.
pub(crate) public_key: std::string::String,
}
/// Progress event emitted during execution and post-validation.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreProgressPayload.ts"
)]
pub(crate) struct DemoExecutionSolanaCoreProgressPayload {
/// RFC 3339 timestamp.
pub(crate) timestamp: std::string::String,
/// Stable severity code.
pub(crate) level: std::string::String,
/// Stable stage code.
pub(crate) stage: std::string::String,
/// Human-readable message.
pub(crate) message: std::string::String,
/// Transaction signature when already available.
pub(crate) signature: std::option::Option<std::string::String>,
}
/// UI-safe result of one execution orchestration.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionSolanaCoreSummaryPayload {
/// Profile used by the execution.
pub(crate) profile_name: std::string::String,
/// Classified cluster code.
pub(crate) cluster: std::string::String,
/// Genesis hash returned by the endpoint.
pub(crate) genesis_hash: std::string::String,
/// Source wallet public key.
pub(crate) wallet_public_key: std::string::String,
/// Recipient public key.
pub(crate) recipient: std::string::String,
/// Whether the recipient account existed before execution.
pub(crate) recipient_existed_before: bool,
/// Recipient balance before execution when present.
#[ts(type = "number | null")]
pub(crate) recipient_balance_before_lamports: std::option::Option<u64>,
/// Rent-exempt minimum required for a new zero-data account.
#[ts(type = "number")]
pub(crate) recipient_minimum_balance_lamports: u64,
/// Source balance before optional funding.
#[ts(type = "number")]
pub(crate) balance_before_lamports: u64,
/// Source balance after optional funding.
#[ts(type = "number")]
pub(crate) balance_after_funding_lamports: u64,
/// Estimated transaction fee.
#[ts(type = "number | null")]
pub(crate) fee_lamports: std::option::Option<u64>,
/// Whether exact simulation succeeded.
pub(crate) simulation_success: bool,
/// Runtime simulation error when present.
pub(crate) simulation_error: std::option::Option<std::string::String>,
/// Runtime simulation logs.
pub(crate) simulation_logs: std::vec::Vec<std::string::String>,
/// Faucet signature when funding was requested.
pub(crate) airdrop_signature: std::option::Option<std::string::String>,
/// Submitted transaction signature.
pub(crate) transaction_signature: std::option::Option<std::string::String>,
/// Terminal confirmation status.
pub(crate) confirmation_status: std::option::Option<std::string::String>,
/// Canonical rows inserted after submission.
#[ts(type = "number | null")]
pub(crate) canonical_inserted: std::option::Option<u64>,
/// Core transactions extracted after submission.
#[ts(type = "number | null")]
pub(crate) core_extracted: std::option::Option<u64>,
/// Contextual decode inputs completed after submission.
#[ts(type = "number | null")]
pub(crate) decode_completed: std::option::Option<u64>,
/// Contextual decode input failures after submission.
#[ts(type = "number | null")]
pub(crate) decode_failed_inputs: std::option::Option<u64>,
/// Pretty JSON representation of the exact execution plan.
pub(crate) plan_json: std::string::String,
/// Pretty JSON representation of the simulation result.
pub(crate) simulation_json: std::string::String,
/// Pretty JSON representation of confirmation and post-validation diagnostics.
pub(crate) diagnostics_json: std::string::String,
}
/// UI-safe result of one Memo v4 Devnet execution orchestration.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionMemoSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionMemoSummaryPayload {
/// Profile used by the execution.
pub(crate) profile_name: std::string::String,
/// Classified cluster code.
pub(crate) cluster: std::string::String,
/// Genesis hash returned by the endpoint.
pub(crate) genesis_hash: std::string::String,
/// Source wallet public key.
pub(crate) wallet_public_key: std::string::String,
/// Exact wallet balance rendered without JSON integer precision loss.
pub(crate) balance_lamports: std::string::String,
/// Estimated transaction fee rendered without JSON integer precision loss.
pub(crate) fee_lamports: std::option::Option<std::string::String>,
/// Whether exact simulation succeeded.
pub(crate) simulation_success: bool,
/// Runtime simulation error when present.
pub(crate) simulation_error: std::option::Option<std::string::String>,
/// Submitted transaction signature.
pub(crate) transaction_signature: std::option::Option<std::string::String>,
/// Terminal confirmation status.
pub(crate) confirmation_status: std::option::Option<std::string::String>,
/// Whether canonical hydration was validated.
pub(crate) canonical_inserted: bool,
/// Whether core extraction was validated.
pub(crate) core_extracted: bool,
/// Whether Memo decode replay was validated.
pub(crate) decode_replayed: bool,
/// Whether the committed transaction annotation was validated.
pub(crate) materialized: bool,
/// Number of exact annotation rows returned by the bounded query.
pub(crate) annotation_count: u32,
/// Whether the second replay produced no failures or new materialized output.
pub(crate) idempotence_validated: bool,
/// Pretty JSON representation of the exact execution plan.
pub(crate) plan_json: std::string::String,
/// Pretty JSON representation of the simulation result.
pub(crate) simulation_json: std::string::String,
/// Pretty JSON representation of annotations and post-validation diagnostics.
pub(crate) diagnostics_json: std::string::String,
}
pub(crate) struct DemoExecutionSolanaCoreObserver<'a> {
pub(crate) app_handle: tauri::AppHandle,
pub(crate) cancel_requested: &'a std::sync::atomic::AtomicBool,
}
impl crate::DemoExecutionSolanaCoreObserver<'_> {
fn emit(
&self,
timestamp: std::string::String,
level: &str,
stage: &str,
message: std::string::String,
signature: std::option::Option<std::string::String>,
) {
let payload = crate::DemoExecutionSolanaCoreProgressPayload {
timestamp,
level: level.to_string(),
stage: stage.to_string(),
message,
signature,
};
let emit_result = self.app_handle.emit_to(
"demo_execution_solana_core",
"demo-execution-solana-core-progress",
payload,
);
if let std::result::Result::Err(error) = emit_result {
tracing::warn!(
target: crate::TRACING_TARGET,
action = "emit_execution_progress",
error = %error,
"cannot emit Solana Core execution progress"
);
}
}
fn cancelled(&self) -> bool {
return self.cancel_requested.load(std::sync::atomic::Ordering::Relaxed);
}
}
impl kb_pipeline::BackfillObserver for crate::DemoExecutionSolanaCoreObserver<'_> {
fn on_progress(&self, event: &kb_pipeline::BackfillProgressEvent) {
self.emit(
event.timestamp.clone(),
event.level.code(),
"canonical_insert",
event.message.clone(),
std::option::Option::None,
);
}
fn is_cancelled(&self) -> bool {
return self.cancelled();
}
}
impl kb_pipeline::CoreExtractionObserver for crate::DemoExecutionSolanaCoreObserver<'_> {
fn on_progress(&self, event: &kb_pipeline::CoreExtractionProgressEvent) {
self.emit(
event.timestamp.clone(),
event.level.code(),
"core_extraction",
event.message.clone(),
std::option::Option::None,
);
}
fn is_cancelled(&self) -> bool {
return self.cancelled();
}
}
impl kb_pipeline::DecodeReplayObserver for crate::DemoExecutionSolanaCoreObserver<'_> {
fn on_progress(&self, event: &kb_pipeline::DecodeReplayProgressEvent) {
self.emit(
event.timestamp.clone(),
event.level.code(),
"decode_replay",
event.message.clone(),
std::option::Option::None,
);
}
fn is_cancelled(&self) -> bool {
return self.cancelled();
}
}
impl kb_pipeline_demo_scenarios::SolanaExecutionObserver
for crate::DemoExecutionSolanaCoreObserver<'_>
{
fn on_execution_progress(
&self,
event: &kb_pipeline_demo_scenarios::SolanaExecutionProgressEvent,
) {
self.emit(
event.timestamp.clone(),
event.level.code(),
event.stage.as_str(),
event.message.clone(),
event.signature.clone(),
);
}
fn is_execution_cancelled(&self) -> bool {
return self.cancelled();
}
}
pub(crate) struct DemoExecutionSolanaCoreRunGuard<'a> {
pub(crate) running: &'a std::sync::atomic::AtomicBool,
}
impl std::ops::Drop for crate::DemoExecutionSolanaCoreRunGuard<'_> {
fn drop(&mut self) {
self.running.store(false, std::sync::atomic::Ordering::Release);
}
}
pub(crate) fn devnet_profile_options(
config: &kb_config::AppConfig,
) -> std::vec::Vec<crate::DemoExecutionSolanaCoreProfileOption> {
let mut output = std::vec::Vec::new();
for profile in &config.profiles {
if profile.wallet.cluster != "devnet"
|| !profile.wallet.temporary_wallet_enabled
|| !profile.wallet.temporary_wallet_persist
{
continue;
}
output.push(crate::DemoExecutionSolanaCoreProfileOption {
name: profile.name.clone(),
wallet_alias: profile.wallet.temporary_wallet_alias.clone(),
max_spend_lamports: profile.execution.devnet_max_spend_lamports,
max_airdrop_lamports: profile.execution.devnet_airdrop_max_lamports,
send_enabled: profile.wallet.devnet_send_enabled,
require_operator_confirmation: profile.execution.require_operator_confirmation,
});
}
return output;
}
pub(crate) fn select_devnet_profile(
config: &kb_config::AppConfig,
profile_name: &str,
) -> std::result::Result<kb_config::ProfileConfig, std::string::String> {
for profile in &config.profiles {
if profile.name == profile_name
&& profile.wallet.cluster == "devnet"
&& profile.wallet.temporary_wallet_enabled
&& profile.wallet.temporary_wallet_persist
{
return std::result::Result::Ok(profile.clone());
}
}
return std::result::Result::Err(format!(
"Devnet execution profile '{profile_name}' is unavailable"
));
}
pub(crate) fn demo_execution_solana_core_summary_payload(
summary: kb_pipeline_demo_scenarios::DevnetSystemTransferSummary,
) -> crate::DemoExecutionSolanaCoreSummaryPayload {
let plan_json = crate::pretty_json(&summary.plan);
let simulation_json = crate::pretty_json(&summary.simulation);
let transaction_signature =
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
let confirmation_status = summary.confirmation.as_ref().map(|value| {
return crate::confirmation_status_code(value.status);
});
let canonical_inserted = summary.backfill.as_ref().map(|value| return value.canonical_inserted);
let core_extracted = summary.core_extraction.as_ref().map(|value| return value.extracted);
let decode_completed = summary.decode_replay.as_ref().map(|value| return value.completed);
let decode_failed_inputs =
summary.decode_replay.as_ref().map(|value| return value.failed_inputs);
let diagnostics_value = serde_json::json!({
"airdropConfirmation": summary.airdrop_confirmation.as_ref(),
"confirmation": summary.confirmation.as_ref(),
"postExecution": summary.post_execution.as_ref(),
"backfill": summary.backfill.as_ref().map(|value| {
return serde_json::json!({
"canonicalInserted": value.canonical_inserted,
"canonicalSkipped": value.canonical_skipped,
"existingSkipped": value.existing_skipped,
"missing": value.missing,
"failed": value.failed
});
}),
"coreExtraction": summary.core_extraction.as_ref().map(|value| {
return serde_json::json!({
"selected": value.selected,
"extracted": value.extracted,
"skipped": value.skipped,
"failed": value.failed
});
}),
"decodeReplay": summary.decode_replay.as_ref().map(|value| {
return serde_json::json!({
"campaignId": value.campaign_id,
"selected": value.selected,
"completed": value.completed,
"unmatched": value.unmatched,
"failedInputs": value.failed_inputs,
"cancelled": value.cancelled
});
})
});
let diagnostics_json = crate::pretty_json(&diagnostics_value);
let cluster = crate::execution_cluster_code(summary.cluster);
return crate::DemoExecutionSolanaCoreSummaryPayload {
profile_name: summary.profile_name,
cluster,
genesis_hash: summary.genesis_hash,
wallet_public_key: summary.wallet.public_key,
recipient: summary.recipient.0,
recipient_existed_before: summary.recipient_existed_before,
recipient_balance_before_lamports: summary.recipient_balance_before_lamports,
recipient_minimum_balance_lamports: summary.recipient_minimum_balance_lamports,
balance_before_lamports: summary.balance_before_lamports,
balance_after_funding_lamports: summary.balance_after_funding_lamports,
fee_lamports: summary.fee.fee_lamports,
simulation_success: summary.simulation.success,
simulation_error: summary.simulation.error,
simulation_logs: summary.simulation.logs,
airdrop_signature: summary.airdrop_signature.map(|value| return value.0),
transaction_signature,
confirmation_status,
canonical_inserted,
core_extracted,
decode_completed,
decode_failed_inputs,
plan_json,
simulation_json,
diagnostics_json,
};
}
pub(crate) fn demo_execution_solana_core_memo_summary_payload(
summary: kb_pipeline_demo_scenarios::DevnetMemoExecutionSummary,
) -> crate::DemoExecutionMemoSummaryPayload {
let plan_json = crate::pretty_json(&summary.plan);
let simulation_json = crate::pretty_json(&summary.simulation);
let transaction_signature =
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
let confirmation_status = summary.confirmation.as_ref().map(|value| {
return crate::confirmation_status_code(value.status);
});
let canonical_inserted = summary
.post_execution
.as_ref()
.is_some_and(|value| return value.canonical_inserted);
let core_extracted =
summary.post_execution.as_ref().is_some_and(|value| return value.core_extracted);
let decode_replayed = summary
.post_execution
.as_ref()
.is_some_and(|value| return value.decode_replayed);
let materialized =
summary.post_execution.as_ref().is_some_and(|value| return value.materialized);
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
return replay.failed_inputs == 0
&& !replay.cancelled
&& replay.processors.iter().all(|processor| {
return processor.failed == 0
&& processor.materialized_outputs == 0
&& processor.materialization_refused == 0;
})
&& replay.processors.iter().map(|processor| return processor.skipped).sum::<u64>()
>= 1;
});
let annotation_count = match u32::try_from(summary.annotations.len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => u32::MAX,
};
let diagnostics_value = serde_json::json!({
"confirmation": summary.confirmation.as_ref(),
"postExecution": summary.post_execution.as_ref(),
"backfill": summary.backfill.as_ref().map(|value| {
return serde_json::json!({
"canonicalInserted": value.canonical_inserted,
"canonicalSkipped": value.canonical_skipped,
"existingSkipped": value.existing_skipped,
"missing": value.missing,
"failed": value.failed
});
}),
"coreExtraction": summary.core_extraction.as_ref().map(|value| {
return serde_json::json!({
"selected": value.selected,
"extracted": value.extracted,
"skipped": value.skipped,
"failed": value.failed
});
}),
"decodeReplay": summary.decode_replay.as_ref().map(|value| {
return serde_json::json!({
"campaignId": value.campaign_id,
"selected": value.selected,
"completed": value.completed,
"failedInputs": value.failed_inputs,
"processors": value.processors.iter().map(|processor| {
return serde_json::json!({
"name": processor.processor_name,
"version": processor.processor_version,
"decoded": processor.decoded,
"skipped": processor.skipped,
"failed": processor.failed,
"materializedOutputs": processor.materialized_outputs,
"materializationRefused": processor.materialization_refused
});
}).collect::<std::vec::Vec<_>>()
});
}),
"idempotenceReplay": summary.idempotence_replay.as_ref().map(|value| {
return serde_json::json!({
"campaignId": value.campaign_id,
"selected": value.selected,
"completed": value.completed,
"failedInputs": value.failed_inputs,
"processors": value.processors.iter().map(|processor| {
return serde_json::json!({
"name": processor.processor_name,
"version": processor.processor_version,
"decoded": processor.decoded,
"skipped": processor.skipped,
"failed": processor.failed,
"materializedOutputs": processor.materialized_outputs,
"materializationRefused": processor.materialization_refused
});
}).collect::<std::vec::Vec<_>>()
});
}),
"annotations": summary.annotations
});
let diagnostics_json = crate::pretty_json(&diagnostics_value);
return crate::DemoExecutionMemoSummaryPayload {
profile_name: summary.profile_name,
cluster: crate::execution_cluster_code(summary.cluster),
genesis_hash: summary.genesis_hash,
wallet_public_key: summary.wallet.public_key,
balance_lamports: summary.balance_lamports.to_string(),
fee_lamports: summary.fee.fee_lamports.map(|value| return value.to_string()),
simulation_success: summary.simulation.success,
simulation_error: summary.simulation.error,
transaction_signature,
confirmation_status,
canonical_inserted,
core_extracted,
decode_replayed,
materialized,
annotation_count,
idempotence_validated,
plan_json,
simulation_json,
diagnostics_json,
};
}
pub(crate) fn execution_cluster_code(
cluster: kb_lib::ExApiExecutionCluster,
) -> std::string::String {
return match cluster {
kb_lib::ExApiExecutionCluster::Localnet => "localnet".to_string(),
kb_lib::ExApiExecutionCluster::Devnet => "devnet".to_string(),
kb_lib::ExApiExecutionCluster::Testnet => "testnet".to_string(),
kb_lib::ExApiExecutionCluster::Mainnet => "mainnet".to_string(),
};
}
pub(crate) fn confirmation_status_code(
status: kb_lib::ExApiExecutionConfirmationStatus,
) -> std::string::String {
return match status {
kb_lib::ExApiExecutionConfirmationStatus::Processed => "processed".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::Confirmed => "confirmed".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::Finalized => "finalized".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::Failed => "failed".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::Expired => "expired".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::TimedOut => "timed_out".to_string(),
};
}
pub(crate) fn pretty_json<T>(value: &T) -> std::string::String
where
T: serde::Serialize,
{
return match serde_json::to_string_pretty(value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
serde_json::json!({"serializationError": error.to_string()}).to_string()
},
};
}
#[cfg(test)]
mod tests {
#[test]
fn example_config_exposes_one_devnet_execution_profile() {
let config =
match kb_config::parse_config_json(include_str!("../../config/example.config.json")) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("example config parse failed: {error}"),
};
let profiles = crate::devnet_profile_options(&config);
assert_eq!(profiles.len(), 1);
assert_eq!(profiles[0].name, "local_devnet");
assert!(profiles[0].send_enabled);
assert!(profiles[0].max_spend_lamports >= 1_000_000);
}
#[test]
fn profile_selection_rejects_mainnet_profiles() {
let config =
match kb_config::parse_config_json(include_str!("../../config/example.config.json")) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("example config parse failed: {error}"),
};
assert!(crate::select_devnet_profile(&config, "local_devnet").is_ok());
assert!(crate::select_devnet_profile(&config, "mainnet").is_err());
}
}

View File

@@ -0,0 +1,71 @@
// file: kb-app-demo-desktop/src/demo_execution_spl.rs
// version: 3
//! Dedicated Tauri window for bounded SPL execution on Devnet.
/// Frontend payload for one independent Devnet SPL validation scenario.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_spl/DevnetSplValidationScenarioPayload.ts"
)]
pub(crate) struct DevnetSplValidationScenarioPayload {
/// Stable scenario identifier.
pub id: std::string::String,
/// Operator-visible label.
pub label: std::string::String,
/// Stable family code.
pub family: std::string::String,
/// Stable executor operation code.
pub operation_code: std::string::String,
/// Current implementation status.
pub implementation_status: std::string::String,
/// Whether the scenario requires proof material.
pub proof_required: bool,
/// Ordered required fixture variables.
pub required_fixture_variables: std::vec::Vec<std::string::String>,
}
#[cfg(test)]
mod tests {
#[test]
fn memo_summary_keeps_lamports_as_json_strings() {
let payload = crate::DemoExecutionMemoSummaryPayload {
profile_name: "local_devnet".to_string(),
cluster: "devnet".to_string(),
genesis_hash: "EtWTRABZaYq6iMfeYKouRu166VU2xqa1".to_string(),
wallet_public_key: kb_program_ids::SYSTEM_PROGRAM_ID.to_string(),
balance_lamports: u64::MAX.to_string(),
fee_lamports: std::option::Option::Some(u64::MAX.to_string()),
simulation_success: true,
simulation_error: std::option::Option::None,
transaction_signature: std::option::Option::None,
confirmation_status: std::option::Option::None,
canonical_inserted: false,
core_extracted: false,
decode_replayed: false,
materialized: false,
annotation_count: 0,
idempotence_validated: false,
plan_json: "{}".to_string(),
simulation_json: "{}".to_string(),
diagnostics_json: "{}".to_string(),
};
let json = match serde_json::to_value(payload) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
assert_eq!(
std::option::Option::Some(error.to_string()),
std::option::Option::None,
"Memo summary serialization failed"
);
return;
},
};
assert!(json["balanceLamports"].is_string());
assert!(json["feeLamports"].is_string());
assert_eq!(json["balanceLamports"], u64::MAX.to_string());
}
}

View File

@@ -0,0 +1,438 @@
// file: kb-app-demo-desktop/src/demo_spl_ata.rs
// version: 4
//! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads.
use ts_rs::TS; // rust-rules: derive-import
/// Request for one representative ATA creation on Devnet.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoExecutionSplAtaRequest.ts"
)]
pub(crate) struct DemoExecutionSplAtaRequest {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Wallet owner used by canonical derivation.
pub(crate) wallet_owner: std::string::String,
/// Mint account.
pub(crate) mint: std::string::String,
/// `classic` or `token2022`.
pub(crate) token_program: std::string::String,
/// `create` or `create_idempotent`.
pub(crate) mode: std::string::String,
/// Whether to sign and submit after simulation.
pub(crate) submit: bool,
/// Explicit operator confirmation.
pub(crate) operator_confirmed: bool,
/// Whether post-execution outputs should be force-replayed.
pub(crate) force_post_validation_replay: bool,
}
/// Request used to derive the representative wallet ATA before execution.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationRequest.ts"
)]
pub(crate) struct DemoSplAtaDerivationRequest {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Mint account.
pub(crate) mint: std::string::String,
/// `classic` or `token2022`.
pub(crate) token_program: std::string::String,
}
/// Readonly payer, wallet, Token Program and derived ATA values.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationPayload.ts"
)]
pub(crate) struct DemoSplAtaDerivationPayload {
/// Profile wallet used as payer.
pub(crate) payer: std::string::String,
/// Wallet owner selected by the representative panel.
pub(crate) wallet_owner: std::string::String,
/// Exact Token Program ID.
pub(crate) token_program_id: std::string::String,
/// Canonically derived ATA.
pub(crate) associated_token_account: std::string::String,
}
/// UI-safe ATA execution result.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoExecutionSplAtaSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionSplAtaSummaryPayload {
/// Stable operation code.
pub(crate) operation: std::string::String,
/// Profile name.
pub(crate) profile_name: std::string::String,
/// Profile wallet public key.
pub(crate) wallet_public_key: std::string::String,
/// Target Token Program ID.
pub(crate) token_program_id: std::string::String,
/// Derived ATA from the exact plan.
pub(crate) associated_token_account: std::string::String,
/// Wallet balance as lossless JSON text.
pub(crate) balance_lamports: std::string::String,
/// Fee estimate as lossless JSON text.
pub(crate) fee_lamports: std::option::Option<std::string::String>,
/// Aggregate stateful readiness.
pub(crate) readiness_status: std::string::String,
/// Exact simulation outcome.
pub(crate) simulation_success: bool,
/// Runtime simulation error.
pub(crate) simulation_error: std::option::Option<std::string::String>,
/// Submitted signature.
pub(crate) transaction_signature: std::option::Option<std::string::String>,
/// Terminal confirmation status.
pub(crate) confirmation_status: std::option::Option<std::string::String>,
/// Number of ATA-owned materialized facts.
pub(crate) materialization_count: u32,
/// Whether the second replay created no output.
pub(crate) idempotence_validated: bool,
/// Exact plan JSON.
pub(crate) plan_json: std::string::String,
/// Stateful readiness JSON.
pub(crate) readiness_json: std::string::String,
/// Simulation JSON.
pub(crate) simulation_json: std::string::String,
/// Confirmation and replay diagnostics JSON.
pub(crate) diagnostics_json: std::string::String,
}
/// Bounded ATA lifecycle journal request.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRequest.ts"
)]
pub(crate) struct DemoSplAtaJournalRequest {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Optional partial signature.
pub(crate) signature_contains: std::option::Option<std::string::String>,
/// Optional exact mint.
pub(crate) mint: std::option::Option<std::string::String>,
/// Optional exact ATA.
pub(crate) associated_token_account: std::option::Option<std::string::String>,
/// Optional exact materialized operation.
pub(crate) operation: std::option::Option<std::string::String>,
/// Maximum returned rows.
pub(crate) limit: u32,
}
/// One UI-safe ATA lifecycle journal row.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRow.ts"
)]
pub(crate) struct DemoSplAtaJournalRow {
/// Transaction signature.
pub(crate) signature: std::string::String,
/// Slot as lossless JSON text.
pub(crate) slot: std::string::String,
/// Materialized family.
pub(crate) family: std::string::String,
/// Lifecycle operation.
pub(crate) operation: std::option::Option<std::string::String>,
/// Exact mint when present.
pub(crate) mint: std::option::Option<std::string::String>,
/// Exact ATA when present.
pub(crate) associated_token_account: std::option::Option<std::string::String>,
/// Full bounded payload JSON.
pub(crate) payload_json: std::string::String,
}
pub(crate) async fn load_profile_wallet(
profile: &kb_config::ProfileConfig,
) -> std::result::Result<kb_wallet::TemporaryWallet, std::string::String> {
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
let directory = if configured.is_absolute() {
configured
} else {
crate::workspace_root_dir().join(configured)
};
let store = match kb_wallet::TemporaryWalletStore::new(directory) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let alias = match kb_wallet::WalletAlias::parse(profile.wallet.temporary_wallet_alias.clone()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return match store.load_or_create(alias).await {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}
pub(crate) fn parse_token_program(
value: &str,
) -> std::result::Result<kb_lib::ExSplAssociatedTokenProgram, std::string::String> {
return match value.trim() {
"classic" => std::result::Result::Ok(kb_lib::ExSplAssociatedTokenProgram::Classic),
"token2022" => std::result::Result::Ok(kb_lib::ExSplAssociatedTokenProgram::Token2022),
_ => std::result::Result::Err("Token Program must be classic or token2022".to_string()),
};
}
pub(crate) fn derive_ata(
wallet: &str,
mint: &str,
token_program: &str,
) -> std::result::Result<std::string::String, std::string::String> {
let wallet: solana_pubkey::Pubkey = match wallet.parse() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!("invalid wallet: {error}"));
},
};
let mint: solana_pubkey::Pubkey = match mint.parse() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!("invalid mint: {error}"));
},
};
let token_program: solana_pubkey::Pubkey = match token_program.parse() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!("invalid Token Program: {error}"));
},
};
return std::result::Result::Ok(
spl_associated_token_account_interface::address::get_associated_token_address_with_program_id(
&wallet,
&mint,
&token_program,
)
.to_string(),
);
}
pub(crate) fn demo_spl_ata_summary_payload(
summary: kb_pipeline_demo_scenarios::DevnetSplAssociatedTokenAccountExecutionSummary,
) -> crate::DemoExecutionSplAtaSummaryPayload {
let (associated_token_account, token_program_id) = match summary.plan.instructions.first() {
std::option::Option::Some(instruction) => {
let ata = match instruction.accounts.get(1) {
std::option::Option::Some(account) => account.pubkey.0.clone(),
std::option::Option::None => std::string::String::new(),
};
let token_program = match instruction.accounts.last() {
std::option::Option::Some(account) => account.pubkey.0.clone(),
std::option::Option::None => std::string::String::new(),
};
(ata, token_program)
},
std::option::Option::None => (std::string::String::new(), std::string::String::new()),
};
let transaction_signature =
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
let confirmation_status = summary.confirmation.as_ref().map(|value| {
return crate::confirmation_status_code(value.status);
});
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
return replay.failed_inputs == 0
&& replay.processors.iter().all(|processor| {
return processor.failed == 0
&& processor.materialized_outputs == 0
&& processor.materialization_refused == 0;
});
});
let materialization_count = match u32::try_from(summary.materializations.len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => u32::MAX,
};
let diagnostics = serde_json::json!({
"confirmation": summary.confirmation.as_ref(),
"postStateValidation": summary.post_state_validation.as_ref(),
"postExecution": summary.post_execution.as_ref(),
"backfill": summary.backfill.as_ref().map(|value| {
return serde_json::json!({
"candidatesCompleted": value.candidates_completed,
"candidatesCancelled": value.candidates_cancelled,
"candidatesNotStarted": value.candidates_not_started,
"canonicalInserted": value.canonical_inserted,
"canonicalSkipped": value.canonical_skipped,
"existingSkipped": value.existing_skipped,
"missing": value.missing,
"failed": value.failed
});
}),
"coreExtraction": summary.core_extraction.as_ref().map(|value| {
return serde_json::json!({
"selected": value.selected,
"extracted": value.extracted,
"skipped": value.skipped,
"failed": value.failed,
"cancelled": value.cancelled
});
}),
"decodeReplay": summary.decode_replay.as_ref().map(|value| {
return serde_json::json!({
"campaignId": value.campaign_id,
"selected": value.selected,
"completed": value.completed,
"unmatched": value.unmatched,
"failedInputs": value.failed_inputs,
"cancelled": value.cancelled
});
}),
"idempotenceReplay": summary.idempotence_replay.as_ref().map(|value| {
return serde_json::json!({
"campaignId": value.campaign_id,
"selected": value.selected,
"completed": value.completed,
"unmatched": value.unmatched,
"failedInputs": value.failed_inputs,
"cancelled": value.cancelled,
"materializedOutputs": value.processors.iter().map(|processor| {
return processor.materialized_outputs;
}).sum::<u64>(),
"materializationRefused": value.processors.iter().map(|processor| {
return processor.materialization_refused;
}).sum::<u64>()
});
}),
"materializations": summary.materializations.iter().map(|value| {
return serde_json::json!({
"processorName": value.processor_name,
"outputKey": value.output_key,
"family": value.materialized_family,
"signature": value.signature,
"slot": value.slot.to_string(),
"payload": value.payload_json
});
}).collect::<std::vec::Vec<_>>()
});
let plan_json = crate::pretty_json(&summary.plan);
let readiness_json = crate::pretty_json(&summary.stateful_readiness);
let simulation_json = crate::pretty_json(&summary.simulation);
let diagnostics_json = crate::pretty_json(&diagnostics);
return crate::DemoExecutionSplAtaSummaryPayload {
operation: summary.stateful_readiness.operation_code,
profile_name: summary.profile_name,
wallet_public_key: summary.wallet.public_key,
token_program_id,
associated_token_account,
balance_lamports: summary.balance_lamports.to_string(),
fee_lamports: summary.fee.fee_lamports.map(|value| return value.to_string()),
readiness_status: match summary.stateful_readiness.status {
kb_pipeline::SplAssociatedTokenAccountStatefulReadinessStatus::Ready => {
"ready".to_string()
},
kb_pipeline::SplAssociatedTokenAccountStatefulReadinessStatus::Blocked => {
"blocked".to_string()
},
},
simulation_success: summary.simulation.success,
simulation_error: summary.simulation.error,
transaction_signature,
confirmation_status,
materialization_count,
idempotence_validated,
plan_json,
readiness_json,
simulation_json,
diagnostics_json,
};
}
pub(crate) fn journal_matches(
payload: &serde_json::Value,
request: &crate::DemoSplAtaJournalRequest,
) -> bool {
for (expected, key) in [
(&request.operation, "operation"),
(&request.mint, "mint"),
(&request.associated_token_account, "associatedTokenAccount"),
] {
if expected.as_ref().is_some_and(|value| {
return payload.get(key).and_then(serde_json::Value::as_str)
!= std::option::Option::Some(value.as_str());
}) {
return false;
}
}
return true;
}
pub(crate) fn journal_row(row: kb_store::MaterializedEventQueryRow) -> crate::DemoSplAtaJournalRow {
let text = |key: &str| {
return row
.payload_json
.get(key)
.and_then(serde_json::Value::as_str)
.map(std::string::ToString::to_string);
};
let operation = text("operation");
let mint = text("mint");
let associated_token_account = text("associatedTokenAccount");
let payload_json = crate::pretty_json(&row.payload_json);
return crate::DemoSplAtaJournalRow {
signature: row.signature,
slot: row.slot.to_string(),
family: row.materialized_family,
operation,
mint,
associated_token_account,
payload_json,
};
}
#[cfg(test)]
mod tests {
#[test]
fn classic_and_token2022_derivations_are_distinct_and_canonical() {
let wallet = kb_program_ids::SYSTEM_PROGRAM_ID;
let mint = "So11111111111111111111111111111111111111112";
let classic = crate::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN_PROGRAM_ID)
.unwrap_or_else(|error| panic!("classic derivation failed: {error}"));
let token2022 = crate::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN2022_PROGRAM_ID)
.unwrap_or_else(|error| panic!("Token-2022 derivation failed: {error}"));
assert_eq!(classic, "aqxoAhCwpy3oB1BpNw9hL1HdLYLgPpbPjzxDrrQj3Fs");
assert_eq!(token2022, "2sZUUBGq1i6aE47ZoxCaCW89jmYm2EXLPPmNMgMDXHMS");
}
#[test]
fn lifecycle_journal_filters_are_exact() {
let request = crate::DemoSplAtaJournalRequest {
profile_name: "local_devnet".to_string(),
signature_contains: std::option::Option::None,
mint: std::option::Option::Some("mint111".to_string()),
associated_token_account: std::option::Option::Some("ata111".to_string()),
operation: std::option::Option::Some("create_idempotent".to_string()),
limit: 100,
};
assert!(crate::journal_matches(
&serde_json::json!({
"operation": "create_idempotent",
"mint": "mint111",
"associatedTokenAccount": "ata111"
}),
&request,
));
assert!(!crate::journal_matches(
&serde_json::json!({
"operation": "create",
"mint": "mint111",
"associatedTokenAccount": "ata111"
}),
&request,
));
}
}

View File

@@ -0,0 +1,493 @@
// file: kb-app-demo-desktop/src/demo_spl_token.rs
// version: 2
//! Thin Tauri adapter for classic SPL Token execution and materialized journals.
use ts_rs::TS; // rust-rules: derive-import
/// Request for one checked classic SPL Token transfer on Devnet.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token/DemoExecutionSplTokenRequest.ts"
)]
pub(crate) struct DemoExecutionSplTokenRequest {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Source token account.
pub(crate) source: std::string::String,
/// Exact mint account carried by `TransferChecked`.
pub(crate) mint: std::string::String,
/// Destination token account.
pub(crate) destination: std::string::String,
/// Simple authority resolved by the selected profile wallet.
pub(crate) authority: std::string::String,
/// Exact raw amount represented as a decimal string.
pub(crate) amount_raw: std::string::String,
/// Expected mint decimals carried by the wire.
pub(crate) decimals: u8,
/// Whether the transaction should be signed and submitted after simulation.
pub(crate) submit: bool,
/// Explicit operator confirmation for signed submission.
pub(crate) operator_confirmed: bool,
/// Whether post-execution core and decode outputs should be force-replayed.
pub(crate) force_post_validation_replay: bool,
}
/// UI-safe result of one checked SPL Token transfer orchestration.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token/DemoExecutionSplTokenSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionSplTokenSummaryPayload {
/// Stable operation code.
pub(crate) operation: std::string::String,
/// Profile used by the orchestration.
pub(crate) profile_name: std::string::String,
/// Classified cluster code.
pub(crate) cluster: std::string::String,
/// Genesis hash returned by the endpoint.
pub(crate) genesis_hash: std::string::String,
/// Source wallet public key.
pub(crate) wallet_public_key: std::string::String,
/// Exact wallet balance rendered without JSON integer precision loss.
pub(crate) balance_lamports: std::string::String,
/// Estimated fee rendered without JSON integer precision loss.
pub(crate) fee_lamports: std::option::Option<std::string::String>,
/// Exact raw token amount.
pub(crate) amount_raw: std::string::String,
/// Expected mint decimals.
pub(crate) decimals: u8,
/// Aggregate stateful preflight status.
pub(crate) readiness_status: std::string::String,
/// Whether exact simulation succeeded.
pub(crate) simulation_success: bool,
/// Runtime simulation error when present.
pub(crate) simulation_error: std::option::Option<std::string::String>,
/// Submitted transaction signature.
pub(crate) transaction_signature: std::option::Option<std::string::String>,
/// Terminal confirmation status.
pub(crate) confirmation_status: std::option::Option<std::string::String>,
/// Whether canonical hydration was validated.
pub(crate) canonical_inserted: bool,
/// Whether core extraction was validated.
pub(crate) core_extracted: bool,
/// Whether SPL Token decode replay was validated.
pub(crate) decode_replayed: bool,
/// Number of materialized rows for the submitted instruction.
pub(crate) materialization_count: u32,
/// Whether the second replay produced no failure or new output.
pub(crate) idempotence_validated: bool,
/// Pretty JSON representation of the exact execution plan.
pub(crate) plan_json: std::string::String,
/// Pretty JSON representation of stateful readiness.
pub(crate) readiness_json: std::string::String,
/// Pretty JSON representation of the exact simulation result.
pub(crate) simulation_json: std::string::String,
/// Pretty JSON representation of confirmation and replay diagnostics.
pub(crate) diagnostics_json: std::string::String,
}
/// Bounded exact filters for the classic SPL Token materialized journal.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalRequest.ts"
)]
pub(crate) struct DemoSplTokenJournalRequest {
/// Devnet profile whose PostgreSQL store is queried.
pub(crate) profile_name: std::string::String,
/// Optional partial signature handled by the bounded store query.
pub(crate) signature_contains: std::option::Option<std::string::String>,
/// Optional exact mint account.
pub(crate) mint: std::option::Option<std::string::String>,
/// Optional exact account occurring in the ordered account list.
pub(crate) account: std::option::Option<std::string::String>,
/// Optional exact materialized family.
pub(crate) family: std::option::Option<std::string::String>,
/// Optional exact operation code without the `spl_token.` prefix.
pub(crate) operation: std::option::Option<std::string::String>,
/// Maximum number of rows returned after typed filtering.
pub(crate) limit: u32,
}
/// UI-safe materialized classic SPL Token journal row.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalRow.ts"
)]
pub(crate) struct DemoSplTokenJournalRow {
/// Materializer processor name.
pub(crate) processor_name: std::string::String,
/// Materializer processor version.
pub(crate) processor_version: std::string::String,
/// Stable processor-owned output key.
pub(crate) output_key: std::string::String,
/// Transaction signature.
pub(crate) signature: std::string::String,
/// Decimal slot rendered as text to preserve JSON precision.
pub(crate) slot: std::string::String,
/// Materialized family code.
pub(crate) family: std::string::String,
/// Exact operation when present.
pub(crate) operation: std::option::Option<std::string::String>,
/// Exact mint account when explicitly available.
pub(crate) mint: std::option::Option<std::string::String>,
/// Exact raw amount when carried by the materialized fact.
pub(crate) amount_raw: std::option::Option<std::string::String>,
/// Stable outer or inner instruction path when present.
pub(crate) instruction_path: std::option::Option<std::string::String>,
/// Ordered account keys preserved by the decoder.
pub(crate) account_keys: std::vec::Vec<std::string::String>,
/// Complete bounded typed payload rendered as JSON text.
pub(crate) payload_json: std::string::String,
/// Database creation timestamp.
pub(crate) created_at: std::string::String,
/// Database replacement timestamp.
pub(crate) updated_at: std::string::String,
}
pub(crate) fn demo_spl_token_summary_payload(
summary: kb_pipeline_demo_scenarios::DevnetSplTokenExecutionSummary,
amount_raw: std::string::String,
decimals: u8,
) -> crate::DemoExecutionSplTokenSummaryPayload {
let plan_json = crate::pretty_json(&summary.plan);
let readiness_json = crate::pretty_json(&summary.stateful_readiness);
let simulation_json = crate::pretty_json(&summary.simulation);
let transaction_signature =
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
let confirmation_status = summary.confirmation.as_ref().map(|value| {
return crate::confirmation_status_code(value.status);
});
let post_execution = summary.post_execution.as_ref();
let canonical_inserted = post_execution.is_some_and(|value| return value.canonical_inserted);
let core_extracted = post_execution.is_some_and(|value| return value.core_extracted);
let decode_replayed = post_execution.is_some_and(|value| return value.decode_replayed);
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
return replay.failed_inputs == 0
&& !replay.cancelled
&& replay.processors.iter().all(|processor| {
return processor.failed == 0
&& processor.materialized_outputs == 0
&& processor.materialization_refused == 0;
});
});
let materialization_count = match u32::try_from(summary.materializations.len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => u32::MAX,
};
let diagnostics = serde_json::json!({
"confirmation": summary.confirmation,
"postExecution": summary.post_execution,
"backfill": summary.backfill.as_ref().map(|value| {
return serde_json::json!({
"canonicalInserted": value.canonical_inserted,
"canonicalSkipped": value.canonical_skipped,
"existingSkipped": value.existing_skipped,
"missing": value.missing,
"failed": value.failed
});
}),
"coreExtraction": summary.core_extraction.as_ref().map(|value| {
return serde_json::json!({
"selected": value.selected,
"extracted": value.extracted,
"skipped": value.skipped,
"failed": value.failed,
"cancelled": value.cancelled
});
}),
"decodeReplay": summary.decode_replay.as_ref().map(|value| {
return replay_diagnostics(value);
}),
"idempotenceReplay": summary.idempotence_replay.as_ref().map(|value| {
return replay_diagnostics(value);
}),
"materializations": summary.materializations
});
return crate::DemoExecutionSplTokenSummaryPayload {
operation: summary.stateful_readiness.operation_code,
profile_name: summary.profile_name,
cluster: crate::execution_cluster_code(summary.cluster),
genesis_hash: summary.genesis_hash,
wallet_public_key: summary.wallet.public_key,
balance_lamports: summary.balance_lamports.to_string(),
fee_lamports: summary.fee.fee_lamports.map(|value| return value.to_string()),
amount_raw,
decimals,
readiness_status: match summary.stateful_readiness.status {
kb_pipeline::SplTokenStatefulReadinessStatus::Ready => "ready".to_string(),
kb_pipeline::SplTokenStatefulReadinessStatus::Blocked => "blocked".to_string(),
},
simulation_success: summary.simulation.success,
simulation_error: summary.simulation.error,
transaction_signature,
confirmation_status,
canonical_inserted,
core_extracted,
decode_replayed,
materialization_count,
idempotence_validated,
plan_json,
readiness_json,
simulation_json,
diagnostics_json: crate::pretty_json(&diagnostics),
};
}
fn replay_diagnostics(summary: &kb_pipeline::DecodeReplaySummary) -> serde_json::Value {
return serde_json::json!({
"campaignId": summary.campaign_id,
"selected": summary.selected,
"completed": summary.completed,
"failedInputs": summary.failed_inputs,
"cancelled": summary.cancelled,
"processors": summary.processors.iter().map(|processor| {
return serde_json::json!({
"name": processor.processor_name,
"version": processor.processor_version,
"decoded": processor.decoded,
"skipped": processor.skipped,
"failed": processor.failed,
"materializedOutputs": processor.materialized_outputs,
"materializationRefused": processor.materialization_refused
});
}).collect::<std::vec::Vec<_>>()
});
}
pub(crate) fn validate_journal_request(
request: crate::DemoSplTokenJournalRequest,
) -> std::result::Result<crate::DemoSplTokenJournalRequest, std::string::String> {
if request.limit == 0 || request.limit > kb_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS {
return std::result::Result::Err(format!(
"journal limit must be between 1 and {}",
kb_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS
));
}
if request.profile_name.trim().is_empty() {
return std::result::Result::Err("journal profile name must not be empty".to_string());
}
let signature_contains =
match bounded_optional(request.signature_contains, "signature filter", 128) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mint = match bounded_optional(request.mint, "mint filter", 64) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let account = match bounded_optional(request.account, "account filter", 64) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let operation = match bounded_optional(request.operation, "operation filter", 64) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let family = match bounded_optional(request.family, "family filter", 32) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if family.as_ref().is_some_and(|value| {
return !matches!(value.as_str(), "token_account" | "admin" | "risk");
}) {
return std::result::Result::Err(
"journal family must be token_account, admin or risk".to_string(),
);
}
return std::result::Result::Ok(crate::DemoSplTokenJournalRequest {
profile_name: request.profile_name.trim().to_string(),
signature_contains,
mint,
account,
family,
operation,
limit: request.limit,
});
}
fn bounded_optional(
value: std::option::Option<std::string::String>,
label: &str,
maximum_length: usize,
) -> std::result::Result<std::option::Option<std::string::String>, std::string::String> {
let trimmed = value.map(|text| return text.trim().to_string());
let trimmed = trimmed.filter(|text| return !text.is_empty());
if trimmed.as_ref().is_some_and(|text| return text.len() > maximum_length) {
return std::result::Result::Err(format!("{label} must not exceed {maximum_length} bytes"));
}
return std::result::Result::Ok(trimmed);
}
pub(crate) fn payload_matches(
payload: &serde_json::Value,
request: &crate::DemoSplTokenJournalRequest,
) -> bool {
if request.operation.as_ref().is_some_and(|expected| {
return payload.get("operation").and_then(serde_json::Value::as_str)
!= std::option::Option::Some(expected.as_str());
}) {
return false;
}
if request.mint.as_ref().is_some_and(|expected| {
return payload_mint(payload).as_deref() != std::option::Option::Some(expected.as_str());
}) {
return false;
}
if request.account.as_ref().is_some_and(|expected| {
return !payload_account_keys(payload).iter().any(|value| return value == expected);
}) {
return false;
}
return true;
}
pub(crate) fn demo_spl_token_journal_row(
row: kb_store::MaterializedEventQueryRow,
) -> DemoSplTokenJournalRow {
let operation = row
.payload_json
.get("operation")
.and_then(serde_json::Value::as_str)
.map(std::string::ToString::to_string);
let amount_raw = row
.payload_json
.get("amountRaw")
.or_else(|| {
return row
.payload_json
.get("parameters")
.and_then(|value| return value.get("amountRaw"));
})
.and_then(serde_json::Value::as_str)
.map(std::string::ToString::to_string);
let instruction_path = row
.payload_json
.get("instructionPath")
.and_then(serde_json::Value::as_str)
.map(std::string::ToString::to_string);
let payload_json = crate::pretty_json(&row.payload_json);
return DemoSplTokenJournalRow {
processor_name: row.processor_name,
processor_version: row.processor_version,
output_key: row.output_key,
signature: row.signature,
slot: row.slot.to_string(),
family: row.materialized_family,
operation,
mint: payload_mint(&row.payload_json),
amount_raw,
instruction_path,
account_keys: payload_account_keys(&row.payload_json),
payload_json,
created_at: row.created_at,
updated_at: row.updated_at,
};
}
fn payload_mint(payload: &serde_json::Value) -> std::option::Option<std::string::String> {
let direct = payload.get("mint").and_then(serde_json::Value::as_str);
if let std::option::Option::Some(value) = direct {
return std::option::Option::Some(value.to_string());
}
return payload.get("accounts").and_then(serde_json::Value::as_array).and_then(|rows| {
return rows.iter().find_map(|row| {
if row.get("role").and_then(serde_json::Value::as_str)
!= std::option::Option::Some("mint")
{
return std::option::Option::None;
}
return row
.get("accountKey")
.and_then(serde_json::Value::as_str)
.map(std::string::ToString::to_string);
});
});
}
fn payload_account_keys(payload: &serde_json::Value) -> std::vec::Vec<std::string::String> {
let values = payload.get("accounts").and_then(serde_json::Value::as_array).map(|rows| {
return rows
.iter()
.filter_map(|row| {
return row
.get("accountKey")
.and_then(serde_json::Value::as_str)
.map(std::string::ToString::to_string);
})
.collect();
});
return match values {
std::option::Option::Some(values) => values,
std::option::Option::None => std::vec::Vec::new(),
};
}
#[cfg(test)]
mod tests {
#[test]
fn journal_filters_are_exact_bounded_and_preserve_raw_amounts() {
let request = crate::DemoSplTokenJournalRequest {
profile_name: "local_devnet".to_string(),
signature_contains: std::option::Option::None,
mint: std::option::Option::Some("mint111".to_string()),
account: std::option::Option::Some("source111".to_string()),
family: std::option::Option::Some("token_account".to_string()),
operation: std::option::Option::Some("transfer_checked".to_string()),
limit: 25,
};
let payload = serde_json::json!({
"operation": "transfer_checked",
"mint": "mint111",
"amountRaw": "18446744073709551615",
"accounts": [
{"role": "source", "accountKey": "source111"},
{"role": "mint", "accountKey": "mint111"}
]
});
assert!(crate::payload_matches(&payload, &request));
assert_eq!(
payload.get("amountRaw").and_then(serde_json::Value::as_str),
std::option::Option::Some("18446744073709551615")
);
let row = crate::demo_spl_token_journal_row(kb_store::MaterializedEventQueryRow {
processor_name: "spl_token_accounts".to_string(),
processor_version: "0.4.4".to_string(),
input_key: "input".to_string(),
output_key: "output".to_string(),
source_event_key: "event".to_string(),
source_decoder_name: "spl_token".to_string(),
source_decoder_version: "0.4.4".to_string(),
signature: "signature".to_string(),
slot: u64::MAX,
materialized_family: "token_account".to_string(),
payload_json: payload,
created_at: "created".to_string(),
updated_at: "updated".to_string(),
});
assert_eq!(row.slot, u64::MAX.to_string());
assert_eq!(row.amount_raw, std::option::Option::Some(u64::MAX.to_string()));
}
#[test]
fn journal_rejects_unbounded_or_unknown_family_requests() {
let request = crate::DemoSplTokenJournalRequest {
profile_name: "local_devnet".to_string(),
signature_contains: std::option::Option::None,
mint: std::option::Option::None,
account: std::option::Option::None,
family: std::option::Option::Some("fee".to_string()),
operation: std::option::Option::None,
limit: 501,
};
assert!(crate::validate_journal_request(request).is_err());
}
}

View File

@@ -0,0 +1,399 @@
// file: kb-app-demo-desktop/src/demo_spl_token2022.rs
// version: 4
//! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios.
use ts_rs::TS; // rust-rules: derive-import
/// Public values loaded from one persisted Token-2022 validation fixture.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token2022/DemoSplToken2022FixturePayload.ts"
)]
pub(crate) struct DemoSplToken2022FixturePayload {
/// Fixture file used by the application.
pub(crate) fixture_path: std::string::String,
/// Token-2022 program ID.
pub(crate) program_id: std::string::String,
/// Mint account.
pub(crate) mint: std::string::String,
/// Source token account.
pub(crate) source: std::string::String,
/// Destination token account.
pub(crate) destination: std::string::String,
/// Dedicated empty account reserved for CloseAccount validation.
pub(crate) close_account: std::string::String,
/// Delegate account.
pub(crate) delegate: std::string::String,
/// Profile-wallet authority.
pub(crate) authority: std::string::String,
/// Freeze authority when configured on the mint.
pub(crate) freeze_authority: std::string::String,
/// Mint decimals.
pub(crate) decimals: u8,
/// Default raw amount for the selected scenario.
pub(crate) default_amount_raw: std::string::String,
}
/// Request for one independent public Token-2022 Devnet scenario.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022Request.ts"
)]
pub(crate) struct DemoExecutionSplToken2022Request {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Stable scenario identifier.
pub(crate) scenario_id: std::string::String,
/// Source or target token account depending on the selected operation.
pub(crate) source: std::string::String,
/// Exact mint account.
pub(crate) mint: std::string::String,
/// Destination token account or close-account lamport destination.
pub(crate) destination: std::string::String,
/// Delegate account used by `ApproveChecked`.
pub(crate) delegate: std::string::String,
/// Owner or mint authority resolved by the selected profile wallet.
pub(crate) authority: std::string::String,
/// Freeze authority used by freeze and thaw scenarios.
pub(crate) freeze_authority: std::string::String,
/// Exact raw amount represented as a decimal string.
pub(crate) amount_raw: std::string::String,
/// Expected mint decimals carried by checked instructions.
pub(crate) decimals: u8,
/// Whether the transaction should be signed and submitted after simulation.
pub(crate) submit: bool,
/// Explicit operator confirmation for signed submission.
pub(crate) operator_confirmed: bool,
/// Whether post-execution core and decode outputs should be force-replayed.
pub(crate) force_post_validation_replay: bool,
}
/// UI-safe result of one public Token-2022 Devnet orchestration.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022SummaryPayload.ts"
)]
pub(crate) struct DemoExecutionSplToken2022SummaryPayload {
/// Stable scenario identifier.
pub(crate) scenario_id: std::string::String,
/// Stable operation code.
pub(crate) operation: std::string::String,
/// Profile used by the orchestration.
pub(crate) profile_name: std::string::String,
/// Classified cluster code.
pub(crate) cluster: std::string::String,
/// Genesis hash returned by the endpoint.
pub(crate) genesis_hash: std::string::String,
/// Source wallet public key.
pub(crate) wallet_public_key: std::string::String,
/// Exact wallet balance rendered without JSON integer precision loss.
pub(crate) balance_lamports: std::string::String,
/// Estimated fee rendered without JSON integer precision loss.
pub(crate) fee_lamports: std::option::Option<std::string::String>,
/// Whether exact simulation succeeded.
pub(crate) simulation_success: bool,
/// Runtime simulation error when present.
pub(crate) simulation_error: std::option::Option<std::string::String>,
/// Submitted transaction signature.
pub(crate) transaction_signature: std::option::Option<std::string::String>,
/// Terminal confirmation status.
pub(crate) confirmation_status: std::option::Option<std::string::String>,
/// Whether canonical hydration was validated.
pub(crate) canonical_inserted: bool,
/// Whether core extraction was validated.
pub(crate) core_extracted: bool,
/// Whether Token-2022 decode replay was validated.
pub(crate) decode_replayed: bool,
/// Number of materialized rows for the submitted instruction.
pub(crate) materialization_count: u32,
/// Whether the second replay produced no failure or new output.
pub(crate) idempotence_validated: bool,
/// Pretty JSON representation of the exact execution plan.
pub(crate) plan_json: std::string::String,
/// Pretty JSON representation of stateful preflight.
pub(crate) preflight_json: std::string::String,
/// Pretty JSON representation of the exact simulation result.
pub(crate) simulation_json: std::string::String,
/// Pretty JSON representation of confirmation and replay diagnostics.
pub(crate) diagnostics_json: std::string::String,
}
pub(crate) fn operation_from_request(
request: &crate::DemoExecutionSplToken2022Request,
) -> std::result::Result<kb_lib::ExSplToken2022Operation, std::string::String> {
let authority = kb_lib::ExSplTokenAuthority {
authority: kb_lib::MdPubkey(request.authority.trim().to_string()),
multisig_signers: std::vec::Vec::new(),
};
let freeze_authority = kb_lib::ExSplTokenAuthority {
authority: kb_lib::MdPubkey(request.freeze_authority.trim().to_string()),
multisig_signers: std::vec::Vec::new(),
};
let value = match request.scenario_id.trim() {
"token2022_mint_to_checked" => kb_lib::ExSplTokenSingleOperation::MintToChecked {
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
destination: kb_lib::MdPubkey(request.source.trim().to_string()),
authority,
amount: kb_lib::ExSplTokenAmount(request.amount_raw.trim().to_string()),
decimals: request.decimals,
},
"token2022_transfer_checked" => kb_lib::ExSplTokenSingleOperation::TransferChecked {
source: kb_lib::MdPubkey(request.source.trim().to_string()),
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
destination: kb_lib::MdPubkey(request.destination.trim().to_string()),
authority,
amount: kb_lib::ExSplTokenAmount(request.amount_raw.trim().to_string()),
decimals: request.decimals,
},
"token2022_approve_checked" => kb_lib::ExSplTokenSingleOperation::ApproveChecked {
source: kb_lib::MdPubkey(request.source.trim().to_string()),
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
delegate: kb_lib::MdPubkey(request.delegate.trim().to_string()),
authority,
amount: kb_lib::ExSplTokenAmount(request.amount_raw.trim().to_string()),
decimals: request.decimals,
},
"token2022_revoke" => kb_lib::ExSplTokenSingleOperation::Revoke {
source: kb_lib::MdPubkey(request.source.trim().to_string()),
authority,
},
"token2022_burn_checked" => kb_lib::ExSplTokenSingleOperation::BurnChecked {
source: kb_lib::MdPubkey(request.source.trim().to_string()),
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
authority,
amount: kb_lib::ExSplTokenAmount(request.amount_raw.trim().to_string()),
decimals: request.decimals,
},
"token2022_freeze_account" => kb_lib::ExSplTokenSingleOperation::FreezeAccount {
account: kb_lib::MdPubkey(request.source.trim().to_string()),
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
authority: freeze_authority,
},
"token2022_thaw_account" => kb_lib::ExSplTokenSingleOperation::ThawAccount {
account: kb_lib::MdPubkey(request.source.trim().to_string()),
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
authority: freeze_authority,
},
"token2022_close_destination" => kb_lib::ExSplTokenSingleOperation::CloseAccount {
account: kb_lib::MdPubkey(request.source.trim().to_string()),
destination: kb_lib::MdPubkey(request.destination.trim().to_string()),
authority,
},
other => {
return std::result::Result::Err(format!(
"unsupported public Token-2022 Devnet scenario {other}"
));
},
};
return std::result::Result::Ok(kb_lib::ExSplToken2022Operation::Instruction {
value: std::boxed::Box::new(value),
});
}
pub(crate) fn demo_spl_token2022_summary_payload(
scenario_id: std::string::String,
operation: std::string::String,
summary: kb_pipeline_demo_scenarios::DevnetSplToken2022ExecutionSummary,
) -> crate::DemoExecutionSplToken2022SummaryPayload {
let plan_json = crate::pretty_json(&summary.plan);
let preflight_json = crate::pretty_json(&summary.stateful_preflight);
let simulation_json = crate::pretty_json(&summary.simulation);
let transaction_signature =
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
let confirmation_status = summary.confirmation.as_ref().map(|value| {
return crate::confirmation_status_code(value.status);
});
let post_execution = summary.post_execution.as_ref();
let canonical_inserted = post_execution.is_some_and(|value| return value.canonical_inserted);
let core_extracted = post_execution.is_some_and(|value| return value.core_extracted);
let decode_replayed = post_execution.is_some_and(|value| return value.decode_replayed);
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
return replay.failed_inputs == 0
&& !replay.cancelled
&& replay.processors.iter().all(|processor| {
return processor.failed == 0
&& processor.materialized_outputs == 0
&& processor.materialization_refused == 0;
});
});
let materialization_count = match u32::try_from(summary.materializations.len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => u32::MAX,
};
let diagnostics = serde_json::json!({
"confirmation": summary.confirmation,
"postExecution": summary.post_execution,
"backfill": summary.backfill.as_ref().map(backfill_summary_json),
"coreExtraction": summary.core_extraction.as_ref().map(core_extraction_summary_json),
"decodeReplay": summary.decode_replay.as_ref().map(decode_replay_summary_json),
"idempotenceReplay": summary.idempotence_replay.as_ref().map(decode_replay_summary_json),
"materializations": summary.materializations
});
return crate::DemoExecutionSplToken2022SummaryPayload {
scenario_id,
operation,
profile_name: summary.profile_name,
cluster: crate::execution_cluster_code(summary.cluster),
genesis_hash: summary.genesis_hash,
wallet_public_key: summary.wallet.public_key,
balance_lamports: summary.balance_lamports.to_string(),
fee_lamports: summary.fee.fee_lamports.map(|value| return value.to_string()),
simulation_success: summary.simulation.success,
simulation_error: summary.simulation.error,
transaction_signature,
confirmation_status,
canonical_inserted,
core_extracted,
decode_replayed,
materialization_count,
idempotence_validated,
plan_json,
preflight_json,
simulation_json,
diagnostics_json: crate::pretty_json(&diagnostics),
};
}
pub(crate) fn parse_fixture(
contents: &str,
) -> std::collections::BTreeMap<std::string::String, std::string::String> {
let mut values = std::collections::BTreeMap::new();
for line in contents.lines() {
let trimmed = line.trim();
let assignment = match trimmed.strip_prefix("export ") {
std::option::Option::Some(value) => value,
std::option::Option::None => continue,
};
let (name, raw_value) = match assignment.split_once('=') {
std::option::Option::Some(value) => value,
std::option::Option::None => continue,
};
let value = raw_value.trim().trim_matches('\'').trim_matches('"').to_string();
values.insert(name.trim().to_string(), value);
}
return values;
}
fn backfill_summary_json(summary: &kb_pipeline::BackfillSummary) -> serde_json::Value {
return serde_json::json!({
"captureSessionId": summary.capture_session_id,
"filterCode": summary.filter_code,
"role": summary.role,
"provider": summary.provider,
"endpointCode": summary.endpoint_code,
"pagesFetched": summary.pages_fetched,
"candidatesSelected": summary.candidates_selected,
"candidatesStarted": summary.candidates_started,
"candidatesCompleted": summary.candidates_completed,
"candidatesCancelled": summary.candidates_cancelled,
"candidatesNotStarted": summary.candidates_not_started,
"transactionsReceived": summary.transactions_received,
"canonicalInserted": summary.canonical_inserted,
"canonicalSkipped": summary.canonical_skipped,
"existingSkipped": summary.existing_skipped,
"missing": summary.missing,
"failed": summary.failed,
"observationsInserted": summary.observations_inserted,
"attempts": summary.attempts,
"cancelled": summary.cancelled,
"resumeBeforeSignature": summary.resume_before_signature,
"startedAt": summary.started_at,
"finishedAt": summary.finished_at
});
}
fn core_extraction_summary_json(summary: &kb_pipeline::CoreExtractionSummary) -> serde_json::Value {
return serde_json::json!({
"processorVersion": summary.processor_version,
"selected": summary.selected,
"started": summary.started,
"completed": summary.completed,
"skipped": summary.skipped,
"extracted": summary.extracted,
"failed": summary.failed,
"cancelledCandidates": summary.cancelled_candidates,
"notStarted": summary.not_started,
"cancelled": summary.cancelled,
"startedAt": summary.started_at,
"finishedAt": summary.finished_at
});
}
fn decode_replay_summary_json(summary: &kb_pipeline::DecodeReplaySummary) -> serde_json::Value {
let processors = summary
.processors
.iter()
.map(|processor| {
return serde_json::json!({
"name": processor.processor_name,
"version": processor.processor_version,
"dispatched": processor.dispatched,
"skipped": processor.skipped,
"decoded": processor.decoded,
"ignored": processor.ignored,
"unsupported": processor.unsupported,
"failed": processor.failed,
"materializedOutputs": processor.materialized_outputs,
"materializationRefused": processor.materialization_refused
});
})
.collect::<std::vec::Vec<serde_json::Value>>();
return serde_json::json!({
"campaignId": summary.campaign_id,
"pipelineVersion": summary.pipeline_version,
"selected": summary.selected,
"started": summary.started,
"completed": summary.completed,
"unmatched": summary.unmatched,
"notStarted": summary.not_started,
"failedInputs": summary.failed_inputs,
"cancelled": summary.cancelled,
"processors": processors,
"startedAt": summary.started_at,
"finishedAt": summary.finished_at
});
}
#[cfg(test)]
mod tests {
fn request(scenario_id: &str) -> crate::DemoExecutionSplToken2022Request {
return crate::DemoExecutionSplToken2022Request {
profile_name: "local_devnet".to_string(),
scenario_id: scenario_id.to_string(),
source: "source".to_string(),
mint: "mint".to_string(),
destination: "destination".to_string(),
delegate: "delegate".to_string(),
authority: "authority".to_string(),
freeze_authority: "freeze".to_string(),
amount_raw: "1".to_string(),
decimals: 9,
submit: false,
operator_confirmed: false,
force_post_validation_replay: true,
};
}
#[test]
fn every_public_scenario_builds_one_typed_operation() {
for scenario in [
"token2022_mint_to_checked",
"token2022_transfer_checked",
"token2022_approve_checked",
"token2022_revoke",
"token2022_burn_checked",
"token2022_freeze_account",
"token2022_thaw_account",
"token2022_close_destination",
] {
assert!(crate::operation_from_request(&request(scenario)).is_ok());
}
}
}

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/lib.rs
// version: 7
// version: 9
//! Tauri desktop demo application for `khadhroony-bot3`.
@@ -13,7 +13,12 @@ mod demo_backfill;
mod demo_config;
mod demo_core_extraction;
mod demo_decode_replay;
mod demo_execution_solana_core;
mod demo_execution_spl;
mod demo_http;
mod demo_spl_ata;
mod demo_spl_token;
mod demo_spl_token2022;
mod demo_sql_common;
mod demo_sql_diag;
mod demo_sql_pg_core;
@@ -54,6 +59,100 @@ pub(crate) use self::demo_backfill::demo_backfill_summary_payload;
pub(crate) use self::demo_config::DemoConfigPayload;
/// Builds the configuration payload from shared application state.
pub(crate) use self::demo_config::demo_config_payload;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionObserver;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionOptionsPayload;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionProgressPayload;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionRequest;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionRunGuard;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionSummaryPayload;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::build_demo_core_extraction_pipeline_request;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::demo_core_extraction_summary_payload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeCoverageSummaryPayload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeDiagnosticsPayload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeProcessorSummaryPayload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayDecoderOption;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayObserver;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayOptionsPayload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayProgressPayload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayRequest;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayRunGuard;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplaySummaryPayload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRequest;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRow;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::annotation_payload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::available_decoders;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::available_materializers;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::build_demo_decode_replay_pipeline_request;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::coverage_payload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::demo_decode_replay_summary_payload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::optional_line_count;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::register_active_campaign;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::text_sample;
/// Request sent by the Memo v4 Devnet execution panel.
pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoRequest;
/// UI-safe result of one Memo v4 Devnet execution orchestration.
pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoSummaryPayload;
/// Public key generated for a disposable Devnet recipient.
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreGeneratedRecipientPayload;
/// ????
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreObserver;
/// Initial options shown by the Solana Core execution demo.
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreOptionsPayload;
/// One Devnet execution profile exposed to the demo window.
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreProfileOption;
/// Progress event emitted during execution and post-validation.
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreProgressPayload;
/// Request sent by the execution demo.
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreRequest;
/// ????
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreRunGuard;
/// UI-safe result of one execution orchestration.
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreSummaryPayload;
/// ????
pub(crate) use self::demo_execution_solana_core::confirmation_status_code;
/// ????
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_memo_summary_payload;
/// ????
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_summary_payload;
/// ????
pub(crate) use self::demo_execution_solana_core::devnet_profile_options;
/// ????
pub(crate) use self::demo_execution_solana_core::execution_cluster_code;
/// ????
pub(crate) use self::demo_execution_solana_core::pretty_json;
/// ????
pub(crate) use self::demo_execution_solana_core::select_devnet_profile;
/// Frontend payload for one independent Devnet SPL validation scenario.
pub(crate) use self::demo_execution_spl::DevnetSplValidationScenarioPayload;
/// HTTP demo response payload.
pub(crate) use self::demo_http::DemoHttpExecutionPayload;
/// One selectable HTTP JSON-RPC method.
@@ -70,12 +169,66 @@ pub(crate) use self::demo_http::build_http_method_options;
pub(crate) use self::demo_http::build_http_role_options;
/// Executes one raw HTTP JSON-RPC request.
pub(crate) use self::demo_http::demo_http_execute_request_inner;
/// Request for one representative ATA creation on Devnet.
pub(crate) use self::demo_spl_ata::DemoExecutionSplAtaRequest;
/// UI-safe ATA execution result.
pub(crate) use self::demo_spl_ata::DemoExecutionSplAtaSummaryPayload;
/// Readonly payer, wallet, Token Program and derived ATA values.
pub(crate) use self::demo_spl_ata::DemoSplAtaDerivationPayload;
/// Request used to derive the representative wallet ATA before execution.
pub(crate) use self::demo_spl_ata::DemoSplAtaDerivationRequest;
/// Bounded ATA lifecycle journal request.
pub(crate) use self::demo_spl_ata::DemoSplAtaJournalRequest;
/// One UI-safe ATA lifecycle journal row.
pub(crate) use self::demo_spl_ata::DemoSplAtaJournalRow;
/// ????
pub(crate) use self::demo_spl_ata::demo_spl_ata_summary_payload;
/// ????
pub(crate) use self::demo_spl_ata::derive_ata;
/// ????
pub(crate) use self::demo_spl_ata::journal_matches;
/// ????
pub(crate) use self::demo_spl_ata::journal_row;
/// ????
pub(crate) use self::demo_spl_ata::load_profile_wallet;
/// ????
pub(crate) use self::demo_spl_ata::parse_token_program;
/// Request for one checked classic SPL Token transfer on Devnet.
pub(crate) use self::demo_spl_token::DemoExecutionSplTokenRequest;
/// UI-safe result of one checked SPL Token transfer orchestration.
pub(crate) use self::demo_spl_token::DemoExecutionSplTokenSummaryPayload;
/// Bounded exact filters for the classic SPL Token materialized journal.
pub(crate) use self::demo_spl_token::DemoSplTokenJournalRequest;
/// UI-safe materialized classic SPL Token journal row.
pub(crate) use self::demo_spl_token::DemoSplTokenJournalRow;
/// ????
pub(crate) use self::demo_spl_token::demo_spl_token_journal_row;
/// Executes one checked SPL Token simulation or explicitly authorized Devnet submission.
pub(crate) use self::demo_spl_token::demo_spl_token_summary_payload;
/// ????
pub(crate) use self::demo_spl_token::payload_matches;
/// Loads a bounded, typed journal of committed classic SPL Token projections.
pub(crate) use self::demo_spl_token::validate_journal_request;
/// Request for one independent public Token-2022 Devnet scenario.
pub(crate) use self::demo_spl_token2022::DemoExecutionSplToken2022Request;
/// UI-safe result of one public Token-2022 Devnet orchestration.
pub(crate) use self::demo_spl_token2022::DemoExecutionSplToken2022SummaryPayload;
/// Public values loaded from one persisted Token-2022 validation fixture.
pub(crate) use self::demo_spl_token2022::DemoSplToken2022FixturePayload;
/// ????
pub(crate) use self::demo_spl_token2022::demo_spl_token2022_summary_payload;
/// ????
pub(crate) use self::demo_spl_token2022::operation_from_request;
/// ????
pub(crate) use self::demo_spl_token2022::parse_fixture;
/// UI-safe SQL table diagnostic snapshot.
pub(crate) use self::demo_sql_common::DemoSqlTableSnapshot;
/// Connects to the configured PostgreSQL store.
pub(crate) use self::demo_sql_common::connect_postgres_store;
/// Formats diagnostic statuses.
pub(crate) use self::demo_sql_common::debug_status;
/// Initializes the PostgreSQL schema during startup.
pub(crate) use self::demo_sql_common::initialize_postgres_schema_for_startup;
/// Opens or focuses one SQL demo window.
pub(crate) use self::demo_sql_common::open_sql_demo_window;
/// Builds PostgreSQL options from the active profile.
@@ -154,6 +307,8 @@ pub(crate) use self::demo_ws::demo_ws_disconnect_inner;
pub(crate) use self::demo_ws::demo_ws_status_inner;
/// Unsubscribes one active WebSocket subscription.
pub(crate) use self::demo_ws::demo_ws_unsubscribe_inner;
/// Disconnects the persistent WebSocket session during application shutdown.
pub(crate) use self::demo_ws::disconnect_demo_ws_app_state;
/// Frontend logging payload.
pub(crate) use self::frontend_log::FrontendLogPayload;
/// Emits one normalized frontend log event.
@@ -175,64 +330,6 @@ pub(crate) use self::splash::emit_splash_order;
/// Waits for the minimum splash duration.
pub(crate) use self::splash::wait_until_minimum;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionObserver;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionOptionsPayload;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionProgressPayload;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionRequest;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionRunGuard;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionSummaryPayload;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::build_demo_core_extraction_pipeline_request;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::demo_core_extraction_summary_payload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeCoverageSummaryPayload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeDiagnosticsPayload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeProcessorSummaryPayload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayDecoderOption;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayObserver;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayOptionsPayload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayProgressPayload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayRequest;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayRunGuard;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplaySummaryPayload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRequest;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRow;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::annotation_payload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::available_decoders;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::available_materializers;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::build_demo_decode_replay_pipeline_request;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::coverage_payload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::demo_decode_replay_summary_payload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::optional_line_count;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::register_active_campaign;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::text_sample;
// Keep the canonical tracing target as the final facade export.
/// Canonical tracing target for the desktop demo application.
pub(crate) use self::constants::TRACING_TARGET;

View File

@@ -64,6 +64,21 @@ pub fn run() -> kb_core::Result<()> {
load_demo_sql_replay_programs,
load_demo_sql_replay_entities,
export_demo_sql_replay_csv,
demo_execution_solana_core_options,
demo_execution_solana_core_generate_recipient,
demo_execution_solana_core_execute,
open_demo_execution_solana_core_window,
demo_execution_spl_memo_execute,
demo_execution_solana_core_cancel,
open_demo_execution_spl_window,
demo_execution_spl_validation_scenarios,
demo_execution_spl_token_execute,
demo_spl_token_journal,
demo_execution_spl_token2022_execute,
demo_spl_token2022_fixture,
demo_spl_ata_derive,
demo_execution_spl_ata_execute,
demo_spl_ata_journal,
open_demo_core_extraction_window,
demo_core_extraction_options,
demo_core_extraction_execute,
@@ -1307,3 +1322,867 @@ async fn load_demo_sql_replay_entities(
}
return std::result::Result::Ok(output);
}
/// Opens or focuses the Solana Core execution demo window.
#[tauri::command]
fn open_demo_execution_solana_core_window(
app_handle: tauri::AppHandle,
) -> std::result::Result<(), std::string::String> {
let existing_window = app_handle.get_webview_window("demo_execution_solana_core");
if let std::option::Option::Some(window) = existing_window {
let show_result = window.show();
if let std::result::Result::Err(error) = show_result {
return std::result::Result::Err(error.to_string());
}
let focus_result = window.set_focus();
if let std::result::Result::Err(error) = focus_result {
return std::result::Result::Err(error.to_string());
}
return std::result::Result::Ok(());
}
let build_result = tauri::WebviewWindowBuilder::new(
&app_handle,
"demo_execution_solana_core",
tauri::WebviewUrl::App("demo_execution_solana_core.html".into()),
)
.title("Khadhroony Bot3 - Exécution Solana Devnet")
.inner_size(1360.0, 920.0)
.min_inner_size(1040.0, 700.0)
.resizable(true)
.visible(true)
.build();
return match build_result {
std::result::Result::Ok(window) => {
let focus_result = window.set_focus();
if let std::result::Result::Err(error) = focus_result {
return std::result::Result::Err(error.to_string());
}
std::result::Result::Ok(())
},
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}
/// Returns compatible Devnet profiles and conservative defaults.
#[tauri::command]
fn demo_execution_solana_core_options(
state: tauri::State<'_, crate::AppState>,
) -> crate::DemoExecutionSolanaCoreOptionsPayload {
let profiles = crate::devnet_profile_options(state.app_config());
let default_profile_name = profiles.first().map(|profile| return profile.name.clone());
return crate::DemoExecutionSolanaCoreOptionsPayload {
profiles,
default_profile_name,
default_transfer_lamports: 1_000_000,
running: state
.demo_execution_solana_core_running()
.load(std::sync::atomic::Ordering::Acquire),
};
}
/// Generates a disposable recipient public key without exposing its private key.
#[tauri::command]
fn demo_execution_solana_core_generate_recipient()
-> std::result::Result<crate::DemoExecutionSolanaCoreGeneratedRecipientPayload, std::string::String>
{
let alias = match kb_wallet::WalletAlias::parse("demo-devnet-recipient") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let wallet = kb_wallet::TemporaryWallet::generate(alias);
return std::result::Result::Ok(crate::DemoExecutionSolanaCoreGeneratedRecipientPayload {
public_key: wallet.public_key(),
});
}
/// Executes one bounded System transfer simulation or Devnet submission.
#[tauri::command]
#[allow(clippy::question_mark_used)]
async fn demo_execution_solana_core_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionSolanaCoreRequest,
) -> std::result::Result<crate::DemoExecutionSolanaCoreSummaryPayload, std::string::String> {
let acquire_result = state.demo_execution_solana_core_running().compare_exchange(
false,
true,
std::sync::atomic::Ordering::AcqRel,
std::sync::atomic::Ordering::Acquire,
);
if acquire_result.is_err() {
return std::result::Result::Err("a Solana Core execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
.demo_execution_solana_core_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let initialize_result = store.initialize_store_schema().await;
if let std::result::Result::Err(error) = initialize_result {
return std::result::Result::Err(error.to_string());
}
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetSystemTransferRequest::new(
format!("demo-execution-{}", chrono::Utc::now().timestamp_micros()),
kb_lib::MdPubkey(request.recipient.trim().to_string()),
request.lamports,
);
pipeline_request.airdrop_lamports = request.airdrop_lamports;
pipeline_request.submit = request.submit;
pipeline_request.operator_confirmed = request.operator_confirmed;
pipeline_request.post_validation_max_retries = 20;
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
pipeline_request.materialize_after_decode = request.materialize_after_decode;
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
std::vec![std::sync::Arc::new(kb_lib::DcSolanaCoreDecoder)];
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> =
if request.materialize_after_decode {
std::vec![
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer,),
std::sync::Arc::new(kb_lib::MtLifecycleMaterializer),
std::sync::Arc::new(kb_lib::MtStakingMaterializer),
]
} else {
std::vec::Vec::new()
};
let observer = crate::DemoExecutionSolanaCoreObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
let workspace_root = crate::workspace_root_dir();
let summary = match kb_pipeline_demo_scenarios::execute_devnet_system_transfer(
&http_pool,
&store,
&profile,
workspace_root.as_path(),
&pipeline_request,
decoders.as_slice(),
materializers.as_slice(),
&observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return std::result::Result::Ok(crate::demo_execution_solana_core_summary_payload(summary));
}
/// Executes one Memo v4 simulation or explicitly authorized Devnet submission.
#[tauri::command]
#[allow(clippy::question_mark_used)]
async fn demo_execution_spl_memo_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMemoRequest,
) -> std::result::Result<crate::DemoExecutionMemoSummaryPayload, std::string::String> {
let acquire_result = state.demo_execution_solana_core_running().compare_exchange(
false,
true,
std::sync::atomic::Ordering::AcqRel,
std::sync::atomic::Ordering::Acquire,
);
if acquire_result.is_err() {
return std::result::Result::Err("a Devnet execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
.demo_execution_solana_core_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetMemoExecutionRequest::new(
format!("demo-memo-execution-{}", chrono::Utc::now().timestamp_micros()),
request.message,
);
pipeline_request.include_wallet_as_memo_signer = request.include_wallet_as_memo_signer;
pipeline_request.submit = request.submit;
pipeline_request.operator_confirmed = request.operator_confirmed;
pipeline_request.post_validation_max_retries = 20;
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
std::vec![std::sync::Arc::new(kb_lib::DcSplMemoDecoder)];
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> =
std::vec![std::sync::Arc::new(kb_lib::MtTransactionAnnotationMaterializer,)];
let observer = crate::DemoExecutionSolanaCoreObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
let workspace_root = crate::workspace_root_dir();
let summary = match kb_pipeline_demo_scenarios::execute_devnet_memo(
&http_pool,
&store,
&profile,
workspace_root.as_path(),
&pipeline_request,
decoders.as_slice(),
materializers.as_slice(),
&observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return std::result::Result::Ok(crate::demo_execution_solana_core_memo_summary_payload(
summary,
));
}
/// Requests cooperative cancellation before the next execution stage.
#[tauri::command]
fn demo_execution_solana_core_cancel(state: tauri::State<'_, crate::AppState>) -> bool {
let running = state
.demo_execution_solana_core_running()
.load(std::sync::atomic::Ordering::Acquire);
if !running {
return false;
}
state
.demo_execution_solana_core_cancel_requested()
.store(true, std::sync::atomic::Ordering::Release);
return true;
}
/// Returns the complete ordered Devnet SPL validation inventory for milestone 0.4.6.
#[tauri::command]
fn demo_execution_spl_validation_scenarios()
-> std::vec::Vec<crate::DevnetSplValidationScenarioPayload> {
return kb_pipeline_demo_scenarios::devnet_spl_validation_scenarios()
.into_iter()
.map(|scenario| {
return crate::DevnetSplValidationScenarioPayload {
id: scenario.id,
label: scenario.label,
family: match scenario.family {
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::Token2022Public => {
"token2022_public".to_string()
},
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::ElGamalRegistry => {
"elgamal_registry".to_string()
},
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::Token2022Confidential => {
"token2022_confidential".to_string()
},
},
operation_code: scenario.operation_code,
implementation_status: match scenario.implementation_status {
kb_pipeline_demo_scenarios::DevnetSplValidationImplementationStatus::Executable => {
"executable".to_string()
},
kb_pipeline_demo_scenarios::DevnetSplValidationImplementationStatus::BackendReady => {
"backend_ready".to_string()
},
kb_pipeline_demo_scenarios::DevnetSplValidationImplementationStatus::ProofFixtureRequired => {
"proof_fixture_required".to_string()
},
},
proof_required: scenario.proof_required,
required_fixture_variables: scenario.required_fixture_variables,
};
})
.collect();
}
/// Opens or focuses the dedicated SPL execution window.
#[tauri::command]
fn open_demo_execution_spl_window(
app_handle: tauri::AppHandle,
) -> std::result::Result<(), std::string::String> {
tracing::info!(target: crate::TRACING_TARGET, "open SPL Devnet execution window");
let existing_window = app_handle.get_webview_window("demo_execution_spl");
if let std::option::Option::Some(window) = existing_window {
let show_result = window.show();
if let std::result::Result::Err(error) = show_result {
return std::result::Result::Err(error.to_string());
}
let focus_result = window.set_focus();
if let std::result::Result::Err(error) = focus_result {
return std::result::Result::Err(error.to_string());
}
return std::result::Result::Ok(());
}
let build_result = tauri::WebviewWindowBuilder::new(
&app_handle,
"demo_execution_spl",
tauri::WebviewUrl::App("demo_execution_spl.html".into()),
)
.title("Khadhroony Bot3 - Exécution SPL Devnet")
.inner_size(1500.0, 960.0)
.min_inner_size(1120.0, 720.0)
.resizable(true)
.visible(true)
.build();
return match build_result {
std::result::Result::Ok(window) => {
let focus_result = window.set_focus();
if let std::result::Result::Err(error) = focus_result {
return std::result::Result::Err(error.to_string());
}
std::result::Result::Ok(())
},
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}
/// Executes one checked SPL Token simulation or explicitly authorized Devnet submission.
#[tauri::command]
#[allow(clippy::question_mark_used)]
async fn demo_execution_spl_token_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionSplTokenRequest,
) -> std::result::Result<crate::DemoExecutionSplTokenSummaryPayload, std::string::String> {
let acquire_result = state.demo_execution_solana_core_running().compare_exchange(
false,
true,
std::sync::atomic::Ordering::AcqRel,
std::sync::atomic::Ordering::Acquire,
);
if acquire_result.is_err() {
return std::result::Result::Err("a Devnet execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
.demo_execution_solana_core_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let amount_raw = request.amount_raw.trim().to_string();
let operation = kb_lib::ExSplClassicTokenOperation::Instruction {
value: kb_lib::ExSplClassicTokenSingleOperation::TransferChecked {
source: kb_lib::MdPubkey(request.source.trim().to_string()),
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
destination: kb_lib::MdPubkey(request.destination.trim().to_string()),
authority: kb_lib::ExSplClassicTokenAuthority {
authority: kb_lib::MdPubkey(request.authority.trim().to_string()),
multisig_signers: std::vec::Vec::new(),
},
amount: kb_lib::ExSplClassicTokenAmount(amount_raw.clone()),
decimals: request.decimals,
},
};
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetSplTokenExecutionRequest::new(
format!("demo-spl-token-execution-{}", chrono::Utc::now().timestamp_micros()),
operation,
);
pipeline_request.submit = request.submit;
pipeline_request.operator_confirmed = request.operator_confirmed;
pipeline_request.post_validation_max_retries = 20;
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
std::vec![std::sync::Arc::new(kb_lib::DcSplTokenDecoder)];
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> = std::vec![
std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer),
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
std::sync::Arc::new(kb_lib::MtRiskMaterializer),
];
let observer = crate::DemoExecutionSolanaCoreObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
let workspace_root = crate::workspace_root_dir();
let summary = match kb_pipeline_demo_scenarios::execute_devnet_spl_token(
&http_pool,
&store,
&profile,
workspace_root.as_path(),
&pipeline_request,
decoders.as_slice(),
materializers.as_slice(),
&observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return std::result::Result::Ok(crate::demo_spl_token_summary_payload(
summary,
amount_raw,
request.decimals,
));
}
/// Loads a bounded, typed journal of committed classic SPL Token projections.
#[tauri::command]
#[allow(clippy::question_mark_used)]
async fn demo_spl_token_journal(
state: tauri::State<'_, crate::AppState>,
request: crate::DemoSplTokenJournalRequest,
) -> std::result::Result<std::vec::Vec<crate::DemoSplTokenJournalRow>, std::string::String> {
let validated = match crate::validate_journal_request(request) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let profile =
match crate::select_devnet_profile(state.app_config(), validated.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let needs_payload_filter =
validated.mint.is_some() || validated.account.is_some() || validated.operation.is_some();
let query_limit = if needs_payload_filter {
kb_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS
} else {
validated.limit
};
let filter = match kb_store::MaterializedEventFilter::new(
std::option::Option::None,
validated.family.clone(),
validated.signature_contains.clone(),
query_limit,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let rows = match kb_store::DecodePipelineStore::list_materialized_events(&store, &filter).await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let mut output = std::vec::Vec::new();
for row in rows {
if row.source_decoder_name != "spl_token"
|| !crate::payload_matches(&row.payload_json, &validated)
{
continue;
}
output.push(crate::demo_spl_token_journal_row(row));
if output.len() >= validated.limit as usize {
break;
}
}
return std::result::Result::Ok(output);
}
/// Derives the representative profile wallet ATA without exposing private key material.
#[tauri::command]
#[allow(clippy::question_mark_used)]
async fn demo_spl_ata_derive(
state: tauri::State<'_, crate::AppState>,
request: crate::DemoSplAtaDerivationRequest,
) -> std::result::Result<crate::DemoSplAtaDerivationPayload, std::string::String> {
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = match crate::load_profile_wallet(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet_owner = wallet.public_key();
let token_program = match crate::parse_token_program(&request.token_program) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let associated_token_account = match crate::derive_ata(
wallet_owner.as_str(),
request.mint.trim(),
token_program.program_id(),
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::DemoSplAtaDerivationPayload {
payer: wallet_owner.clone(),
wallet_owner,
token_program_id: token_program.program_id().to_string(),
associated_token_account,
});
}
/// Executes one representative Create or CreateIdempotent ATA flow.
#[tauri::command]
#[allow(clippy::question_mark_used)]
async fn demo_execution_spl_ata_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionSplAtaRequest,
) -> std::result::Result<crate::DemoExecutionSplAtaSummaryPayload, std::string::String> {
let acquired = state.demo_execution_solana_core_running().compare_exchange(
false,
true,
std::sync::atomic::Ordering::AcqRel,
std::sync::atomic::Ordering::Acquire,
);
if acquired.is_err() {
return std::result::Result::Err("a Devnet execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
.demo_execution_solana_core_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let profile_wallet = match crate::load_profile_wallet(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if request.wallet_owner.trim() != profile_wallet.public_key() {
return std::result::Result::Err(
"the representative ATA panel requires the profile wallet as wallet owner".to_string(),
);
}
let token_program = match crate::parse_token_program(&request.token_program) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet_owner = kb_lib::MdPubkey(request.wallet_owner.trim().to_string());
let mint = kb_lib::MdPubkey(request.mint.trim().to_string());
let operation = match request.mode.as_str() {
"create" => kb_lib::ExSplAssociatedTokenAccountOperation::Create {
wallet_owner,
mint,
token_program,
},
"create_idempotent" => kb_lib::ExSplAssociatedTokenAccountOperation::CreateIdempotent {
wallet_owner,
mint,
token_program,
},
_ => {
return std::result::Result::Err(
"ATA mode must be create or create_idempotent".to_string(),
);
},
};
let mut pipeline_request =
kb_pipeline_demo_scenarios::DevnetSplAssociatedTokenAccountExecutionRequest::new(
format!("demo-spl-ata-execution-{}", chrono::Utc::now().timestamp_micros()),
operation,
);
pipeline_request.submit = request.submit;
pipeline_request.operator_confirmed = request.operator_confirmed;
pipeline_request.post_validation_max_retries = 20;
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> = std::vec![
std::sync::Arc::new(kb_lib::DcSplAssociatedTokenAccountDecoder),
std::sync::Arc::new(kb_lib::DcSplTokenDecoder),
];
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> = std::vec![
std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer),
std::sync::Arc::new(kb_lib::MtRiskMaterializer),
];
let observer = crate::DemoExecutionSolanaCoreObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
let summary = match kb_pipeline_demo_scenarios::execute_devnet_spl_associated_token_account(
&http_pool,
&store,
&profile,
crate::workspace_root_dir().as_path(),
&pipeline_request,
decoders.as_slice(),
materializers.as_slice(),
&observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return std::result::Result::Ok(crate::demo_spl_ata_summary_payload(summary));
}
/// Loads a bounded journal of ATA-owned lifecycle facts.
#[tauri::command]
#[allow(clippy::question_mark_used)]
async fn demo_spl_ata_journal(
state: tauri::State<'_, crate::AppState>,
request: crate::DemoSplAtaJournalRequest,
) -> std::result::Result<std::vec::Vec<crate::DemoSplAtaJournalRow>, std::string::String> {
if request.limit == 0 || request.limit > 500 {
return std::result::Result::Err("ATA journal limit must be between 1 and 500".to_string());
}
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let filter = match kb_store::MaterializedEventFilter::new(
std::option::Option::Some("spl_token_accounts".to_string()),
std::option::Option::None,
request.signature_contains.clone(),
500,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let rows = match kb_store::DecodePipelineStore::list_materialized_events(&store, &filter).await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let mut output = std::vec::Vec::new();
for row in rows {
if row.source_decoder_name != "spl_associated_token_account"
|| !crate::journal_matches(&row.payload_json, &request)
{
continue;
}
output.push(crate::journal_row(row));
if output.len() >= request.limit as usize {
break;
}
}
return std::result::Result::Ok(output);
}
/// Executes one public Token-2022 simulation or explicitly authorized Devnet submission.
#[tauri::command]
#[allow(clippy::question_mark_used)]
async fn demo_execution_spl_token2022_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionSplToken2022Request,
) -> std::result::Result<crate::DemoExecutionSplToken2022SummaryPayload, std::string::String> {
let acquire_result = state.demo_execution_solana_core_running().compare_exchange(
false,
true,
std::sync::atomic::Ordering::AcqRel,
std::sync::atomic::Ordering::Acquire,
);
if acquire_result.is_err() {
return std::result::Result::Err("a Devnet execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
.demo_execution_solana_core_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let operation = match crate::operation_from_request(&request) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let operation_code = operation.operation_code().to_string();
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetSplToken2022ExecutionRequest::new(
format!("demo-spl-token-2022-execution-{}", chrono::Utc::now().timestamp_micros()),
operation,
);
pipeline_request.submit = request.submit;
pipeline_request.operator_confirmed = request.operator_confirmed;
pipeline_request.post_validation_max_retries = 20;
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
std::vec![std::sync::Arc::new(kb_lib::DcSplToken2022Decoder)];
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> = std::vec![
std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer),
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
std::sync::Arc::new(kb_lib::MtRiskMaterializer),
std::sync::Arc::new(kb_lib::MtFeesMaterializer),
std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer),
];
let observer = crate::DemoExecutionSolanaCoreObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
let workspace_root = crate::workspace_root_dir();
let summary = match kb_pipeline_demo_scenarios::execute_devnet_spl_token2022(
&http_pool,
&store,
&profile,
workspace_root.as_path(),
&pipeline_request,
decoders.as_slice(),
materializers.as_slice(),
&observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return std::result::Result::Ok(crate::demo_spl_token2022_summary_payload(
request.scenario_id,
operation_code,
summary,
));
}
/// Loads public Token-2022 fixture values without reading private key bytes.
#[tauri::command]
fn demo_spl_token2022_fixture(
state: tauri::State<'_, crate::AppState>,
profile_name: std::string::String,
) -> std::result::Result<crate::DemoSplToken2022FixturePayload, std::string::String> {
let profile = match crate::select_devnet_profile(state.app_config(), profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
let wallet_dir = if configured.is_absolute() {
configured
} else {
crate::workspace_root_dir().join(configured)
};
let fixture_path = wallet_dir.join("spl_token2022_validation").join("fixture.env");
let contents = match std::fs::read_to_string(&fixture_path) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!(
"unable to read Token-2022 fixture {}: {error}",
fixture_path.display()
));
},
};
let values = crate::parse_fixture(contents.as_str());
let required = |name: &str| -> std::result::Result<std::string::String, std::string::String> {
return match values.get(name) {
std::option::Option::Some(value) if !value.trim().is_empty() => {
std::result::Result::Ok(value.clone())
},
_ => std::result::Result::Err(format!("Token-2022 fixture is missing {name}")),
};
};
let program_id = match required("TOKEN_2022_PROGRAM") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mint = match required("TOKEN_2022_MINT") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let source = match required("TOKEN_2022_SOURCE") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let destination = match required("TOKEN_2022_DESTINATION") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let close_account = match required("TOKEN_2022_CLOSE_ACCOUNT") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let delegate = match required("TOKEN_2022_DELEGATE") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let authority = match required("TOKEN_2022_AUTHORITY") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let freeze_authority = match required("TOKEN_2022_FREEZE_AUTHORITY") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let decimals_text = match required("TOKEN_2022_DECIMALS") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let decimals = match decimals_text.parse::<u8>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!("invalid TOKEN_2022_DECIMALS value: {error}"));
},
};
return std::result::Result::Ok(crate::DemoSplToken2022FixturePayload {
fixture_path: fixture_path.display().to_string(),
program_id,
mint,
source,
destination,
close_account,
delegate,
authority,
freeze_authority,
decimals,
default_amount_raw: values
.get("TOKEN_2022_TRANSFER_AMOUNT_RAW")
.cloned()
.unwrap_or_else(|| return "1".to_string()),
});
}

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/vite.config.ts
// version: 5
// version: 6
import { defineConfig, normalizePath } from "vite";
import { NodePackageImporter } from "sass-embedded";
@@ -33,6 +33,8 @@ export default defineConfig(() => ({
"demo_config": normalizePath(resolve(__dirname, 'frontend/demo_config.html')),
"demo_core_extraction": normalizePath(resolve(__dirname, 'frontend/demo_core_extraction.html')),
"demo_decode_replay": normalizePath(resolve(__dirname, 'frontend/demo_decode_replay.html')),
"demo_execution_solana_core": normalizePath(resolve(__dirname, 'frontend/demo_execution_solana_core.html')),
"demo_execution_spl": normalizePath(resolve(__dirname, 'frontend/demo_execution_spl.html')),
},
output: {
entryFileNames: 'js/[name]-[hash].js',

View File